Reading data out of datastore with dialogflow - firebase

I'm doing a chatbot similar to helpdesk (Dialogflow - inline editor). I'm able to write to datastore but I`m facing some issues with read out of data, it is a basic operation of finding UserID but code is not kicking off -please help code below.
const Datastore = require('#google-cloud/datastore');
const datastore = new Datastore({
projectId: 'bot-datastore-mnddjv'
});
function write(agent) {
var name = agent.parameters.name;
var sur = agent.parameters.sur;
var uid = agent.parameters.uid;
const taskKey = datastore.key('Key');
const entity = {
key: taskKey,
data: {
name: name,
sur: sur,
uid: uid
}
};
return datastore.save(entity).then(() => {
console.log(`Saved ${entity.key.name}: ${entity.data.item_name}`);
agent.add(`Stored ${name},${sur}`); -----> That part is working
});
}
function read(agent){
const query = datastore.createQuery('Key').filter('name');
return datastore.runQuery(query).then(() =>{
const sortA = query.order('name');
const sortD = query.order('name',( {descending:true}));
agent.add("Scores: ",sortA); ----// This funcion is not working
});
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('Zapis', write);
intentMap.set('Odczyt', read);
agent.handleRequest(intentMap);
});

The issue seems to be in your usage of filter().
You need to use an operator like =, > etc. so if that condition is met the query will run.
Here is a sample code from the documentation.
const query = datastore
.createQuery('Task')
.filter('done', '=', false)
.filter('priority', '>=', 4)
.order('priority', {
descending: true,
});

Related

How can I avoid a race condition when updating a field that's dependent multiple documents in a subcollection in Google cloud firestore?

Here's a simplified representation of the data models for my firestore collections
// /posts/{postId}
interface Post {
id: string;
lastCommentedAt: Timestamp | null
}
// /posts/{postId}/comments/{commentId}
interface Comment {
id: string;
createdAt: Timestamp;
}
So there's a collection of posts, and within each post is a subcollection of comments.
If a post has no comments, the lastCommentedAt field should be null. Otherwise, lastCommmentedAt should match the createdAt value for the most recent comment on that post. The utility of this field is to enable me to query for posts and sort them by ones that have recent comments.
I'm having trouble thinking through the scenario of deleting comments.
When a comment is deleted, the value of lastCommentedAt will become stale if the comment being deleted is the most recent comment on that post, and so will need to be updated. This is where I'm struggling to come up with a safe solution.
My first thought was to query for the most recent comment on the post that doesn't match the comment to be deleted, and then do a batched write where I delete the comment and update lastCommentedAt on the post. Example Javscript code using the Firebase web SDK here:
async function deleteComment(post, comment) {
const batch = writeBatch(firestore);
const postRef = doc("posts", post.id);
const commentRef = doc("posts", post.id, "comments", comment.id);
const commentsCollection = collection("posts", post.id, "comments");
const recentCommentSnapshot = await getDocs(
query(
commentsCollection,
where("id", "!=", comment.id),
orderBy("createdAt", "desc"),
limit(1)
)
);
let lastCommentedAt = null;
if (recentCommentSnapshot.docs.length > 0) {
lastCommentedAt = recentCommentSnapshot.docs[0].data().createdAt;
}
batch.delete(commentRef);
batch.update(postRef, { lastCommentedAt });
await batch.commit();
}
However, I believe the code above would be vulnerable to a race condition if new comments are created after the query but before the writes, or if the recent comment was deleted after the query but before the writes.
So I think I need a transaction, but you can't query for documents in a transaction, so I'm not really sure where to go from here.
You could use Firestore Getting real-time updates which listens to changes between snapshots. Here's an example from the documentation:
import { collection, query, where, onSnapshot } from "firebase/firestore";
const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === "added") {
console.log("New city: ", change.doc.data());
}
if (change.type === "modified") {
console.log("Modified city: ", change.doc.data());
}
if (change.type === "removed") {
console.log("Removed city: ", change.doc.data());
}
});
});
In here you can listen to any changes to your documents.
Check the code that I created which is triggered when the document is deleted from firestore and automatically update the posts.lastCommentedAt:
var latestCreatedAt = null;
const q = query(
collection(db, "posts", "post.id", "comments")
);
const recentCommentQuery = query(
collection(db, "posts", "post.id", "comments"),
orderBy("createdAt", "desc"),
limit(1)
);
const postsRef = query(collection(db, "posts"));
const postRef = doc(db, "posts", "post.id");
async function deletedComment (createdAt) {
const querySnapshot = await getDocs(recentCommentQuery);
querySnapshot.forEach((doc) => {
latestCreatedAt = doc.data().createdAt.toDate();
});
await updateDoc(postRef, {
lastCommentedAt: latestCreatedAt
})
}
const unsubscribe = onSnapshot(q, (snapshot, querySnapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === "removed") {
deletedComment(change.doc.data().createdAt.toDate());
}
});
});
You can even add a condition when adding or modifying documents that will also trigger to update the posts.lastCommentedAt.

Map Firestore saving field name

can you help me? I have a problem to my code coz instead of updating my map value the path changes also
const userId = firebase.auth().currentUser.uid;
const availableRecord = firebase.firestore().collection('orders').doc(this.state.OrderId);
availableRecord.update({
stores: { userId: 'On the way'}
}).then(( res) => {
console.log('Product is set into AVAILABLE')
})
Instead of
the result is
Using the square brackets notation, as follows, should do the trick:
const userId = firebase.auth().currentUser.uid;
const availableRecord = firebase.firestore().collection('orders').doc(this.state.OrderId);
const stores = {};
stores[userId] = 'On the way';
availableRecord.update({ stores }).then(() => {
console.log('Product is set into AVAILABLE');
});
Doing
availableRecord
.update({ stores: { [userId]: 'On the way' } })
also works, as you noted in your comment.

How to pass array for Dialogflow to firebase by getting data from several intents

I am trying to get data from multiple intents one by one and then store it in the firebase.
I am contact details from the user.
Main intention is to make the employees and employers get details of each other.
So once stored the data i would search the database and let the other one know the people satisfying their criteria.
My current fulfillment code looks like this:
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
//const {Card, Suggestion} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
admin.initializeApp();
let db = admin.firestore();
var a = {};
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
function add_name(agent)
{
const name = agent.parameters.name;
//agent.add('Thank you ! ' + name);
//a.name = agent.parameters.name;
db.collection('names').add({name : name});
}
function add_role(agent)
{
const role = agent.parameters.role;
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('get name', add_name);
intentMap.set('get role', add_role);
intentMap.set('get age', add_age);
intentMap.set('get phone', add_phone);
intentMap.set('get email', add_email);
intentMap.set('get experience', add_experience);
intentMap.set('get profile', add_profile);
intentMap.set('get salary', add_salary);
function add_salary(agent)
{
a.min_sal = agent.parameters.min_sal;
a.max_sal = agent.parameters.max_sal;
}
function add_profile(agent)
{
a.profile = agent.parameters.profile;
}
function add_experience(agent)
{
a.experience = agent.parameters.experience;
db.collection('test-data').add(a)
.then((d)=>{console.log(d);});
//console.log(a);
}
function add_age(agent)
{
a.age = agent.parameters.age;
}
function add_phone(agent)
{
a.phone = agent.parameters.phone;
}
function add_email(agent)
{
a.email = agent.parameters.email;
}
// intentMap.set('your intent name here', yourFunctionHandler);
// intentMap.set('your intent name here', googleAssistantHandler);
agent.handleRequest(intentMap);
});
I have tried the youtube tutorials but couldn't get anywhere.
It's Confusing how to get values in an array.
Thanks in advance!

dialogflow chatbot how to go to firestore database and return with support relevant questions?

I am using "firestore" database for my "dialogflow" chat bot which I already created for an online grocery store. The problem is: I want my chatbot to initially ask questions from users to find the proper item title in my database and then return to user by asking 3-4 support relevant questions about that item. the questions must be the item attributes (Brand, color, size...) and will vary from one item to another. so the chatbot will stream down the user to find the best item.
can you please help me to find the answer? I already created the codes but they don't work and I do not know what is wrong with that. If you already created this and have the index.js file, I appreciate to propose me here.
index.js:
'use strict';
const functions = require('firebase-functions');
// Import admin SDK
const admin = require('firebase-admin');
const {
WebhookClient
} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements
admin.initializeApp(functions.config().firebase);
// here we get the database in a variable
const db = admin.firestore();
const data = {...};
// Add a new document in collection "dialogflow" with document ID 'agent'
const dialogflowAgentRef = db.collection('dialogflow').doc('agent').set(data);
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({
request,
response
});
function writeToDb(agent) {
// Get parameter from Dialogflow with the string to add to the database doc
const databaseEntry = agent.parameters.databaseEntry;
// Get the database collection 'dialogflow' and document 'agent' and store
// the document {entry: "<value of database entry>"} in the 'agent' document
const dialogflowAgentRef = db.collection('dialogflow').doc('agent').where('title', '==', title);
return db.runTransaction(t => {
t.set(dialogflowAgentRef, {
entry: databaseEntry
});
return Promise.resolve('Write complete');
}).then(doc => {
agent.add(`Wrote "${databaseEntry}" to the Firestore database.`);
}).catch(err => {
console.log(`Error writing to Firestore: ${err}`);
agent.add(`Failed to write "${databaseEntry}" to the Firestore database.`);
});
}
function readFromDb(agent) {
// Get the database collection 'dialogflow' and document 'agent'
const dialogflowAgentDoc = db.collection('dialogflow/agent/rss/channel/item'); // .doc('agent')
// Get the value of 'entry' in the document and send it to the user
return dialogflowAgentDoc.get()
.then(doc => {
if (!doc.exists) {
agent.add('No data found in the database!');
} else {
agent.add(doc.data().entry);
}
return Promise.resolve('Read complete');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
agent.add('Please add a entry to the database first by saying, "Write <your phrase> to the database"');
});
}
// Map from Dialogflow intent names to functions to be run when the intent is matched
let intentMap = new Map();
intentMap.set('ReadFromFirestore', readFromDb);
intentMap.set('WriteToFirestore', writeToDb);
agent.handleRequest(intentMap);
});
There are a number of issues with your code as you've shown it that could cause problems reading and writing with the Firestore database.
It looks like you're trying to find an existing collection to write to with the line
const dialogflowAgentRef = db.collection('dialogflow').doc('agent').where('title', '==', title);
but title isn't defined anywhere, which I suspect causes an error. Furthermore, doc() returns a DocumentReference, but there is no where() method in a DocumentReference.
Remember that you need to structure Firestore using alternating collections and documents. So your "firebase" collection can contain a document named "agent", and that document may have subcollections.
When you're trying to read with
const dialogflowAgentDoc = db.collection('dialogflow/agent/rss/channel/item');
You're getting a collection, but then trying to treat it as a document. The comment suggests that you're trying to read a specific doc from this collection (which makes sense), but you're loading that document by a hard-coded string "agent", rather than trying to get the agent from the parameters passed to you from Dialogflow.
Finally - the paths in the read and write sections don't match. Using hard-coded paths are fine when testing, but make sure you're using matching paths and that they reflect the collection/doc/collection/doc/... path requirement.
So in both cases, you might have a reference that looks something like
const docTitle = agent.parameters.title;
const docRef = db.collection('dialogflow').doc(title);
Which, if you have defined a "title" parameter in your Intents in Dialogflow, will use this to reference the doc, which you can then read or write.
thanks for the answer I already changed my database to real time firebase instead of firestore. still having problem with support relevant questions. I want to go to my real time database to find the item by search using "oederByChild" and "equalTo" methods as I found these in people questions and answer in this website. still cannot find and item title through my database child. here is the codes are written:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {
WebhookClient
} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements
admin.initializeApp(functions.config().firebase);
const db = admin.database();
// const ref = db.ref('server/saving-data/fireblog');
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({
request,
response
});
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
// Get the database collection 'dialogflow' and document 'agent' and store
// the document {entry: "<value of database entry>"} in the 'agent' document
function writeToDb(agent) {
const databaseEntry = agent.parameters.databaseEntry;
const acc = db.ref('rss/channel/item/4/title'); //**This worked! */
acc.set({
entry: databaseEntry
});
return Promise.resolve('write complete')
.then(_acc => {
agent.add(`Wrote ${databaseEntry} to the realtime database.`);
return false;
}).catch(err => {
console.log(`Error writing to Firestore: ${err}`);
agent.add(`Failed to write "${databaseEntry}" to the Firestore database.`);
});
}
// and this is when we want to write to in the same child, keeping the old values:
//const acc = db.ref('/rss/channel/item/5/color'); //**This worked! */
//const result = acc.child(databaseEntry).set({entry: databaseEntry});
//agent.add(`Wrote ${databaseEntry} to the realtime database.`);
//console.log(result.key);
//});
// to read data
function readFromDb(agent) {
const any = agent.parameters.any;
agent.add(`Thank you...`);
var rootRef = db.ref();
var childref = rootRef.child("rss/channel/item");
return childref.orderByChild("title").equalTo("Icebreaker").once("value").then(function(snapshot){ //has been taken from the bus example: https://stackoverflow.com/questions/51917390/dialogflow-how-do-i-pass-a-parameter-through-in-a-firebase-query
var colored = snapshot.child("color/__text").val();
var sized = snapshot.child("size/__text").val();
agent.add(`Your search result for ` + any + ` Throughout the database is ` + colored +
` Color and ` + sized + ` Size`);
return Promise.resolve('Read complete');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
agent.add('Please add a entry to the database first by saying, "Write <your phrase> to the database"');
});
}
// Map from Dialogflow intent names to functions to be run when the intent is matched
let intentMap = new Map();
intentMap.set('IWannaBuy', readFromDb);
intentMap.set('WriteToFirebase', writeToDb);
agent.handleRequest(intentMap);
});
enter code here
[this is how my database is][1]
[1]: https://i.stack.imgur.com/QdFy5.png

Migrate Firebase Realtime Database to Firestore

I am looking for the best way to migrate my apps database which is using firebase realtime database to the new Cloud Firestore database. I am confident for the project I am working on I don't need to make any data schema changes, so I am pretty much just trying to 1-1 map it. Firebase has suggested on their site to just write a script to do this, but I am not sure of the best way to go about that. Has anyone already made a script that accomplishes this?
I wrote up a little node script that migrated things in a quick and dirty way and it worked quite nicely.
It is below if anyone else is interested.
Note: This should only be used if your data model in the realtime database was completely flat and did not have much nested data, and you intend on keeping your data flat as well in Firestore
To run this script just create a node file called index.js and throw it in a directory along with your service account file and raw json file from the realtime database export and run the following from command line.
$ node index.js
Script implementation below.
const admin = require('firebase-admin');
var serviceAccount = require("./config.json");
var database = require("./database.json");
var async = require ('async');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
var db = admin.firestore();
var allEntityNames = Object.keys(database);
var asyncTasks = [];
for (var i in allEntityNames) {
var entityName = allEntityNames[i];
var entity = database[entityName];
var entityKeys = Object.keys(entity);
console.log("began migrating "+ entityName);
for (var j in entityKeys) {
var entityKey = entityKeys[j];
var dict = entity[entityKey];
asyncTasks.push(function(callback){
db.collection(entityName).doc(entityKey).set(dict)
.then(function() {
callback();
})
.catch(function(error) {
console.log(error);
callback();
});
});
}
async.parallel(asyncTasks, function(){
console.log("Finished migrating "+ entityName);
});
}
Actually, I wrote a script in Node-Js that use batch in writing to Firestore (batch is super fast and suitable for write may items)
here is my code, just change files name to your's name and run node YOUR_FILE_NAME.js
const admin = require('firebase-admin');
var serviceAccount = require('./firestore-config.json');
var database = require('./database.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'YOUR_FILE_STORE_DB_URL',
});
var db = admin.firestore();
var allEntityNames = Object.keys(database);
var counter = 0;
var commitCounter = 0;
var batches = [];
batches[commitCounter] = db.batch();
var ref = db.collection('users');
allEntityNames.forEach(function(k, i) {
if (counter <= 498) {
var thisRef = ref.doc(k);
batches[commitCounter].set(thisRef, database[k]);
counter = counter + 1;
} else {
counter = 0;
commitCounter = commitCounter + 1;
batches[commitCounter] = db.batch();
}
});
for (var i = 0; i < batches.length; i++) {
batches[i].commit().then(function() {
console.count('wrote batch');
});
}
if you don't have Node-Js on your machine, google to install it. it is not so hard.
you can download firestore-config.json from your firebase console.
I just did this with a very basic node script and I hope it will serve as an example to the next one having this issue:
require('firebase/firestore')
const fs = require('fs')
const { initializeApp, firestore } = require('firebase/app')
const UID = 'asdfasdf' // UID of the user you are migrating
initializeApp({
apiKey: process.env.API_KEY,
projectId: process.env.PROJECT_ID
})
// db.json is the downloaded copy from my firebasedatabase
fs.readFile('db.json', (err, data) => {
if (err) throw err
const json = JSON.parse(data)
const readings = json.readings[UID]
const result = Object.values(readings)
result.forEach(({ book, chapter, date }) =>
// In my case the migration was easy, I just wanted to move user's readings to their own collection
firestore().collection(`users/${UID}/readings`)
.add({ date: firestore.Timestamp.fromDate(new Date(date)), chapter, book })
.catch(console.error)
)
console.log('SUCCESS!')
})
Of course you can also iterate twice to do it for every user but in my case it was not needed :)
There's a decent 3rd party npm package to help with importing, it boils down to basically one line:
await firestoreService.restore({ "my-table": myTable });
https://www.npmjs.com/package/firestore-export-import
Hi i have created a script for the same
import { AngularFirestore, AngularFirestoreCollection } from 'angularfire2/firestore';
import { AngularFireDatabase } from 'angularfire2/database';
constructor( private afs: AngularFirestore, private angularfire: AngularFireDatabase ) {}
convert() {
this.itemsCollection = this.afs.collection('requests');//ref()
this.angularfire.list('/requests/').auditTrail().subscribe((data: any) => {
_.each(data, element =>{
this.itemsCollection.doc(element.key).set(element.payload.val()) .then((result) => { }); }); });}

Resources