Dialogflow ES fulfillment did not reply - google-cloud-functions

[Update] with the entire code
// 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(functions.config().firebase);
const db = admin.firestore();
const settings = {/* your settings... */ timestampsInSnapshots: true};
db.settings(settings);
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));
function writeToDb (agent) {
const startDate = agent.parameters.startdate;
// Always assume user enter time in the past (for existing time)
console.log("start date:" + startDate);
if (Date.parse(startDate) > Date.now()) {
startDate.setFullYear(startDate.getFullYear() - 1);
// console.log("modify start date to: " + startDate);
}
const dfRef = db.collection('period').doc(request.body.originalDetectIntentRequest.payload.data.sender.id);
agent.add('Got it. That me write it done for ya');
dfRef.get().then(user => {
if(!user.exists) {
dfRef.create({dates: admin.firestore.FieldValue.arrayUnion(startDate)})
.then(() => {
}).catch(err => {
console.log('error create firestore date entry:' + `${err}`);
});
} else {
dfRef.update({dates: admin.firestore.FieldValue.arrayUnion(startDate)})
.then(() => {
}).catch(err => {
console.log('error update firestore date entry:' + `${err}`);
});
}
}).catch(err => {
console.log('error access firestore date:' + `${err}`);
});
}
function readFromDb (agent) {
// Get the database collection 'dialogflow' and document 'agent'
const startDate = agent.parameters.startdate;
const future = agent.parameters.future;
const dialogflowAgentDoc = db.collection('period').doc(request.body.originalDetectIntentRequest.payload.data.sender.id);
// Get the value of 'entry' in the document and send it to the user
return dialogflowAgentDoc.get()
.then(doc => {
if (doc.exists) {
var darray = doc.data().dates;
if (darray.length > 0) {
if (future) {
agent.add('let me calculate for you..');
var next = new Date(darray[darray.length-2]);
const dayDiff = calculateSchedule(darray);
next.setDate(next.getDate() + dayDiff * 1);
agent.add(next.toLocaleDateString('en-us', {month:"short", day: 'numeric', weekday: 'short'}));
} else {
agent.add('let me look up for you..');
agent.add(new Date(darray[darray.length-1]).toLocaleDateString('en-us', {month:"short", day: 'numeric', weekday: 'short'}));
}
} else {
agent.add('I cant find anything :( ');
}
} else {
agent.add('something was wrong, I cant find your record :/');
}
return Promise.resolve('complete!');
}).catch(err => {
agent.add(`Error reading entry from the Firestore database. ${err}`);
});
}
function calculateSchedule(arr) {
// emitted..
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('it start today', writeToDb);
intentMap.set('when did it start', readFromDb);
agent.handleRequest(intentMap);
});
[Original]
Hi my dialogflow ES is connected to fb messenger and the purpose is to reply a message after I recorded what the customer say into DB, I checked if the document exist: here is my code
function writeToDb (agent) {
const startDate = agent.parameters.startdate;
const dfRef = db.collection('start').doc('my_id');
dfRef.get().then(user => {
if(!user.exists) {
dfRef.create({dates: admin.firestore.FieldValue.arrayUnion(startDate)})
.then(() => {
agent.add('Got it. let me write it down for ya');
}).catch(err => {
console.log(`${err}`);
});
} else {
dfRef.update({dates: admin.firestore.FieldValue.arrayUnion(startDate)})
.then(() => {
agent.add('Got it. let me write it down for ya');
}).catch(err => {
console.log(`${err}`);
});
}
});
}
the startDate value is successfully store in the firestore. However, I never get the reply message, is there anything I did wrong? I felt it should be simple enough.
Thanks for your help.

You are missing to set up the IntentMap() see step 5 and 6.
In the intentMap, you will need to add a map from the Intent name to a function that will do the handling when that Intent triggers the webhook. You can have a different handler function for each Intent, use the same function for some, have those functions call other functions, whatever you need. Finally use agent.handleRequest(intentMap);.

Related

Webhook error on Dialog Flow ES when trying to communicate (save data) between Google Sheets and Dialog Flow

I have a problem with connecting dialog flow with any database. In this case, it is for Google sheets. I followed up this video for connecting dialog flow with google sheets:
"[https://www.youtube.com/watch?v=FVPRAnJ5jRk&t=785s]".
So, when I communicate with the bot, and when I finish filling in the information that I specified in the fulfillment, the Diagnostic Info displays an error:
"message": "Webhook call failed. Error: UNAVAILABLE, State: URL_UNREACHABLE, Reason: UNREACHABLE_5xx, HTTP status code: 500."
Please, can anyone help me with this. Really, I tried every database to integrate with Dialog Flow, but none worked. I don't know what is happening with Dialog Flow.
Bellow is my code that I have written in fulfillment at index.js:
`
'use strict';
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const axios = require('axios');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
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));
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function fallback(agent) {
return new Promise((resolve, reject) => {
const queryText = request.body.queryResult.queryText;
axios.post('https://sheetdb.io/api/v1/jv8fq8y7xucpf?sheet=failed', {
"data": {
"content": queryText
}
});
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
});
}
function appointment(agent) {
const doctor = agent.parameters.doctor.name;
const date = agent.parameters.date;
const name = agent.parameters.name;
const phone = agent.parameters.phone;
return new Promise((resolve, reject) => {
axios.get(`https://sheetdb.io/api/v1/jv8fq8y7xucpf/search?name=*${doctor}*`).then(function(response) {
let doctor = response.data[0];
if (doctor) {
axios.post('https://sheetdb.io/api/v1/jv8fq8y7xucpf?sheet=appointments', {
"data": {
"doctor": doctor.name,
"patient": name,
"patient_phone": phone,
"date": date,
"created": new Date()
}
});
agent.add("Ok your appointment is set up for you");
} else {
agent.add(`Unfortuneatly we did not find ${doctor} in our doctors`);
}
resolve();
});
});
}
// Run the proper function handler based on the matched Dialogflow intent name
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Default Fallback Intent', fallback);
intentMap.set('AppointmentIntent', appointment);
agent.handleRequest(intentMap);
});
`

How do I return an asynchronous DB query result from one module to another using Node.js?

I'm new to Node, and I'm trying to follow a pattern from a Udemy API course. The API is structured to utilize route, controller and service modules for flow. Database queries are to be run as services and they are supposed to be called from controllers.
I need to run a series of DB queries to generate a list (I'm showing only 2 of 6 queries in this example). I am running these using async/await in my function. The queries are working fine. My problem occurs when I try to return the 'batch result' (the result of all the queries) to the controller at the end of the process. I get Promise { <pending> }. I have tried many things, but I cannot end the promise to access the final result from my controller module--I can only access it from my service module.
Here is my code from my controller module (groups.controller.js) where I call my function:
const groupsService = require('../services/groups.service');
exports.propertyList = (req, res, next) => {
const uid = req.body.uid;
const batchResponse = groupsService.batchQuery(uid, res);
console.log(batchResponse);
}
And here is my code from my service module (groups.services.js) where I run the queries:
const mysql = require('mysql2');
const dbAsync = require("../config/db.config");
async function batchQuery(uid, res) {
var Q1;
var Q2;
var uid = uid * -1;
const pool = mysql.createPool(dbAsync.dbAsync);
const promisePool = pool.promise();
try {
Q1 = await promisePool.query('SELECT PropertyID FROM GroupMembership WHERE GroupID = ?', [uid]);
Q2 = await promisePool.query('SELECT SubGroupID FROM GroupMembership WHERE GroupID = ? AND PropertyID = ?', [uid, 0]);
}
catch(error) {
console.log(error);
res.status(401).send('Server error');
return error;
}
finally {
const batchResponse = {
Q1: Q1[0],
Q2: Q2[0]
}
console.log('Q1: '+ Q1[0][0].PropertyID + ', Q2: ' + Q2[0][0].SubGroupID);
res.status(200).send(batchResponse);
return batchResponse;
}
}
module.exports = {batchQuery};
When I send a post via postman, I get the expected query result (below). However, I can only get this to work if I put my res in my service module.
{
"Q1": [
{
"PropertyID": 0
}
],
"Q2": [
{
"SubGroupID": 397
}
]
}
Is there a way to end the promise in this pattern and return the desired batch response? Thank you.
EDIT: Adding the code updates provided by #traynor.
New controller:
const groupsService = require('../services/groups.service');
exports.propertyList = async (req, res, next) => {
const uid = req.body.uid;
let batchResponse;
try {
batchResponse = await groupsService.batchQuery(uid);
console.log(batchResponse);
return res.status(200).send(batchResponse);
} catch(error) {
console.log('Error: ' + error);
return res.status(401).send('Server error');
}
}
New service:
const mysql = require('mysql2');
const dbAsync = require("../config/db.config");
function batchQuery(uid) {
return new Promise((resolve, reject) => {
var Q1;
var Q2;
var uid = uid * -1;
const pool = mysql.createPool(dbAsync.dbAsync);
const promisePool = pool.promise();
try {
Q1 = await promisePool.query('SELECT PropertyID FROM GroupMembership WHERE GroupID = ?', [uid]);
Q2 = await promisePool.query('SELECT SubGroupID FROM GroupMembership WHERE GroupID = ? AND PropertyID = ?', [uid, 0]);
} catch(error) {
console.log(error);
reject(error);
} finally {
const batchResponse = {
Q1: Q1[0],
Q2: Q2[0]
}
console.log('Q1: '+ Q1[0][0].PropertyID + ', Q2: ' + Q2[0][0].SubGroupID);
resolve(batchResponse);
}
})
}
module.exports = {batchQuery};
the service is now returning a promise, and it's also handling response instead of controller.
to return from service, you need to promisify service: return a promise which resolves when you get db data, or on error, and then you also need to await the service, which it's wrapped in try/catch for error handling.
once it's all done, handle response from the controller:
service:
function batchQuery(uid) {
return new Promise(async (resolve, reject) => {
var Q1;
var Q2;
//...
try {
//...
} catch (error) {
console.log(error);
reject(error);
} finally {
const batchResponse = {
Q1: Q1[0],
Q2: Q2[0]
}
console.log('Q1: ' + Q1[0][0].PropertyID + ', Q2: ' + Q2[0][0].SubGroupID);
resolve(batchResponse);
}
});
controller:
exports.propertyList = async (req, res, next) => {
const uid = req.body.uid;
let batchResponse;
try {
batchResponse = await groupsService.batchQuery(uid);
console.log(batchResponse);
res.status(200).send(batchResponse);
} catch(error) {
return res.status(401).send('Server error');
}
}

cannot connect client app with web3js to metamask

I am a dapp beginner. This is a demo app for a todolist
I am unable to get a connection to the blockchain in web3js. Websocket connection error
localhost is localhost:8545
using web3js CDN : https://cdn.jsdelivr.net/npm/web3#latest/dist/web3.min.js
this is my app.js
App = {
loading: false,
contracts: {},
load: async () => {
console.log('app loading ...')
console.log(web3);
await App.loadWeb3()
await App.loadAccount()
// await App.loadContract()
// await App.render()
},
// https://medium.com/metamask/https-medium-com-metamask-breaking-change-injecting-web3-7722797916a8
loadWeb3: async () => {
let web3 = new Web3('ws://localhost:8545');
if (typeof web3 !== 'undefined') {
App.web3Provider = web3.currentProvider
web3.setProvider('ws://localhost:8546');
web3.eth.getAccounts().then(console.log);
} else {
window.alert("Please connect to Metamask.")
}
// Modern dapp browsers...
if (window.ethereum) {
window.web3 = new Web3(ethereum)
try {
// Request account access if needed
await ethereum.enable()
// Acccounts now exposed
web3.eth.sendTransaction({/* ... */})
console.log('MetaMask is installed!');
} catch (error) {
// User denied account access...
}
}
// Legacy dapp browsers...
else if (window.web3) {
App.web3Provider = web3.currentProvider
window.web3 = new Web3(web3.currentProvider)
// Acccounts always exposed
web3.eth.sendTransaction({/* ... */})
}
// Non-dapp browsers...
else {
console.log('Non-Ethereum browser detected. You should consider trying MetaMask!')
}
},
loadAccount: async () => {
// Set the current blockchain account
App.account = web3.eth.accounts[0]
console.log(App.account)
// web3 set up by loadWeb3, includes all accounts, loading first one via MetaMask
},
loadContract: async () => {
// Create a JavaScript version of the smart contract
const todoList = await $.getJSON('TodoList.json')
App.contracts.TodoList = TruffleContract(todoList)
App.contracts.TodoList.setProvider(App.web3Provider)
// Hydrate the smart contract with values from the blockchain
App.todoList = await App.contracts.TodoList.deployed()
},
render: async () => {
// Prevent double render
if (App.loading) {
return
}
// Update app loading state
App.setLoading(true)
// Render Account
$('#account').html(App.account)
// Render Tasks
await App.renderTasks()
// Update loading state
App.setLoading(false)
},
renderTasks: async () => {
// Load the total task count from the blockchain
const taskCount = await App.todoList.taskCount()
const $taskTemplate = $('.taskTemplate')
// Render out each task with a new task template
for (var i = 1; i <= taskCount; i++) {
// Fetch the task data from the blockchain
const task = await App.todoList.tasks(i)
const taskId = task[0].toNumber()
const taskContent = task[1]
const taskCompleted = task[2]
// Create the html for the task
const $newTaskTemplate = $taskTemplate.clone()
$newTaskTemplate.find('.content').html(taskContent)
$newTaskTemplate.find('input')
.prop('name', taskId)
.prop('checked', taskCompleted)
.on('click', App.toggleCompleted)
// Put the task in the correct list
if (taskCompleted) {
$('#completedTaskList').append($newTaskTemplate)
} else {
$('#taskList').append($newTaskTemplate)
}
// Show the task
$newTaskTemplate.show()
}
},
createTask: async () => {
App.setLoading(true)
const content = $('#newTask').val()
await App.todoList.createTask(content)
window.location.reload()
},
toggleCompleted: async (e) => {
App.setLoading(true)
const taskId = e.target.name
await App.todoList.toggleCompleted(taskId)
window.location.reload()
},
setLoading: (boolean) => {
App.loading = boolean
const loader = $('#loader')
const content = $('#content')
if (boolean) {
loader.show()
content.hide()
} else {
loader.hide()
content.show()
}
}
}
$(() => {
$(window).load(() => {
App.load()
})
})
I get this error in the console :
Again I am a total newbie, any help is appreciated.
Any source of info did not help
Hey after metamask update , it no longer injects web3 .
You can check the blog below , It has shown how to connect metamask with our project .
https://dapp-world.com/blogs/01/how-to-connect-metamask-with-dapp--1616927367052
Only thing is its web application , you can relate it with your project .
Hope it works !

cloud function for sending fcm notifications to a collection of tokens

I am trying to send a notification whenever a new update to my database takes place. I have the onUpdate side working but I am new to FCM and I am stuck at the sending the notification.
The structure of the devices collection is:
+devices/
+tokenId/
-tokenId:njurhvnlkdnvlksnvlñaksnvlkak
-userId:nkjnjfnwjfnwlknlkdwqkwdkqwdd
The function that I have right now that gets stuck with an empty value of token is:
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp();
const db = admin.firestore();
const settings = { timestampsInSnapshots: true };
db.settings(settings);
.....
exports.fcmSend = functions.firestore
.document(`chats/{chatId}`).onUpdate((change, context) => {
const messageArray = change.after.data().messages;
const message = messageArray[(messageArray.length-1)].content
if (!change.after.data()) {
return console.log('nothing new');
}
const payload = {
notification: {
title: "nuevo co-lab",
body: message,
}
};
return admin.database().ref(`/devices`)
.once('value')
.then(token => token.val())
.then(userFcmToken => {
console.log("Sending...", userFcmToken);
return admin.messaging().sendToDevice(userFcmToken, payload)
})
.then(res => {
console.log("Sent Successfully", res);
})
.catch(err => {
console.log("Error: ", err);
});
});
I am not able to get the token from the database. It is null or undefined. Can anyone help me with this second part of the function?
Thanks a lot in advance!
Thanks Frank for the tip!
I managed to solve the problem with this code in case anybody needs it:
const payload = {
notification: {
title: "nuevo mensaje de co-lab",
body: message,
}
};
// Get the list of device tokens.
const allTokens = await admin.firestore().collection('devices').get();
const tokens = [];
allTokens.forEach((tokenDoc) => {
tokens.push(tokenDoc.id);
});
if (tokens.length > 0) {
// Send notifications to all tokens.
return await admin.messaging().sendToDevice(tokens, payload);
}else {
return null;
}

Tracking DB querying time - Bookshelf/knex

I would like to monitor the time taken by a query on my API's db. I so created the following function, using bookshelf-signals, a Bookshelf plugin. :
bookshelf.on('fetching', () => {
server.app.fetching = new Date().valueOf();
});
bookshelf.on('counting', () => {
server.app.fetching = new Date().valueOf();
});
bookshelf.on('fetched', () => {
server.statsd.gauge('db_query', new Date().valueOf() - server.app.fetching);
});
... so that I can retrieve the time just before and just after a fetch/count; I did the same with deleting-deleted and saving-saved.
What I think I fail to understand is when fetching and fetched are supposed to be triggered... When I tried to to see when fetching and fetched were triggered, basically it ended up with this :
'fetching event A'
'fetching event B'
'fetching event C'
'fetched event C'
'fetched event B'
'fetched event A'
Resulting in the timers returning wrong values obliviously, do you have any lead/clue ?
I also saw that one could trigger 'query' events on Knex, and thought of using this as an alternative solution. However, it seems that it only works if I specify the table where I query, ie :
knex('whatever_table').on('query', () => {///});
Making it impracticable in the case where I want to apply an event handler on every model...
I think I should stick with Bookshelf, but how can I do with the way the events are handled?
Thank you in advance!
I just wrote some small test code how to trace transaction duration with knex.
https://runkit.com/embed/679qu91ylu4w
/**
* Calculate transaction durations in knex
*
*/
require('sqlite3');
var knex = require("knex")({
client: 'sqlite',
connection: ':memory:',
pool: { min: 1, max: 10 }
});
function isTransactionStart(querySpec) {
return querySpec.sql === 'BEGIN;';
}
function isTransactionEnd(querySpec) {
return querySpec.sql === 'COMMIT;' || querySpec.sql === 'ROLLBACK;';
}
const transactionDurations = {};
knex.on('query', querySpec => {
console.log('On query', querySpec);
if (isTransactionStart(querySpec)) {
if (transactionDurations[querySpec.__knexUid]) {
console.error('New transaction started, before earlier was ended');
return;
}
transactionDurations[querySpec.__knexUid] = new Date().getTime();
}
if (isTransactionEnd(querySpec)) {
const startTime = transactionDurations[querySpec.__knexUid];
if (!startTime) {
console.error('Transaction end detected, but start time not found');
}
const endTime = new Date().getTime();
transactionDurations[querySpec.__knexUid] = null;
console.log('TRANSACTION DURATION', endTime - startTime);
}
});
// just as an example of other available events to show when they are called
knex.on('query-response', (res, querySpec) => {
// console.log('On query response', res, querySpec);
});
knex.on('query-error', (err, querySpec) => {
// console.log('On query error', err, querySpec);
});
try {
a = await Promise.all([
knex.transaction(trx => {
return trx.raw('select 1');
}),
knex.transaction(trx => {
return trx.raw('select 2');
}),
knex.transaction(trx => {
return trx.raw('error me');
})
]);
} catch (e) {
console.log('Got ERROR:', e);
}
The same king of approach should work also for query timing. To prevent timer bookkeeping from leaking memory you should add some cleanup code though.
Query duration timer should be started in query event and stopped in query-response or query-error depending which one triggers first.
To be able to match query - query-response pair querySpec.__knexQueryUid attribute can be used.
Based on Mikael Lepistö snippet I came up with this :
const dbEvents = (server, sdc) => {
knex.on('query', data => {
server.app[data.__knexQueryUid + ''] = new Date().valueOf();
});
knex.on('query-response', (data, obj, builder) => {
sdc.counter('db_queries_time', new Date().valueOf() - server.app[obj.__knexQueryUid + '']);
sdc.increment('nr_db_queries');
});
};
And I then call the function when I start the server - I am working with Hapijs.
EDIT: sdc is a statsd client, I use it to send the DB time :)