How to query firestore with the Dialogflow inline editor to get information - google-cloud-firestore

I am using the inline editor within Dialogflow with the aim of making queries to the database I have created within Firestore.
In short, the user requests a list of courses, I'd like the chatbot to then grab that information form the db and display that back to the user.
Below I have tried to create a function that will do this, I want to take the user input, say "Art Courses" and have my db return those results.
So far, I have created a function that is triggered when the intent is matched, like so;
function getCourses(agent){
let courseRequest = agent.parameters.courseRequest;
if (getCourses){
console.log('Here is the list you requested for ${getCourses}' + parameters.courseRequest);
return admin.firestore().collection('Course_Information').doc.where('CoureTypes').get();
}
}
Are there any notable things I need to add to my function to perform what I wish to achieve?
Thank you.
UPDATE
This code deploys fine, but when I communicate with my bot and trigger the CourseEnquiry intent, cloud Functions shows this error:
admin.collection is not a function
Whilst this seems self explanatory I can't make sure of what it means, I thought declaring const admin = require('firebase-admin');enables me to use admin.collection
// 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
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 getDate(agent){
var today = new Date();
}
function welcome(agent) {
agent.add(`Welcome to my agent!`);
}
function test(agent){
agent.add("The test is successful");
}
function getCourses(agent){
// Get the database collection and document
const getCourseDoc = admin.collection('Course_Information').doc('Course_Types');
return getCourseDoc.get()
.then(doc => {
if (!doc.exists) {
agent.add('No data found in the database!');
} else {
agent.add(doc.data().entry);
}
return Promise.resolve('Here is the information you wanted');
}).catch(() => {
agent.add('Error reading entry from the Firestore database.');
});
}
function getSubmissionDateSep(agent){
agent.add('Your next submission date is for coursework 1 is');
}
let intentMap = new Map();
intentMap.set('Default Welcome Intent', welcome);
intentMap.set('Test_Test', test);
intentMap.set('CourseEnquiry', getCourses);
intentMap.set('Submission_Dates - sept', getSubmissionDateSep);
agent.handleRequest(intentMap);
});
UPDATE #2
Hey guys, still not got anywhere with this, I have tried adding:
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
According to this document but I get this error when deploying:
The deployment of your Cloud Function failed:
Function load error: Code in file index.js can't be loaded.
Is there a syntax error in your code?
Detailed stack trace: Error: Firebase config variables are not available. Please use the latest version of the Firebase CLI to deploy this function.

You don't show how you're responding to the user with your results, but you'll want to make sure you handle that as part of the then() clause in a Promise. Since the get() in the firestore collection returns a Promise, and you are returning it from your function, you need to make sure that the calling function treats it as a Promise, has a then() clause, and sends back the result as part of something inside this clause.

Related

Flutter Firebase Google Cloud functions error with .onCreate trigger to send notification to device with FCM token Type Error

I've been stuck a while now and would appreciate any help. I've never worked with Cloud Functions before and there may be a fairly easy solution here. I save the FCM token and other proper variables in my documents. The way my firestore database is organized is the following. Trips/{tripId}/proposedRides/{proposedRideId}/. The proposedRides subCollection creates a new document when a potential passenger requests to join a trip. This is where I want a notification sent to the driver via cloud functions and FCM.
I thought I could make a simple function like this. It is authenticated and setup properly to my knowledge. I added the .json credentials in an env variable and all that jazz. Here's what I've been trying:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendProposedRideNotification =
functions.region('southamerica-east1').firestore.document('proposedRides/{rideId}')
.onCreate(async(snapshot, context) => {
// Get the tripId and passengerName associated with the proposedRide
const tripData = snapshot.data();
if (!tripData.proposedByName || !tripData.proposedToDriverFCMToken) {
console.error('Required fields are missing');
return;
}
const passengerName = tripData.proposedByName;
const proposedToDriverFCMToken = tripData.proposedToDriverFCMToken;
// Send the notification to the driver's device
const payload = {
notification: {
title: 'New Proposed Ride',
body: `A new proposed ride has
been created from ${passengerName}!`
}
};
const options = {
priority: 'high',
timeToLive: 60 * 60 * 24
};
return admin.messaging().sendToDevice(proposedToDriverFCMToken, payload, options);
});
I'll then check out firebase functions:logs and get the error
Snapshot has no readTime. Using now()
and
TypeError: Cannot read properties of undefined (reading 'proposedByName')
This is strange because when I test the 'proposedByName' field is created in the document. I've tried changing the beginning of the function to
functions.region('southamerica-east1').firestore.document('trips/{tripId}/proposedRides/{rideId}')
.onCreate(async(snapshot, context) => {
but I had the same errors.

Intent not moving to next intent

first intent
second intent
As shown in the below code, the flow is not going from Number Intent to First Intent, it is been looped into the number loop. In dialog flow, with every intent corresponding context is also made. The flow is not moving as per context and is stuck in NumberIntent.
The flow should be like the google ask the user its survey id, the user says its id 612020 and google start asking its questions. The flow works fine until the type of question is rating i.e. user has to speak number. The error arises when the user is asked to answer in descriptive manner.
'use strict';
// Import the Dialogflow module from the Actions on Google client library.
const {dialogflow} = require('actions-on-google');
const functions = require('firebase-functions');
// Instantiate the Dialogflow client.
const app = dialogflow({debug: true});
const axios = require('axios').default;
global.ques = [];
global.i=0;
app.intent('Default Welcome Intent', (conv) => {
conv.add('Hello! What is your survey id?');
});
app.intent('NumberIntent', (conv,{number}) => {
return axios.get('https://www.openeyessurvey.com/api/get_open_survey_info/612020')
.then((result) => {
result.data.Item.QUESTIONS.map(questionobj => {
ques.push(questionobj.question);
})
conv.ask(ques[i]);
i+=1;
}).catch( err => {
console.log("error", JSON.stringify(err,null,2));
conv.close('This is not a valid survey ID');
});
});
app.intent('FirstIntent', (conv, {number}) => {
conv.ask(ques[i]);
i+=1;
});
app.intent('SecondIntent', (conv) => {
const des = conv.parameters.any;
if(des === 'ankit'){
conv.ask(ques[i]);
i+=1;
}
});
app.intent('ThirdIntent', (conv) => {
conv.ask(ques[i]);
i+=1;
});
app.intent('FourthIntent', (conv, {number}) => {
conv.ask(ques[i]);
i+=1;
});
app.intent('FifthIntent', (conv) => {
conv.ask(ques[i]);
i+=1;
conv.close('Goodbye!')
});
// Set the DialogflowApp object to handle the HTTPS POST request.
exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);
Output
Output2
INVALID INTENT NAME ERROR
I suspect that the issue is that i never actually gets updated.
You treat ques and i as global object, but since this is running under Firebase Cloud Functions, each call may be a new instance of the function. As a new instance, these would get reinitialized.
The flip side of this is that if you didn't get a new instance, it also has the problem that this would not work correctly if more than one person was using the Action at the same time since they would all be sharing the same value of i.
The solution to both is that, instead of storing i as a global variable, store it either in the Actions on Google session storage or in a Dialogflow context parameter.
Storing it as a session parameter, you would get the value, use it, increment it, and then save it again in the session parameter. It might look something like this:
const i = conv.data.i;
conv.ask(ques[i]);
conv.data.i = i+1;

How to trigger Google Composer Airflow dag using appscript?

I want to trigger a Google Composer airflow dag using Appscript. Is there any way to do it via rest API or another way.
If it is possible please suggest the solution.
Airflow has an endpoint that allows to trigger a DAG through its REST API, however it’s not possible to access it directly, since within the Cloud Composer architecture, the Airflow web server is located under an App Engine flexible environment. By default, the Airflow web server is integrated with Identity-Aware Proxy (IAP) and authentication is required.
Based on that, I found an example in the Cloud Composer documentation, that guides you to trigger a DAG using Cloud Functions, although the code is in JavaScript I don’t think it’s possible to execute it by Google App Script.
On the other hand, a workaround is to follow the Triggering DAGs guide changing some settings as follows.
In the creation of the function instead of setting the trigger type as Cloud Storage set it as HTTP, and check the “Allow unauthenticated invocations” for test purpose. An URL will be displayed, the goal is that every time that URL is accessed the DAG is executed.
Modify the first part of the index.js file, since no data would be passed as parameters and also the makeIapPostRequest function to return the response of the API call.
exports.triggerDag = async (req, res) => { // Modification
// Fill in your Composer environment information here.
// The project that holds your function
const PROJECT_ID = 'your-project-id';
// Navigate to your webserver's login page and get this from the URL
const CLIENT_ID = 'your-iap-client-id';
// This should be part of your webserver's URL:
// {tenant-project-id}.appspot.com
const WEBSERVER_ID = 'your-tenant-project-id';
// The name of the DAG you wish to trigger
const DAG_NAME = 'composer_sample_trigger_response_dag';
// Other constants
const WEBSERVER_URL = `https://${WEBSERVER_ID}.appspot.com/api/experimental/dags/${DAG_NAME}/dag_runs`;
const USER_AGENT = 'gcf-event-trigger';
const BODY = {conf: ‘’}; // Modification
// Make the request
try {
const iap = await authorizeIap(CLIENT_ID, PROJECT_ID, USER_AGENT);
const apiReponse = await makeIapPostRequest(WEBSERVER_URL, BODY, iap.idToken, USER_AGENT); // Modification
res.status(200).send('DAG_running!'); // Modification
} catch (err) {
console.error('Error authorizing IAP:', err.message);
throw new Error(err);
}
};
const makeIapPostRequest = async (url, body, idToken, userAgent) => {
const res = await fetch(url, {
method: 'POST',
headers: {
'User-Agent': userAgent,
Authorization: `Bearer ${idToken}`,
},
body: JSON.stringify(body),
});
if (!res.ok) {
const err = await res.text();
console.error('Error making IAP post request:', err.message);
throw new Error(err);
}
return {
apiRes: res.ok, // Modification
};
};
At this point, anything else has to be changed, so in your Script file execute the next instructions in order to trigger the DAG.
function myFunction() {
var response = UrlFetchApp.fetch("Cloud-function-URL");
Logger.log(response.getAllHeaders());
}
Finally, verify in the Airflow web interface if the DAG was triggered.

Getting Undefined value for SignIn.status during account linking

I am working on the Account Linking & set google Sign-IN in Linking type in Google.
I have created two intents, one will call the google Sign-In feature and the second one will read the data from google account for. eg. email id, name.
In Intent 1, I have enabled the webhook call for this intent.
In Intent 2, I have set Event to actions_intent_SIGN_IN & enabled the webhook call for this intent.
Though my these functions (Intents results) in Inline Editors are successfully executing, still I am getting Undefined value for SignIn.status, code is given below, please help.
'use strict';
const {dialogflow, SignIn} = require('actions-on-google');
const app = dialogflow({
clientId: "174911074867-tuffsr7ec28vg7brppr0ntkjutthfq8n.apps.googleusercontent.com",
});
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function accountlinking(agent) {
var signin=new SignIn('To get your account details');
}
function testsignData(agent) {
console.log("status :"+SignIn.status);
}
let intentMap = new Map();
intentMap.set('Intent1', accountlinking);
intentMap.set('Intent2', testsignData);
agent.handleRequest(intentMap);
});
1). On my Action calling, it is asking for the Google Account linking first and after linking process only it is moving ahead. But I need to get into the action, have a little conversation and when required only then asking for the Linking. I need to call via my intent. How to do that?
2). Though my these functions (Intents results) are successfully executing, still I am getting Undefined value for SignIn.status
Your testSigninData() function is calling Signin.status, but you don't have any variable called SignIn in this function, so that is why it is undefined. Try changing your function so it accepts a conv, params and signin object that are given during a sign-in.
If you have a look at the account linking documentation you can see which parameters are provided during the accountlinking process.
Example accountlinking setup for Actions on Google
const {dialogflow, SignIn} = require('actions-on-google');
const app = dialogflow({
// REPLACE THE PLACEHOLDER WITH THE CLIENT_ID OF YOUR ACTIONS PROJECT
clientId: CLIENT_ID,
});
// Intent that starts the account linking flow.
app.intent('Start Signin', (conv) => {
conv.ask(new SignIn('To get your account details'));
});
// Create a Dialogflow intent with the `actions_intent_SIGN_IN` event.
app.intent('Get Signin', (conv, params, signin) => {
if (signin.status === 'OK') {
const payload = conv.user.profile.payload;
conv.ask(`I got your account details, ${payload.name}. What do you want to do next?`);
} else {
conv.ask(`I won't be able to save your data, but what do you want to do next?`);
}
});
The above code uses the actions on google dialogflow handler called app. In your code you are using the WebhookClient object to handle dialogflow intents. I'm not sure if you can use the WebhookClient for actions on google accountlinking.
If it still doesn't work after you changed the testSigninDate function parameters, it might be worth trying to remove the webhookclient and see if you can use the app.intent() calls to handle your intents just like the above code example.

What is correct way to respond from webhook running nodejs?

Trying to implement web-hook (with V2 dialogflow) running nodejs. Received response "MalformedResponse 'final_response' must be set.". Below is the code. To the end of POST (app.post) code block was expecting conv.close would send SimpleResponse. But that's not happening. Need help understand why this error is seen and probable direction to solve it.
Thanks
const express = require('express');
const {
dialogflow,
Image,
SimpleResponse,
} = require('actions-on-google')
const bodyParser = require('body-parser');
const request = require('request');
const https = require("https");
const app = express();
const Map = require('es6-map');
// Pretty JSON output for logs
const prettyjson = require('prettyjson');
const toSentence = require('underscore.string/toSentence');
app.use(bodyParser.json({type: 'application/json'}));
// http://expressjs.com/en/starter/static-files.html
app.use(express.static('public'));
// http://expressjs.com/en/starter/basic-routing.html
app.get("/", function (request, response) {
console.log("Received GET request..!!");
//response.sendFile(__dirname + '/views/index.html');
response.end("Response from my server..!!");
});
// Handle webhook requests
app.post('/', function(req, res, next) {
console.log("Received POST request..!!");
// Log the request headers and body, to aide in debugging. You'll be able to view the
// webhook requests coming from API.AI by clicking the Logs button the sidebar.
console.log('======Req HEADERS================================================');
logObject('Request headers: ', req.headers);
console.log('======Req BODY================================================');
logObject('Request body: ', req.body);
console.log('======Req END================================================');
// Instantiate a new API.AI assistant object.
const assistant = dialogflow({request: req, response: res});
// Declare constants for your action and parameter names
//const PRICE_ACTION = 'price'; // The action name from the API.AI intent
const PRICE_ACTION = 'revenue'; // The action name from the API.AI intent
var price = 0.0
// Create functions to handle intents here
function getPrice(assistant) {
console.log('** Handling action: ' + PRICE_ACTION);
let requestURL = 'https://blockchain.info/q/24hrprice';
request(requestURL, function(error, response) {
if(error) {
console.log("got an error: " + error);
next(error);
} else {
price = response.body;
logObject('the current bitcoin price: ' , price);
// Respond to the user with the current temperature.
//assistant.tell("The demo price is " + price);
}
});
}
getPrice(assistant);
var reponseText = 'The demo price is ' + price;
// Leave conversation with SimpleResponse
assistant.intent(PRICE_ACTION, conv => {
conv.close(new SimpleResponse({
speech: responseText,
displayText: responseText,
}));
});
}); //End of app.post
// Handle errors.
app.use(function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('Oppss... could not check the price');
})
// Pretty print objects for logging.
function logObject(message, object, options) {
console.log(message);
console.log(prettyjson.render(object, options));
}
// Listen for requests.
let server = app.listen(process.env.PORT || 3000, function () {
console.log('Your app is listening on ' + JSON.stringify(server.address()));
});
In general, The "final_response" must be set error is because you didn't send anything back. You have a lot going on in your code, and while you're on the right track, there are a few things in the code that could be causing this error.
First - in the code, it looks like you are confused about how to send a response. You have both a call to conv.close() and the commented out assistant.tell(). The conv.close() or conv.ask() methods are the way to send a reply using this version of the library. The tell() method was used by a previous version and is no longer supported.
Next, your code looks like it is only setting up the assistant object when the routing function is called. While this can be done, it is not the usual way to do it. Typically you'll create the assistant object and setup the Intent handlers (using assistant.intent()) as part of the program initialization. This is a rough equivalent to setting up the express app and the routes for it before the request itself comes in.
The portion that sets up the Assistant and then hooks it into a route might look something like this:
const assistant = dialogflow();
app.post('/', assistant);
If you really wanted to examine the request and response objects first, you might do this as something like
const assistant = dialogflow();
app.post('/', function( req, res ){
console.log(JSON.stringify(req.body,null,1));
assistant( req, res );
});
Related to this appears to be that you're trying to execute code in the route handler and then trying to call the intent handler. Again, this might be possible, but isn't the suggested way to use the library. (And I haven't tried to debug your code to see if there are problems in how you're doing it to see if you're doing it validly.) More typical would be to call getPrice() from inside the Intent handler instead of trying to call it from inside the route handler.
But this leads to another problem. The getPrice() function calls request(), which is an asynchronous call. Async calls are one of the biggest problems that causes an empty response. If you are using an async call, you must return a Promise. The easiest way to use a Promise with request() is to use the request-promise-native package instead.
So that block of code might look something (very roughly) like this:
const rp = require('request-promise-native');
function getPrice(){
return rp.get(url)
.then( body => {
// In this case, the body is the value we want, so we'll just return it.
// But normally we have to get some part of the body returned
return body;
});
}
assistant.intent(PRICE_ACTION, conv => {
return getPrice()
.then( price => {
let msg = `The price is ${price}`;
conv.close( new SimpleResponse({
speech: msg,
displayText: msg
});
});
});
The important thing to note about both getPrice() and the intent handler are that they both return a Promise.
Finally, there are some odd aspects in your code. Lines such as res.status(500).send('Oppss... could not check the price'); probably won't do what you think they will do. It won't, for example, send a message to be spoken. Instead, the Assistant will just close the connection and say that something went wrong.
Many thanks to #Prisoner. Below is the V2 working solution based on above comments. Same has been verified on nodejs webhook (without firebase). V1 version of the code was referenced from https://glitch.com/~aog-template-1
Happy coding..!!
// init project pkgs
const express = require('express');
const rp = require('request-promise-native');
const {
dialogflow,
Image,
SimpleResponse,
} = require('actions-on-google')
const bodyParser = require('body-parser');
const request = require('request');
const app = express().use(bodyParser.json());
// Instantiate a new API.AI assistant object.
const assistant = dialogflow();
// Handle webhook requests
app.post('/', function(req, res, next) {
console.log("Received POST request..!!");
console.log('======Req HEADERS============================================');
console.log('Request headers: ', req.headers);
console.log('======Req BODY===============================================');
console.log('Request body: ', req.body);
console.log('======Req END================================================');
assistant(req, res);
});
// Declare constants for your action and parameter names
const PRICE_ACTION = 'revenue'; // The action name from the API.AI intent
var price = 0.0
// Invoke http request to obtain blockchain price
function getPrice(){
console.log('getPrice is invoked');
var url = 'https://blockchain.info/q/24hrprice';
return rp.get(url)
.then( body => {
// In this case, the body is the value we want, so we'll just return it.
// But normally we have to get some part of the body returned
console.log('The demo price is ' + body);
return body;
});
}
// Handle AoG assistant intent
assistant.intent(PRICE_ACTION, conv => {
console.log('intent is triggered');
return getPrice()
.then(price => {
let msg = 'The demo price is ' + price;
conv.close( new SimpleResponse({
speech: msg,
}));
});
});
// Listen for requests.
let server = app.listen(process.env.PORT || 3000, function () {
console.log('Your app is listening on ' + JSON.stringify(server.address()));
});