Skip to content

Commit

Permalink
[BotBuilder-Solutions] End the executed dialog when an issue is raised (
Browse files Browse the repository at this point in the history
microsoft#2800)

* Cancel dialogs updating routerDialog bb-solutions

* Remove try/catch validation in the onBegin event

* Cancel dialogs updating activityHandlerDialog bb-solutions

* Add known issue related to the cancellation of the dialog
  • Loading branch information
Batta32 authored and darrenj committed Dec 17, 2019
1 parent 278c6d3 commit 4247b60
Show file tree
Hide file tree
Showing 3 changed files with 163 additions and 113 deletions.
35 changes: 34 additions & 1 deletion docs/_docs/help/known-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,37 @@ In the Bot Builder SDK version 4.5.3 and below, there is a bug which causes the
For more information, refer to the following resources:
- [Bot Builder SDK issue](https://github.com/microsoft/botbuilder-dotnet/issues/2474)
- [Bot Builder SDK pull request](https://github.com/microsoft/botbuilder-dotnet/pull/2580)
- [Bot Builder SDK pull request](https://github.com/microsoft/botbuilder-dotnet/pull/2580)
## Dialogs are not ending when an error is raised in the conversation
There is a known issue in the dialogs of the Virtual Assistant and the Skill in which the executed conversation is not ending when an error is raised, this is happening in C# and in TypeScript as well.
To resolve this issue, it's necessary to add a `try/catch` in the `MainDialog` of the bots, to handle any error during the conversation:
[MainDialog.ts](https://github.com/microsoft/botframework-solutions/blob/master/templates/Virtual-Assistant-Template/typescript/samples/sample-assistant/src/dialogs/mainDialog.ts)
```typescript
protected async onContinueDialog(dc: DialogContext): Promise<DialogTurnResult> {
try {
} catch (error) {
return await dc.endDialog();
}
}
```

[MainDialog.cs](https://github.com/microsoft/botframework-solutions/blob/master/templates/Virtual-Assistant-Template/csharp/Sample/VirtualAssistantSample/Dialogs/MainDialog.cs)
```C#
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
try {
} catch (Exception ex) {
return await innerDc.EndDialogAsync().ConfigureAwait(false);
}
}
```

For more information, check the following issues:
* [#1589](https://github.com/microsoft/botframework-solutions/issues/1589) - `OnTurnError function inside DefaultAdapter doesn't end the current dialog`
* [#2766](https://github.com/microsoft/botframework-solutions/issues/2766) - `OnTurnError is not getting called in VA`
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
Expand Down Expand Up @@ -55,80 +56,88 @@ protected override Task<DialogTurnResult> OnBeginDialogAsync(DialogContext inner
/// </remarks>
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
// Check for any interruptions.
var status = await OnInterruptDialogAsync(innerDc, cancellationToken).ConfigureAwait(false);

if (status == InterruptionAction.Resume)
{
// Interruption message was sent, and the waiting dialog should resume/reprompt.
await innerDc.RepromptDialogAsync().ConfigureAwait(false);
}
else if (status == InterruptionAction.Waiting)
{
// Interruption intercepted conversation and is waiting for user to respond.
return EndOfTurn;
}
else if (status == InterruptionAction.End)
{
// Interruption ended conversation, and current dialog should end.
return await innerDc.EndDialogAsync().ConfigureAwait(false);
}
else if (status == InterruptionAction.NoAction)
try
{
// No interruption was detected. Process activity normally.
var activity = innerDc.Context.Activity;
// Check for any interruptions.
var status = await OnInterruptDialogAsync(innerDc, cancellationToken).ConfigureAwait(false);

switch (activity.Type)
if (status == InterruptionAction.Resume)
{
case ActivityTypes.Message:
{
// Pass message to waiting child dialog.
var result = await innerDc.ContinueDialogAsync().ConfigureAwait(false);
// Interruption message was sent, and the waiting dialog should resume/reprompt.
await innerDc.RepromptDialogAsync().ConfigureAwait(false);
}
else if (status == InterruptionAction.Waiting)
{
// Interruption intercepted conversation and is waiting for user to respond.
return EndOfTurn;
}
else if (status == InterruptionAction.End)
{
// Interruption ended conversation, and current dialog should end.
return await innerDc.EndDialogAsync().ConfigureAwait(false);
}
else if (status == InterruptionAction.NoAction)
{
// No interruption was detected. Process activity normally.
var activity = innerDc.Context.Activity;

if (result.Status == DialogTurnStatus.Empty)
switch (activity.Type)
{
case ActivityTypes.Message:
{
// There was no waiting dialog on the stack, process message normally.
await OnMessageActivityAsync(innerDc).ConfigureAwait(false);
// Pass message to waiting child dialog.
var result = await innerDc.ContinueDialogAsync().ConfigureAwait(false);

if (result.Status == DialogTurnStatus.Empty)
{
// There was no waiting dialog on the stack, process message normally.
await OnMessageActivityAsync(innerDc).ConfigureAwait(false);
}

break;
}

break;
}

case ActivityTypes.Event:
{
await OnEventActivityAsync(innerDc).ConfigureAwait(false);
break;
}

case ActivityTypes.Invoke:
{
// Used by Teams for Authentication scenarios.
await innerDc.ContinueDialogAsync().ConfigureAwait(false);
break;
}

case ActivityTypes.ConversationUpdate:
{
await OnMembersAddedAsync(innerDc).ConfigureAwait(false);
break;
}

default:
{
// All other activity types will be routed here. Custom handling should be added in implementation.
await OnUnhandledActivityTypeAsync(innerDc).ConfigureAwait(false);
break;
}
case ActivityTypes.Event:
{
await OnEventActivityAsync(innerDc).ConfigureAwait(false);
break;
}

case ActivityTypes.Invoke:
{
// Used by Teams for Authentication scenarios.
await innerDc.ContinueDialogAsync().ConfigureAwait(false);
break;
}

case ActivityTypes.ConversationUpdate:
{
await OnMembersAddedAsync(innerDc).ConfigureAwait(false);
break;
}

default:
{
// All other activity types will be routed here. Custom handling should be added in implementation.
await OnUnhandledActivityTypeAsync(innerDc).ConfigureAwait(false);
break;
}
}
}

if (innerDc.ActiveDialog == null)
{
// If the inner dialog stack completed during this turn, this component should be ended.
return await innerDc.EndDialogAsync().ConfigureAwait(false);
}
}

if (innerDc.ActiveDialog == null)
return EndOfTurn;
}
catch (Exception ex)
{
// If the inner dialog stack completed during this turn, this component should be ended.
await innerDc.Context.SendActivityAsync(new Activity(type: ActivityTypes.Trace, text: $"Exception Message: {ex.Message}, Stack: {ex.StackTrace}")).ConfigureAwait(false);
return await innerDc.EndDialogAsync().ConfigureAwait(false);
}

return EndOfTurn;
}

protected override async Task<DialogTurnResult> EndComponentAsync(DialogContext outerDc, object result, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,62 +21,70 @@ export abstract class RouterDialog extends InterruptableDialog {
}

protected async onContinueDialog(innerDc: DialogContext): Promise<DialogTurnResult> {
const status: InterruptionAction = await this.onInterruptDialog(innerDc);

if (status === InterruptionAction.MessageSentToUser) {
// Resume the waiting dialog after interruption
await innerDc.repromptDialog();

return Dialog.EndOfTurn;
} else if (status === InterruptionAction.StartedDialog) {
// Stack is already waiting for a response, shelve inner stack
return Dialog.EndOfTurn;
} else {
const activity: Activity = innerDc.context.activity;

if (ActivityExtensions.isStartActivity(activity)) {
await this.onStart(innerDc);
}
try {
const status: InterruptionAction = await this.onInterruptDialog(innerDc);

if (status === InterruptionAction.MessageSentToUser) {
// Resume the waiting dialog after interruption
await innerDc.repromptDialog();

return Dialog.EndOfTurn;
} else if (status === InterruptionAction.StartedDialog) {
// Stack is already waiting for a response, shelve inner stack
return Dialog.EndOfTurn;
} else {
const activity: Activity = innerDc.context.activity;

if (ActivityExtensions.isStartActivity(activity)) {
await this.onStart(innerDc);
}

switch (activity.type) {
case ActivityTypes.Message: {
// Note: This check is a workaround for adaptive card buttons that should map to an event
// (i.e. startOnboarding button in intro card)
if (activity.value) {
await this.onEvent(innerDc);
} else if (activity.text !== undefined && activity.text !== '') {
const result: DialogTurnResult = await innerDc.continueDialog();
switch (result.status) {
case DialogTurnStatus.empty: {
await this.route(innerDc);
break;
switch (activity.type) {
case ActivityTypes.Message: {
// Note: This check is a workaround for adaptive card buttons that should map to an event
// (i.e. startOnboarding button in intro card)
if (activity.value) {
await this.onEvent(innerDc);
} else if (activity.text !== undefined && activity.text !== '') {
const result: DialogTurnResult = await innerDc.continueDialog();
switch (result.status) {
case DialogTurnStatus.empty: {
await this.route(innerDc);
break;
}
case DialogTurnStatus.complete: {
await this.complete(innerDc, result);
// End active dialog
await innerDc.endDialog();
break;
}
default:
}
case DialogTurnStatus.complete: {
await this.complete(innerDc, result);
// End active dialog
await innerDc.endDialog();
break;
}
default:
}
break;
}
case ActivityTypes.Event: {
await this.onEvent(innerDc);
break;
}
case ActivityTypes.Invoke: {
// Used by Teams for Authentication scenarios.
await innerDc.continueDialog();
break;
}
default: {
await this.onSystemMessage(innerDc);
}
break;
}
case ActivityTypes.Event: {
await this.onEvent(innerDc);
break;
}
case ActivityTypes.Invoke: {
// Used by Teams for Authentication scenarios.
await innerDc.continueDialog();
break;
}
default: {
await this.onSystemMessage(innerDc);
}
}

return Dialog.EndOfTurn;
return Dialog.EndOfTurn;
}
} catch (err) {
await innerDc.context.sendActivity({
type: ActivityTypes.Trace,
text: err.message
});
return await innerDc.endDialog();
}
}

Expand Down

0 comments on commit 4247b60

Please sign in to comment.