Error: No handler for requested intent at WebhookClient.handleRequest - google-cloud-functions

Default intent calling a cloud function gives error
Error: No handler for requested intent
at WebhookClient.handleRequest (/user_code/node_modules/dialogflow-fulfillment/src/dialogflow-fulfillment.js:287:29)
at exports.dialogflowFirebaseFulfillment.functions.https.onRequest (/user_code/index.js:73:11)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:57:9)
at /var/tmp/worker/worker.js:783:7
at /var/tmp/worker/worker.js:766:11
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
as my webresponse in diagnostic info log shows this.
{
"responseId": "86043a10-8bc2-4ee7-8e8b-1e997289ad7c",
"queryResult": {
"queryText": "hi",
"action": "input.welcome",
"parameters": {},
"allRequiredParamsPresent": true,
"fulfillmentText": "Hi. Am Uma. Kindly let me know your experience facing an issue.",
"fulfillmentMessages": [
{
"text": {
"text": [
"Hi. Am Uma and welcome to support. Kindly let me know your experience facing an issue."
]
}
}
],
"outputContexts": [
{
"name": "projects/handymanticketagent/agent/sessions/e416a522-da87-ebd1-348e-9fdea1efbf65/contexts/defaultwelcomeintent-followup",
"lifespanCount": 2
}
],
"intent": {
"name": "projects/handymanticketagent/agent/intents/c58f706f-6cb6-499d-9ce2-459e8054ddc1",
"displayName": "Default Welcome Intent"
},
"intentDetectionConfidence": 1,
"diagnosticInfo": {
"webhook_latency_ms": 10001
},
"languageCode": "en"
},
"webhookStatus": {
"code": 4,
"message": "Webhook call failed. Error: Request timeout."
}
}
Based on the stack overflow answers here, Have added an intent mapped to function but am still getting error and could progress further. Where and how the cloud function console says am missing a handler for my request?
Update : As #prisoner said, including my cloud function code.
'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.firestore();
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
console.log(request.body.queryResult.fulfillmentText);
console.log(request);
console.log(response);
const agent = new WebhookClient({ request, response });
console.log(agent);
function writeToDb(agent) {
// Get parameter from Dialogflow with the string to add to the database
const databaseEntry = agent.parameters.databaseEntry;
console.log(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');
console.log(dialogflowAgentRef);
return db.runTransaction(t => {
t.set(dialogflowAgentRef, { entry: databaseEntry });
console.log(Promise.resolve('Write complete'));
return Promise.resolve('Write complete');
}).then(doc => {
agent.add('Wrote "${databaseEntry}" to the Firestore database.');
return null;
}).catch(err => {
if (err) {
console.log(err.stack);
}
console.log('Error writing to Firestore: ${err}');
agent.add('Failed to write "${databaseEntry}" to the Firestore database.');
});
}
function readFromDb(agent) {
console.log(agent);
// Get the database collection 'dialogflow' and document 'agent'
const dialogflowAgentDoc = db.collection('dialogflow').doc('agent');
console.log(dialogflowAgentDoc);
// 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"');
});
}
function defaultwelcomeintent_function(agent) {
console.log(agent);
}
// Map from Dialogflow intent names to functions to be run when the intent is matched
let intentMap = new Map();
intentMap.set('defaultwelcomeintent-followup', defaultwelcomeintent_function);
intentMap.set('ReadFromFirestore', readFromDb);
intentMap.set('WriteToFirestore', writeToDb);
console.log(intentMap);
agent.handleRequest(intentMap);
});

The diagnostic info says that the intent's display name for that fulfillment is "Default Welcome Intent":
"intent": {
"name": "projects/handymanticketagent/agent/intents/c58f706f-6cb6-499d-9ce2-459e8054ddc1",
"displayName": "Default Welcome Intent"
},
So you'd need to create a mapping for it like this:
intentMap.set('Default Welcome Intent', defaultwelcomeintent_function);
Where defaultwelcomeintent_function is the handler you have defined within your cloud function.

I had the same issue with the exact error from Dialogflow : Error: No handler for requested intent, in my case I'm using async/await in order to make synchronous calls through a cloud function in dialogflow fulfillement.
I noticed that in one of my main function mapped to an agent, I wasn't returning anything. Since the function was using async I added a return statment with the promise that I was waiting for at the beggining.
async function getInfo(agent) {
var hh = await getUserInfos(request.body.originalDetectIntentRequest.payload.uuid);
// Do what you want here
var yy = hh.aa[0].zz.yy;
agent.setFollowupEvent({ "name": "xxx", "parameters": { "xxx": yy } });
// Return your promise
return hh;
}

Related

I wanted to get users from an array of names but it throws an error

Create an async function getUsers(names), that gets an array of GitHub logins, fetches the users from GitHub and returns an array of GitHub users.
The GitHub url with user information for the given USERNAME is: https://api.github.com/users/USERNAME.
There’s a test example in the sandbox.
Important details:
1.There should be one fetch request per user.
2.Requests shouldn’t wait for each other. So that the data arrives as soon as possible.
3.If any request fails, or if there’s no such user, the function should return null in the resulting array.
Input:array;
output:array;
TypeError: r.json is not a function
async function getUsers(names) {
let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));//gets users
let users = [];//Final answer
await Promise.allSettled(requests)
.then(responses => new Promise(function(resolve) {// returrn correct users promise
let corrects = [];
responses.forEach((result) => {
if (result.value.ok) { //check statuse 200-299
corrects.push(result);
} else {
users.push(result); // else add to Finell answer null
}
})
resolve(corrects); //return users with 200-299 statuse
}))
.then(corrects => Promise.all(corrects.map(r => r.json()))) //processing
.then(results => results.forEach(result => users.push(result))); //add to finel answer correct requests
return users;
}
//Input:array;
//output:array;
//TypeError: r.json is not a function
There's a number of things slightly wrong with your code, but I think the main issue is that you're pushing the results of allSettled into 'corrects' but, you want to push the .value instead.
You also don't actually do anything with corrects and only return failed requests.
But here's a version that cleans it all up. I'm assuming you want to ignore failed requests, but not sure, because it's hard to tell from your code:
async function getUsers(names) {
const requests = names.map(name => fetch(`https://api.github.com/users/${name}`));//gets users
const results = await Promise.allSettled(requests);
const successResponses = results
.filter(result => {
// Filter out rejected promises and error responses.
// I think this is what you want but not sure?
if (result.status!=='fulfilled' || !result.value.ok) return false;
});
return Promise.all(successResponses.map(response => response.json()));
}
Promise.allSettled is a very special-purpose function and you will not need it in most cases. There are other pain points like the explicit promise constructor anti-pattern. Instead decompose the problem into smaller, simple parts -
getUser(name) takes a single name and returns a user object or null
getUsers(names) takes a list of names and maps getUser over each
async function getUser(name) {
try {
const res = await fetch(`https://api.github.com/users/${name}`)
return res.ok ? res.json() : null
}
catch (err) {
return null
}
}
function getUsers(names) {
return Promise.all(names.map(getUser))
}
getUsers(["ivg", "glennsl", "jeffsco", "nosuchuser111"]).then(console.log, console.error)
.as-console-wrapper { min-height: 100%; top: 0; }
[
{
"login": "ivg",
"id": 2336698,
"node_id": "MDQ6VXNlcjIzMzY2OTg=",
...
},
{
"login": "glennsl",
"id": 5207036,
"node_id": "MDQ6VXNlcjUyMDcwMzY=",
...
},
{
"login": "jeffsco",
"id": 4043178,
"node_id": "MDQ6VXNlcjQwNDMxNzg=",
...
},
null // user not found
]

How to Publish a pub/sub message from a Cloud Function triggered by Cloud Pub/Sub

hope you're all safe! I need some help with my cloud function triggered by Cloud Pub/Sub...
I have a cloud function that is triggered by one of my topics. From this message I want to send a new message to another topic, but I'm getting some errors.
My Function:
const { PubSub } = require("#google-cloud/pubsub");
exports.helloPubSub = async (message, context) => {
try {
let messageData = Buffer.from(message.data, "base64").toString();
let messageObjectData = JSON.parse(messageData);
//send pub/sub message to another topic
sendPubSubMessage(messageObjectData);
} catch (error) {
console.log(error.message);
}
};
function sendPubSubMessage(
messageObjectData,
topicName = "projects/myproject/topics/mytopic",
data = JSON.stringify(messageObjectData)
) {
const pubSubClient = new PubSub();
async function publishMessage() {
const dataBuffer = Buffer.from(data);
try {
const messageId = await pubSubClient.topic(topicName).publish(dataBuffer);
console.log(`Message ${messageId} published.`);
} catch (error) {
console.error(`Received error while publishing: ${error.message}`);
process.exitCode = 1;
}
}
publishMessage();
}
Package.json:
{
"name": "sample-pubsub",
"version": "0.0.1",
"dependencies": {
"#google-cloud/pubsub": "^0.18.0"
}
}
When this function runs I get this error:
ERROR: PubSub is not a constructor
I tried to change the dependency version to the last 2.18.3, but then it does not deploy...
I also tried to load both versions:
{
"name": "sample-pubsub",
"version": "0.0.1",
"dependencies": {
"#google-cloud/pubsub": "^0.18.0",
"lastPubSub" : "npm:google-cloud/pubsub#^2.18.3",
}
}
and got the same error...
Does anyone have any clue on how to fix it?
Thanks in advance!
Thank you all for the comments, finally I got it...
Solution:
package.json:
{
"name": "sample-pubsub",
"version": "0.0.1",
"dependencies": {
"#google-cloud/pubsub": "^0.18.0",
"googlePub": "npm:#google-cloud/pubsub#^2.18.3"
}
}

Attempting to load list of values in column for Angular Table

I'm struggling with trying to figure out what I'm doing wrong, mostly down to not having a good understanding of AngularJS due to being new. The main goal is that I'm trying to list out all the values in the additionalText list out on the front-end, but it seems to be causing issue with this error:
Error: [$http:badreq] Http request configuration url must be a string or a $sce trusted object. Received: []
Context:
I have table in my application that relies on the API, this variable contains a list and outputs the following:
{
"name": "TEST",
"description": "TEST",
"additionalText": [
{
"name": "TEST",
"description": "TEST",
"lockId": 0
}
{
"name": "TEST",
"description": "TEST",
"lockId": 0
}
],
"lockId": 0
}
The API is working as expected, I can carry out all the necessary REST calls successfully. So I'm not struggling with that, the front-end is where I am having some difficulty.
HTML:
<td data-title="'additionalTexts'" sortable="'additionalTexts'">
<span ng-repeat="additionalText in additionalTextList[entity.name]">
<i>{{additionalText.name}}</i><br>
</span>
</td>
AngularJS:
$scope.refreshTextTable= function() {
SpringDataRestService.query(
{
collection: "APIURL"
},
function (response) {
var additionalTextRoles = response;
$scope.textTableOptions = new NgTableParams({}, {
dataset: additionalTextRoles,
counts: [],
});
// Also populate a list of all linked roles
for (var i = 0; i < additionalTextRoles.length; i++) {
var additionalTextRole = additionalTextRoles[i];
// This approach allows you to inject data into the callback
$http.get(additionalTextRole.additionalText).then((function (additionalTextRole) {
return function(response) {
$scope.additionalTextList[additionalTextRole.name] = response.additionalText;
};
})(additionalTextRole));
}
},
function (response) {
// TODO: Error Handling
}
);
};
Any help would be greatly appreciated, I'm really struggling with this one.
Can you try this below code:
$scope.refreshTextTable = function() {
SpringDataRestService.query({
collection: "APIURL"
},
function(response) {
var additionalTextRoles = response;
$scope.textTableOptions = new NgTableParams({}, {
dataset: additionalTextRoles,
counts: [],
});
// Also populate a list of all linked roles
for (var i = 0; i < additionalTextRoles.length; i++) {
var additionalTextRole = additionalTextRoles[i];
// This approach allows you to inject data into the callback
$http.get(additionalTextRole.additionalText).then((function(additionalTextRole) {
return function(response) {
$scope.additionalTextList = response.additionalText;
};
})(additionalTextRole));
}
},
function(response) {
// TODO: Error Handling
}
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min.js"></script>
<td data-title="'additionalTexts'" sortable="'additionalTexts'">
<span ng-repeat="additionalText in additionalTextList">
<i>{{additionalText.name}}</i><br>
</span>
</td>
The error message says the url must be a string.
For debugging purposes, console.log the URL:
for (var i = 0; i < additionalTextRoles.length; i++) {
var additionalTextRole = additionalTextRoles[i];
// This approach allows you to inject data into the callback
var url = additionalTextRole.additionalText;
console.log(i, url);
$http.get(url).then((function (additionalTextRole) {
return function(response) {
$scope.additionalTextList[additionalTextRole.name] = response.additionalText;
};
})(additionalTextRole));
}
Also note that the response object returned by the $http service does not have a property named additionalText. So it is likely that the intention is response.data.additionalText. To avoid the IIFE, use the forEach method:
additionalTextRoles.forEach( role => {
var url = role.additionalText;
console.log(url);
$http.get(url).then((function(response) {
$scope.additionalTextList[role.name] = response.data.additionalText;
});
});

object keys are undefined in if conditional, but inside the if statement I can access it

As the title states, I have a variable which is a javascript object, i'm comparing it with another js object by stringifying them. The problem is that the variable is completely accessible without calling the keys, so these
if(JSON.stringify(response) == JSON.stringify(lastcmd))
if(JSON.stringify(response.id) == JSON.stringify(lastcmd))
work perfectly fine, but accessing lastcmd's id key will cause it to throw undefined.
if(JSON.stringify(response) == JSON.stringify(lastcmd.id))
full code link here
Edit: Here's the JSON
{ "id" : "001", "app": "msgbox", "contents": { "title": "Newpaste", "message": "I'm a edited paste!" } }
Edit2: Here's the code on the post
const { BrowserWindow, app, dialog, ClientRequest } = require("electron");
const axios = require("axios");
const url = require("url");
let win = null;
let lastcmd;
function grabCurrentInstructions(fetchurl) {
return axios
.get(fetchurl)
.then(response => {
// handle success
//console.log(response.data);
return response.data;
})
.catch(function(error) {
// handle error
console.log(error);
});
}
function boot() {
//console.log(process.type);
win = new BrowserWindow({
resizable: true,
show: false,
frame: false
});
win.loadURL(`file://${__dirname}/index.html`);
//Loop everything in here every 10 seconds
var requestLoop = setInterval(getLoop, 4000);
function getLoop() {
grabCurrentInstructions("https://pastebin.com/raw/i9cYsAt1").then(
response => {
//console.log(typeof lastcmd);
//console.log(typeof response);
if (JSON.stringify(response.app) == JSON.stringify(lastcmd.app)) {
console.log(lastcmd.app);
clearInterval(requestLoop);
requestLoop = setInterval(getLoop, 4000);
} else {
lastcmd = response;
switch (response.app) {
case "msgbox":
dialog.showMessageBox(response.contents);
//console.log(lastcmd);
clearInterval(requestLoop);
requestLoop = setInterval(getLoop, 1000);
}
}
}
);
}
}
app.on("ready", boot);
And here's the error:
(node:7036) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'id' of undefined
at grabCurrentInstructions.then.response (C:\Users\The Meme Machine\Desktop\nodejsprojects\electronrat\index.js:42:64)
at process._tickCallback (internal/process/next_tick.js:68:7)
Thanks to user str I saw that my lastcmd was undefined when I ran the comparison the first time, this would break it and thereby loop the same error over and over, by addding
grabCurrentInstructions("https://pastebin.com/raw/i9cYsAt1").then(
response => {
lastcmd = response;
}
);
below this line
win.loadURL(`file://${__dirname}/index.html`);
I made sure that the last command sent while the app was offline wouldn't be executed on launch and fixing my problem at the same time!

Exception using a naming convention w/ Breeze Angular mySql Node Express stack

I'm able to successfully connect and query data from a mySql db via a Breeze/Angular client, following the todo-angular example. I switched out the db table and the GUI and was still ok. The problem starts when I try to use a naming convention. (I don't have control over the db that I have to connect to & I really don't want to use Uppercase_Underscored_Words in my client!)
I'm getting the following exception:
/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/node_modules/breeze-client/breeze.debug.js:1852
throw new Error("Unable to locate a registered object by the name: " + k
^
Error: Unable to locate a registered object by the name: NamingConvention.underscoreCamelCase
at Object.__config._fetchObject (/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/node_modules/breeze-client/breeze.debug.js:1852:13)
at MetadataStore.proto.importMetadata (/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/node_modules/breeze-client/breeze.debug.js:6517:40)
at new module.exports.MetadataMapper (/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/MetadataMapper.js:19:8)
at SequelizeManager.importMetadata (/Users/Sherri/Sites/awdb-web/node_modules/breeze-sequelize/SequelizeManager.js:46:24)
at createSequelizeManager (/Users/Sherri/Sites/awdb-web/server/routes.js:114:8)
at /Users/Sherri/Sites/awdb-web/server/routes.js:23:27
When I take the "namingConvention": "camelCase" line out of the metadata.json file, the error goes away, but of course, the database property is not able to be correctly converted.
Here is the relevant code I use to set up the Entity Manager: (EDIT: I'm pretty sure my problem is server side and has nothing to do with this code, though)
var namingConvention = new UnderscoreCamelCaseConvention();
namingConvention.setAsDefault();
breeze.core.config.initializeAdapterInstance("uriBuilder", "json");
var serviceName = 'breeze/awdb';
var manager = new breeze.EntityManager(serviceName);
// Take any server property name and make it camelCase for the client to use.
// also, save it so that we can convert from the client back to the server's name
function UnderscoreCamelCaseConvention() {
var serverNames = {
netPoints: 'netPoints',
netPointsSpent: 'netPointsSpent'
}; // every translated server name
return new breeze.NamingConvention({
name: 'underscoreCamelCase',
clientPropertyNameToServer: clientPropertyNameToServer,
serverPropertyNameToClient: serverPropertyNameToClient
});
function clientPropertyNameToServer(clientPropertyName) {
return serverNames[clientPropertyName];
}
function serverPropertyNameToClient(serverPropertyName) {
var clientName = _.camelCase(serverPropertyName);
serverNames[clientName] = serverPropertyName;
return clientName;
}
}
And here is a snippet of my metadata.json file:
{
"metadataVersion": "1.0.5",
"namingConvention": "underscoreCamelCase",
"localQueryComparisonOptions": "caseInsensitiveSQL",
"dataServices": [
{
"serviceName": "breeze/awdb/",
"hasServerMetadata": true,
"jsonResultsAdapter": "webApi_default",
"useJsonp": false
}
],
"structuralTypes": [
{
"shortName": "person",
"namespace": "AWdb.Models",
"autoGeneratedKeyType": "Identity",
"defaultResourceName": "people",
"dataProperties": [
{
"name": "Person_ID",
"dataType": "Int32",
"isNullable": false,
"defaultValue": 0,
"isPartOfKey": true,
"validators": [
{
"name": "required"
},
{
"min": -2147483648,
"max": 2147483647,
"name": "int32"
}
]
},
{
"name": "Household_ID",
"dataType": "Int32",
"validators": [
{
"min": -2147483648,
"max": 2147483647,
"name": "int32"
}
]
},
....
]
}
],
"resourceEntityTypeMap": {"people": "person:#AWdb.Models"}
}
EDIT:
Here is code from my routes.js file that gets the metadata.
var fs = require('fs');
var breezeSequelize = require('breeze-sequelize');
var SequelizeManager = breezeSequelize.SequelizeManager;
var SequelizeQuery = breezeSequelize.SequelizeQuery;
var SequelizeSaveHandler = breezeSequelize.SequelizeSaveHandler;
var breeze = breezeSequelize.breeze;
var EntityQuery = breeze.EntityQuery;
var dbConfig = {
host: 'localhost',
user: 'xx',
password: 'xx',
dbName: 'xx'
};
var _sequelizeManager = createSequelizeManager();
// _sequelizeManager.sync(true).then(seed).then(function(){
// console.log('db init successful');
// });
exports.init = init;
function init(app) {
app.get('/breeze/awdb/Metadata', function (req, res, next) {
try {
var metadata = readMetadata();
res.send(metadata);
} catch(e){
next(e);
}
});
function createSequelizeManager() {
var metadata = readMetadata();
var sm = new SequelizeManager(dbConfig);
sm.importMetadata(metadata);
return sm;
}
function readMetadata() {
var filename = "server/AWdbMetadata.json";
if (!fs.existsSync(filename)) {
filename = "AWdbMetadata.json";
if (!fs.existsSync(filename)) {
throw new Error("Unable to locate file: " + filename);
}
}
var metadata = fs.readFileSync(filename, 'utf8');
return JSON.parse(metadata);
}
Any ideas? Should I be able to use a custom naming convention when I'm on a node.js server, using a metadata.json file instead of a .net entity framework?
If I'm looking at this correctly, then I think your issue is the metadata on the server. If I understand correctly, your table and column names follow the Uppercase_Underscored_Word pattern. The Breeze/Sequelize stack on the server currently doesn't have the ability to convert names, so you must use the names of entities and properties exactly as they are in the DB schema. Otherwise, the Breeze to Sequelize translation will fail. You can still use a naming convention on the client to turn the underscored server names into whatever you want them to be on the client.
So, you need two metadata files. One for the server that is used by the Breeze/Sequelize stack and that uses names exactly as they are in the DB and then a separate metadata file for the client, where you can do the translation.