Smooch: How to do postback dependent state transition? - javascript

I am trying to transition the script from one state to another based on Smooch postback payloads; but getting error code H12.
Consider the example https://github.com/smooch/smooch-bot-example
Say I modify the script https://github.com/smooch/smooch-bot-example/blob/master/script.js as follows
start: {
receive: (bot) => {
return bot.say('Hi! I\'m Smooch Bot! Continue? %[Yes](postback:askName) %[No](postback:bye) );
}
},
bye: {
prompt: (bot) => bot.say('Pleasure meeting you'),
receive: () => 'processing'
},
The intention is that the's bot's state would transition depending on the postback payload.
Question is, how do I make that happen?
My approach was add
stateMachine.setState(postback.action.payload)
to the handlePostback method of github.com/smooch/smooch-bot-example/blob/master/heroku/index.js
However, that threw an error code H12. I also experimented with
stateMachine.transition(postback.action,postback.action.payload)
to no avail.

I got the same issue with the [object Object] instead of a string. This is because the state you get or set with a function is contained in an object, not a string... I fixed it with this code inside index.js, replacing the existing handlePostback function in the smooch-bot-example GitHub repo:
function handlePostback(req, res) {
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
const postback = req.body.postbacks[0];
if (!postback || !postback.action) {
res.end();
};
const smoochPayload = postback.action.payload;
// Change conversation state according to postback clicked
switch (smoochPayload) {
case "POSTBACK-PAYLOAD":
Promise.all([
stateMachine.bot.releaseLock(),
stateMachine.setState(smoochPayload), // set new state
stateMachine.prompt(smoochPayload) // call state prompt() if any
]);
res.end();
break;
default:
stateMachine.bot.say("POSTBACK ISN'T RECOGNIZED") // for testing purposes
.then(() => res.end());
};
}
Then inside script.js all you need to do is define states corresponding to the exact postback payloads. If you have multiple postbacks that should take the user to other states, just add them to the case list like so :
case "POSTBACK-PAYLOAD-1":
case "POSTBACK-PAYLOAD-2":
case "POSTBACK-PAYLOAD-3":
case "POSTBACK-PAYLOAD-4":
Promise.all([
stateMachine.bot.releaseLock(),
stateMachine.setState(smoochPayload), // set new state
stateMachine.prompt(smoochPayload) // call state prompt() if any
]);
res.end();
break;
Note that you should not write break; at the end of each case if the outcome you want is the same (here : setting the state and prompting the corresponding message).
If you want to handle other postbacks differently, you can add cases after the break; statement and do other stuff instead.
Hope this helps!

Postbacks won't automatically transition your conversation from one state to the next, you have to write that logic yourself. Luckily the smooch-bot-example you're using already has a postback handler defined here:
https://github.com/smooch/smooch-bot-example/blob/30d2fc6/heroku/index.js#L115
So whatever transition logic you want should go in there. You can do this by creating a stateMachine and calling receiveMessage() on it the same way handleMessages() already works. For example:
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
stateMachine.receiveMessage({
text: 'whatever your script expects'
})
Alternatively, you could have your handlePostback implementation call stateMachine.setState(state) and stateMachine.prompt(state) independently, if you wanted to have your postbacks behave differently from regular text responses.

If you want to advance the conversation based on a postback you'll have to first output the buttons from the bot's prompt (so you can handle the button click in the receive), modify the handlePostback function in index.js, then handle the user's "reply" in your receive method - try this - modify script.js like so:
start: {
prompt: (bot) => bot.say(`Hi! I'm Smooch Bot! Continue? %[Yes](postback:askName) %[No](postback:bye)`),
receive: (bot, message) => {
switch(message.text) {
case 'Yes':
return bot.say(`Ok, great!`)
.then(() => 'hi')
break;
case 'No':
return bot.say(`Ok, no prob!`)
.then(() => 'bye')
break;
default:
return bot.say(`hmm...`)
.then(() => 'processing')
break;
}
}
},
hi: {
prompt: (bot) => bot.say('Pleasure meeting you'),
receive: () => 'processing'
},
bye: {
prompt: (bot) => bot.say('Pleasure meeting you'),
receive: () => 'processing'
},
Then modify the handlePostback function in index.js so that it treats a postback like a regular message:
function handlePostback(req, res) {
const postback = req.body.postbacks[0];
if (!postback || !postback.action)
res.end();
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
const msg = postback;
// if you want the payload instead just do msg.action.paylod
msg.text = msg.action.text;
stateMachine.receiveMessage(msg)
.then(() => res.end())
.catch((err) => {
console.error('SmoochBot error:', err);
res.end();
});
}
Now when a user clicks your button it will be pushed to the stateMachine and handled like a reply.

Related

How to perform two firebase database operation on different nodes in single request/function?

I am a react-native developer and new to firebase. I am performing firebase realtime database operation, have a look at code below;
firebase.database().ref('events/wedding/items').push(object).then((data) => {
//success callback
dispatch(addPendingInvoice({ ...invoice, id: data.key }))
Alert.alert('Successfully added to Invoices', 'Please go to invoice section to clear first and continue.', [{ text: 'Ok' }])
}).catch((error) => {
//error callback
Alert.alert("Can't book package.", 'Please check your internet connection!', [{ text: 'OK', style: 'destructive' }])
})
Now, I wish to push another object to another node events/wedding/packages right after this firebase database function above. I can use another function inside then callback in above firebase functions. This is not a professional way to do this.
Is there any way to do this?
You can use the update() method to "simultaneously write to specific children of a node without overwriting other child nodes". Note that "simultaneous updates made this way are atomic: either all updates succeed or all updates fails", see the doc.
So in your case you would do along the following lines:
var newNodeKey = firebase.database().ref().child('events/wedding/items').push().key;
var updates = {};
updates['events/wedding/items/' + newNodeKey] = { foo: "bar" };
updates['events/wedding/packages/' + newNodeKey] = { bar: "foo" };
firebase.database().ref().update(updates)
.then(() => {
// The two writes are completed, do whatever you need
// e.g. dispatch(...);
});
All Firebase operations return a promise so you can use Promise.all() to run them all simultaneously.
Promise.all([
firebase.database().ref(reference).set({}),
firebase.database().ref(reference2).set({})
]).then(() => {
console.log("Operations Successful")
}).catch((e) => console.log(e))
You can also push all your operations to an array and then pass that array in Promise.all()

Trigger won't return the proper user object in Realm/MongoDB

With Realm sync of MongoDB, I'm trying to launch a trigger when a realm user is created to insert his newly created ID into my cluster. Here's the javascript function I made that is being called by the trigger :
exports = async function createNewUserDocument({ user }) {
const users = context.services
.get("mongodb-atlas")
.db("BD")
.collection("patients");
const query = { email: context.user.data.email };
const update = {
$set: {
patientId: context.user.id
}
};
// Return the updated document instead of the original document
const options = { returnNewDocument: true };
console.log(context.user.data.email);
return users.findOneAndUpdate(query, update, options)
.then(updatedDocument => {
if(updatedDocument) {
console.log(`Successfully updated document: ${updatedDocument}.`)
} else {
console.log("No document matches the provided query.")
}
return updatedDocument
})
.catch(err => console.error(`Failed to find and update document: ${err}`))
};
When running from the embed editor, while specifying the proper user manually, it's working perfectly. However, when launched by the trigger, it looks like the user is the system user and not the created user, because the error I get in the logs is the same I get when I run from the editor by specifying System user, which is Failed to find and update document: FunctionError: cannot compare to undefined. This makes sense because the System user is not a user per se, so the context.user is undefined.
I find it weird since I specify in the function settings that it should be executed with the permissions of the user calling the function. So my question is, is it possible to access the user.context of a user on his creation, and if so, how would I do it ?

Jasmine test case for PrimeNg Confirmation Service not working

I have a function which executes some operations in the "accept" call of PrimeNg Confirmation service. I tried to write a Unit test case for it as following:
fit('submit preview config', fakeAsync(() => {
addValues();
component.submitConfig();
component.submitPreviewForm();
fixture.detectChanges();
const confirmationService = TestBed.get(ConfirmationService);
tick(200);
spyOn<any>(confirmationService, 'confirm').and.callFake((params: any) => {
params.accept();
httpMock.expectOne(baseUrl + '/api/project/addOrUpdate').flush(mockSubmitResponse);
expect(component.successMsz).toBe(mockSubmitResponse.message);
});
flush();
}));
The problem is the execution never goes inside callFake. The test case passes but the operation never takes place. Any ideas are welcome.
This is the function I want to test:
submitPreviewForm() {
const messageContent = `<strong>You have updated the following fields:</strong><br/>
<span class="subText" style="font-size: 12px; color: blue;">•${Array.from(this.updatedHighlightedFields).join('<br/> •')}</span>
<br/>
<strong>This will clear JIRA data from DB. Are you sure you want to proceed?</strong>`;
this.confirmationService.confirm({
message: messageContent,
accept: () => {
...
}
});
}
I am using V6 of PrimeNg.
I saw the implementation on this Stack Overflow question:
Angular Unit Test of a PRIME ng confirmation service
Your order of operations seems to be a bit off, you need to spy before calling submitPreviewform.
Try this:
fit('submit preview config', fakeAsync(() => {
const confirmationService = TestBed.get(ConfirmationService); // grab a handle of confirmationService
spyOn<any>(confirmationService, 'confirm').and.callFake((params: any) => {
params.accept();
httpMock.expectOne(baseUrl + '/api/project/addOrUpdate').flush(mockSubmitResponse);
expect(component.successMsz).toBe(mockSubmitResponse.message);
}); // spy on confirmationService.confirm now
addValues();
component.submitConfig();
component.submitPreviewForm();
fixture.detectChanges();
tick(200);
flush();
}));

Combine multiple observables in to a single RxJS stream

It appears I am lacking knowledge on which RxJS operator to resolve the following problem:
In my music application, I have a submission page (this is like a music album). To load the submission, I use the following query:
this.submissionId = parseInt(params['album']);
if (this.submissionId) {
this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.subscribe((submission) => {
//submission loaded here!
});
}
Easy enough! However, once I've loaded the submission, I have to load some auxiliary information such as the current user (to check if they are the artist of the submission) and comments. In order to avoid nested subscriptions, I can modify the above query to use switchMap to switch the query stream to user and comments observables once the submission resolves:
// stream to query for the submission and then switch query to user
this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.submission = submission;
return this.auth.user$
})
).subscribe((user) => {
// needs value of submission here
if (user.id == this.submission.user.id) {
//user is owner of submission
}
})
// stream to query for the submission and then switch query to comments
this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.comments$ = this.commentsGQL.watch({
submissionId: submission.id //needs submission response here
})
return this.comments$.valueChanges
})
).subscribe((comments) => {
this.comments = comments;
})
Great! I've avoided the nested subscription issue BUT now...the first part of each submission request is identical. Basically, once, the submission is queried, i want to launch off two parallel queries:
a query for the user
a query for the comments
Which RxJS operator can perform such an operation? I suppose the subscribe at the end would emit an array response like:
.subscribe([user, comments] => {
// check if user == submission.user.id here
// also assign comments to component variable here
})
I believe mergeMap is sort of what I need but I'm not sure how to implement that properly. Or is this a case where I should share() the submission query and then build off my parallel queries separately? I'm very curious! Please let me know, thanks!
You can use the RxJS forkJoin operator for this scenario. As stated on the documentation,
When all observables complete, emit the last emitted value from each.
const userQuery$ = this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.submission = submission;
return this.auth.user$
})
)
// stream to query for the submission and then switch query to comments
const commentsQuery$ = this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.comments$ = this.commentsGQL.watch({
submissionId: submission.id //needs submission response here
})
return this.comments$.valueChanges
})
)
forkJoin(userQuery$, commentsQuery$).subscribe([user, comments] => {
// check if user == submission.user.id here
// also assign comments to component variable here
})
Try:
this.submissionGQL.watch({
id: this.submissionId
}).valueChanges.pipe(
switchMap(submission => {
this.submission = submission;
const user$ = this.auth.user$;
this.comments$ = this.commentsGQL.watch({
submissionId: submission.id
});
return combineLatest(user$, this.comments$);
}),
// maybe put a takeUntil to remove subscription and not cause memory leaks
).subscribe(([user, comments]) => {
// check if user == submission.user.id here
// also assign comments to component variable here
});
Something you should consider is eliminating instance variables with the help of the async pipe given by Angular (https://malcoded.com/posts/angular-async-pipe/).
It will subscribe to the observable, present it into the view and automatically unsubscribe when the view is destroyed.
So, using that, we can get rid of this.submissions = submission by putting:
submissions$: Observable<ISubmission>; // assuming there is an interface of ISubmission, if not put any
// then when this.submissionId is defined
this.submissions$ = this.submissionGQL.watch({
id: this.submissionId
}).valueChanges;
// then when using it in your view you can do {{ this.submissions$ | async }}
The same thing can go for this.comments$. All of this is optional though. I try to minimize instance variables as much as possible when using RxJS because too many instance variables leads to confusion.
Then you can lead off of this.submissions$ observable and subscribe for the other main stream.
this.submission$.pipe(
switchMap(submission => ..... // everything else being the same
)
I chose the combineLatest operator but you can use zip and forkJoin as you see fit. They all have subtle differences (https://scotch.io/tutorials/rxjs-operators-for-dummies-forkjoin-zip-combinelatest-withlatestfrom).

What's the proper way to dispatch action inside epic?

What is the proper way to dispatch operationReset() inside of redux-observable epic?
Should I import actual store and use it?
It used to be like this, but following store is deprecated, and will be removed
// show operation failed message
(action$, store) => action$.ofType(OPERATION_FAILURE).map(() => (error({
title: 'Operation Failed',
message: 'Opps! It didn\'t go through.',
action: {
label: 'Try Again',
autoDismiss: 0,
callback: () => store.dispatch(operationReset())
}
}))),
This probably raises a larger question about how one should do notifications with callbacks, since it means you're sending a non-JSON serializable function as part of an action.
I'll assume you want to match the react notification system still. There's a way you can do this using Observable.create:
(action$, store) =>
action$.pipe(
ofType(OPERATION_FAILURE),
mergeMap(() =>
Observable.create(observer => {
observer.next(
error({
title: "Operation Failed",
message: "Oops! It didn't go through.",
action: {
label: "Try Again",
autoDismiss: 0,
callback: () => {
// Send off a reset action
observer.next(operationReset());
// Close off this observable
observer.complete();
},
// If the notification is dismissed separately (can they click an x?)
onRemove: () => observer.complete()
}
})
);
})
)
);
NOTE: I still wouldn't want to send callbacks as part of actions. Amusingly, one of my projects uses that notification system component too -- we have epics that will add notifications and clear them based on actions. All actions stay pure, and the notification system is a controlled side effect.

Categories