I Have a notification dialog to be invoked proactively...
e.g.
Bot: Hi, you have an event scheduled in next 15 mts... blah blah
Bot: would you want me to email the details?
User input: yes/no
Bot: Great!
This is a simple waterfall dialog...
step1 inform and prompt confirm.
step2. process user input..
Now this dialog is initiated proactively .
step 1 works.
However the dialogContext / dialogStack is not getting saved and when user says yes it is going to main dailog and not this proactive dialog which should be on top of stack.
Basically, none of the activityHandler methods like onDialog event on the activityHandler are getting invoked for proactive dialog.
Question is how to have the messages from proactive Dialog go through activityHandler methods so that the dialogStack is persisted ?
I use nodejs.
Updating with Code sample below
// middleware
const { ActivityTypes } = require('botbuilder');
class MyMiddleware {
async onTurn(context, next) {
await context.onSendActivities(async (context, activities, nextSend) => {
console.log(`messages: ${activities.map( a => a.text).join(',')}`)
return await nextSend();
});
// By calling next() you ensure that the next Middleware is run.
return await next();
};
}
module.exports.MyMiddleware = MyMiddleware;
// main bot.
const { ActivityHandler, TurnContext } = require('botbuilder');
class ProactiveBot extends ActivityHandler {
constructor(conversationReferences) {
super();
this.conversationReferences = conversationReferences;
this.onConversationUpdate(async (context, next) => {
this.addConversationReference(context.activity);
await next();
});
this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded;
for (let cnt = 0; cnt < membersAdded.length; cnt++) {
if (membersAdded[cnt].id !== context.activity.recipient.id) {
const welcomeMessage = 'Welcome to the Proactive Bot sample. Navigate to http://localhost:3978/api/notify to proactively message everyone who has previously messaged this bot.';
await context.sendActivity(welcomeMessage);
}
}
await next();
});
this.onMessage(async (context, next) => {
this.addConversationReference(context.activity);
await context.sendActivity(`You sent '${ context.activity.text }'`);
await next();
});
this.onDialog(async (context, next) =>{
console.log(`I am called`)
})
}
addConversationReference(activity) {
const conversationReference = TurnContext.getConversationReference(activity);
this.conversationReferences[conversationReference.conversation.id] = conversationReference;
}
}
module.exports.ProactiveBot = ProactiveBot;
// index
const bot = new ProactiveBot(conversationReferences);
server.post('/api/messages', (req, res) => {
adapter.processActivity(req, res, async (context) => {
// Route to main dialog.
await bot.run(context);
});
});
server.get('/api/notify', async (req, res) => {
for (const conversationReference of Object.values(conversationReferences)) {
await adapter.continueConversation(conversationReference, async turnContext => {
await turnContext.sendActivity('proactive hello');
});
}
res.setHeader('Content-Type', 'text/html');
res.writeHead(200);
res.write('<html><body><h1>Proactive messages have been sent.</h1></body></html>');
res.end();
});
When I call notify api I expect the onDialog event to be called... and print "I am called". But that doest not get printed to console.
It looks like you do not have the following bit of code in your "mainBot.js" file. Adding it should solve your problem and not require you to save state after every step. I have a proactive dialog that presents the user with a "yes/no" option, as well. However, I do not have to save with every step in order to capture the user's response.
this.onDialog(async (context, next) => {
console.log('Dialog detected');
// Save any state changes.
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
await next();
});
That being said, I have a component dialog that is listening for the user's choice sending an appropriate response. The proactive dialog is not being captured but the response context is. Because the "mainDailog.js" extends the component dialog the context is added to the stack which is then processed via the this.onDialog() method in "mainBot.js". Thus, state is saved. Check out this SO response I posted recently that demo's this setup which includes the above code but is not displayed. In that case, the user also wanted a timer built into the process which you can ignore.
Hope of help!
Related
I've written testcase for the below code where we try to delete the genre document if present in the DB, otherwise throw error with status code 404.
Code:
router.delete('/:id',[endpoint_middleware,admin_middleware], async (req, res) => {
const doc_to_delete = await CollectionClass.findByIdAndRemove(req.params.id);
if (!doc_to_delete) return res.status(404).send('The genre with the given ID was not found.');
res.send(doc_to_delete);
});
Testcase:
describe('Delete genre', () => {
let token;
let curr_genre;
let id;
beforeEach(async () => {
curr_genre = new CollectionClass({name: 'genre1'});
await curr_genre.save();
id = curr_genre._id;
token = new UserCollectionClass({isAdmin : true}).generateToken();
});
const delete_genre = async () => {
return await supertest(server)
.delete('/api/genres/'+id)
.set('x-jwtInHeader',token)
.send();
};
it('return error for deleting invalid id', async () => {
id = 1;
const response = await delete_genre();
expect(response.status).toBe(404);
});
});
The expected response status is 404 since the id is invalid. But the received response status is 500. Other test scenarios work as expected. Kindly help.
Looks like the issue can be related to tests being not strictly isolated. Lets just try to debug a bit and improve the current code.
you have your delete handler, but I see it is error-prone as await operation is not handled well.
can you please try to replace it with this code and run tests again, you should see the exact error
router.delete("/:id", [endpoint_middleware, admin_middleware], async (req, res) => {
try {
const doc_to_delete = await CollectionClass.findByIdAndRemove(req.params.id);
if (!doc_to_delete) return res.status(404).send("The genre with the given ID was not found.");
res.send(doc_to_delete);
} catch (error) {
console.log(error)
throw new Error(error)
}
});
I'm new to fetching and posting data using an API, and I can't work out how to do something once my Post has been completed.
I have a function that calls the API with the Post data. I need to set the loading state to false once the Post has been completed. Everything works apart from that, the data gets sent to Mongo, I just need to turn off my loading spinner once it has completed.
How do I do this, please?
This is how I'm trying to do it:
const postData = async () => {
setLoading(true)
await axios.post('/api/addData',form)
.then(response => {
setLoading(false)
})
}
And this is the API bit:
import { connectToDatabase } from "util/mongodb"
export default async (req, res) => {
const { db } = await connectToDatabase()
await db
.collection("posts")
.insertOne(req.body);
}
There is two potential problem in your code, first you're not sending any data back to the front in your backend code. Usually you send back the id of the inserted element (It can be usefull to do some mutation in your front), you'll also need to try catch your call to the db to notify that something went wrong to the front end side :
import { connectToDatabase } from "util/mongodb"
export default async (req, res) => {
try {
const { db } = await connectToDatabase()
const insertedPost = await db
.collection("posts")
.insertOne(req.body);
res.status(201).send(insertedPost.insertedId);
// again it's up to you to know what can be usefull to your front-end to use
// Look at http status code online to know what's the best fit
} catch (err) {
res.status(500).send(err.message);
// send whatever that can be usefull for your front end to handle the error
}
}
In your front-end code you're using await with .then, it's weird usage. You can put your setLoading(false) after the await without the .then but you'll still need to try catch it. What I prefer to do is using the finally block to stop loading, so if my api call fail the loading is still stopped :
const postData = async () => {
setLoading(true)
try {
const response = await axios.post('/api/addData',form)
// do something with response
} catch (err) {
// notify user that something went wrong
} finally {
setLoading(false);
}
}
const postData = () => {
setLoading(true)
axios.post('/api/addData',form)
.then(response => {
// do something with response
})
.catch((err) => {
// notify user that something went wrong
})
.finally(() => {
setLoading(false);
})
}
Building a NodeJS REST API.
Trying to send load data from FireBase collection, then sending it to the user (as API response).
Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {
snapshot.docs.forEach(msg => {
console.log(msg.data().messageContent)
return {
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}
})
})
}
try {
const chatData = await getChatData()
console.log(chatData)
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData
})
} catch (err) {
if (!err.statusCode) {
err.statusCode(500)
}
next(err)
}
}
As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:
[] (from console.logs(chatData))
All messages (from console.log(msg.data().messageContent))
Is there any way to block the code unti the firebase data realy fetched?
If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId;
const collectionRef = db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc');
try {
const chatsQuerySnapshot = await collectionRef.get();
const chatData = [];
chatsQuerySnapshot.forEach((msg) => {
console.log(msg.data().messageContent);
chatData.push({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
});
});
console.log(chatData);
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode(500);
}
next(err);
}
};
The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.
You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.
const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
db.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})
const snapshot = await getChatData();
console.log(snapshot)
// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}));
res.status(200).json({
message: 'Chat Has Found',
chatData
})
Right now, getChatData is this (short version):
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {}) // some things inside
}
What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:
const getChatData = async () =>
db
.collection('chats')
// ...
Which is the same as const getChatData = async() => { return db... }
Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.
I need to add fields before sending the object so I tried
turnContext.onSendActivities(async (ctx, activities, nextSend) => {
for (let activity of activities){
activity.channelData.data='hi'
console.log(activity)
}
return await nextSend();
})
but it doesn't reflect as what I understand the activities have been already sent and the function is for logging.
is there any way to make it work ?
Just include the onSendActivities() function within a handler. Then be sure to return await next() before proceeding to any following code, including the handler's await next(). The following setup will append the { data: 'hi' } object to every activity.
The only other step is to decide the right handler and the right logic to send the data you want when you want.
Hope of help!
this.onTurn(async (turnContext, next) => {
await turnContext.onSendActivities(async (ctx, activities, next) => {
for (let activity of activities) {
activity.channelData = { data: 'hi' };
}
return await next();
});
await next();
});
I'm used to v3 node botbuilder sdk so I have a middleware where I look at the dialog stack and do operation I needed as follows.
V3 middleware which knows dialog stack:-
bot.use({
botbuilder: function (session, next) {
if (session.dialogStack()&& session.dialogStack().length <= 0 ) {
// Do something is dialog stack is empty.
}
},
send: function (event, next) {
if (event.type != "typing" && event.type != "endOfConversation") {
logUserConversation("Botoutput", event);
}
next();
}
});
V4 middleware where I need to use dialog stack to perform some operations.
adapter.use(async (turnContext, next) => {
// pre-processing of the current incoming activity
turnContext.onSendActivities(async (sendContext, activities, nextSend) => {
// console.log(`pre-processing of outgoing activities`);
await nextSend();
***//Need to know the dialog stack here.***
});
await next();
});
I looked up on turnContext object but there is no indication if dialog stack. I can see DialogContext object has a 'stack' property but, not sure how to use in my middleware.
You only need to add the activities.filter method to retrieve the passed thru data and you're good to go.
const conversationState = new ConversationState(memoryStorage);
const userState = new UserState(memoryStorage);
adapter.use(async (turnContext, next) => {
const userActivity = turnContext.activity;
if (userActivity.from.role === 'user' && turnContext.activity.text.length > 0) {
console.log('From user: ', userActivity);
}
turnContext.onSendActivities(async (sendContext, activities, nextSend) => {
await nextSend();
activities.filter(a => a.type !== 'typing' && a.type !== 'endOfConversation').forEach(a => console.log('From bot: ', a));
});
await next();
});
Hope of help!