Skip to content

ResumeDialog is not called after resumption with adaptivedialog (#5426) #5427

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 1 commit into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions libraries/Microsoft.Bot.Builder.Dialogs.Adaptive/AdaptiveDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -653,22 +653,35 @@ protected async Task<DialogTurnResult> ContinueActionsAsync(DialogContext dc, ob
// from the stack and we want to detect this so we can stop processing actions.
var instanceId = GetUniqueInstanceId(actionContext);

// Initialize local interruption detection
// - Any steps containing a dialog stack after the first step indicates the action was interrupted. We
// want to force a re-prompt and then end the turn when we encounter an interrupted step.
var interrupted = false;

// Execute queued actions
var actionDC = CreateChildContext(actionContext);
while (actionDC != null)
{
// Continue current step
// DEBUG: To debug step execution set a breakpoint on line below and add a watch
// statement for actionContext.Actions.
var result = await actionDC.ContinueDialogAsync(cancellationToken).ConfigureAwait(false);

// Start step if not continued
if (result.Status == DialogTurnStatus.Empty && GetUniqueInstanceId(actionContext) == instanceId)
DialogTurnResult result;
if (actionDC.Stack.Count == 0)
{
// Call begin dialog on our next step, passing the effective options we computed
// Start step
var nextAction = actionContext.Actions.First();
result = await actionDC.BeginDialogAsync(nextAction.DialogId, nextAction.Options, cancellationToken).ConfigureAwait(false);
}
else
{
// Set interrupted flag
if (interrupted && !actionDC.State.TryGetValue(TurnPath.Interrupted, out _))
{
actionDC.State.SetValue(TurnPath.Interrupted, true);
}

// Continue step execution
result = await actionDC.ContinueDialogAsync(cancellationToken).ConfigureAwait(false);
}

// Is the step waiting for input or were we cancelled?
if (result.Status == DialogTurnStatus.Waiting || GetUniqueInstanceId(actionContext) != instanceId)
Expand Down Expand Up @@ -712,6 +725,7 @@ protected async Task<DialogTurnResult> ContinueActionsAsync(DialogContext dc, ob
// Apply any local changes and fetch next action
await actionContext.ApplyChangesAsync(cancellationToken).ConfigureAwait(false);
actionDC = CreateChildContext(actionContext);
interrupted = true;
}

return await OnEndOfActionsAsync(actionContext, cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,16 +230,18 @@ public abstract class InputDialog : Dialog
public override async Task<DialogTurnResult> ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
var activity = dc.Context.Activity;
if (activity.Type != ActivityTypes.Message)

// Interrupted dialogs reprompt so we can ignore the incoming activity.
var interrupted = dc.State.GetValue<bool>(TurnPath.Interrupted, () => false);
if (!interrupted && activity.Type != ActivityTypes.Message)
{
return Dialog.EndOfTurn;
return EndOfTurn;
}

var interrupted = dc.State.GetValue<bool>(TurnPath.Interrupted, () => false);
var turnCount = dc.State.GetValue<int>(TURN_COUNT_PROPERTY, () => 0);

// Perform base recognition
var state = await this.RecognizeInputAsync(dc, interrupted ? 0 : turnCount, cancellationToken).ConfigureAwait(false);
var state = await RecognizeInputAsync(dc, interrupted ? 0 : turnCount, cancellationToken).ConfigureAwait(false);

if (state == InputState.Valid)
{
Expand Down
9 changes: 9 additions & 0 deletions libraries/Microsoft.Bot.Builder.Dialogs/SkillDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ public override async Task<DialogTurnResult> BeginDialogAsync(DialogContext dc,
/// return value.</remarks>
public override async Task<DialogTurnResult> ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default)
{
// with adaptive dialogs, ResumeDialog is not called directly. Instead the Interrupted flag is set, which
// acts as the signal to the SkillDialog to resume the skill.
if (dc.State.TryGetValue<bool>(TurnPath.Interrupted, out bool interrupted) && interrupted)
{
// resume dialog execution
dc.State.SetValue(TurnPath.Interrupted, false);
return await this.ResumeDialogAsync(dc, DialogReason.EndCalled, cancellationToken: cancellationToken).ConfigureAwait(false);
}

if (!OnValidateActivity(dc.Context.Activity))
{
return EndOfTurn;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,7 @@ public override async Task<string> CreateSkillConversationIdAsync(
// Create the storage key based on the SkillConversationIdFactoryOptions.
var conversationReference = options.Activity.GetConversationReference();

string skillConversationId = string.Join(
"-",
options.FromBotId,
options.BotFrameworkSkill.AppId,
conversationReference.Conversation.Id,
conversationReference.ChannelId,
"skillconvo");
var skillConversationId = Guid.NewGuid().ToString();

// Create the SkillConversationReference instance.
var skillConversationReference = new SkillConversationReference
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,12 @@ public async Task AdaptiveDialog_TopLevelFallbackMultipleActivities()
await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer);
}

[Fact]
public async Task AdaptiveDialog_ParentBotInterruption()
{
await TestUtils.RunTestScript(_resourceExplorerFixture.ResourceExplorer);
}

[Fact]
public async Task TestBindingTwoWayAcrossAdaptiveDialogs()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
{
"$schema": "../../../tests.schema",
"$kind": "Microsoft.Test.Script",
"dialog": {
"$kind": "Microsoft.AdaptiveDialog",
"id": "AdaptiveDialog",
"recognizer": {
"$kind": "Microsoft.RegexRecognizer",
"intents": [
{
"intent": "dialoga",
"pattern": "(?i)dialoga"
},
{
"intent": "dialogb",
"pattern": "(?i)dialogb"
}
]
},
"triggers": [
{
"$kind": "Microsoft.OnIntent",
"intent": "dialoga",
"actions": [
{
"$kind": "Microsoft.BeginDialog",
"dialog": {
"id": "dialogA",
"$kind": "Microsoft.AdaptiveDialog",
"recognizer": {
"$kind": "Microsoft.RegexRecognizer"
},
"triggers": [
{
"$kind": "Microsoft.OnBeginDialog",
"actions": [
{
"$kind": "Microsoft.TextInput",
"allowInterruptions": true,
"property": "dialog.value",
"prompt": "DialogA Prompt"
},
{
"$kind": "Microsoft.SendActivity",
"activity": "DialogA: ${dialog.value}"
}
]
}
]
}
}
]
},
{
"$kind": "Microsoft.OnIntent",
"intent": "dialogb",
"actions": [
{
"$kind": "Microsoft.BeginDialog",
"dialog": {
"$kind": "Microsoft.AdaptiveDialog",
"id": "dialogB",
"recognizer": {
"$kind": "Microsoft.RegexRecognizer"
},
"triggers": [
{
"$kind": "Microsoft.OnBeginDialog",
"actions": [
{
"$kind": "Microsoft.TextInput",
"allowInterruptions": true,
"property": "dialog.value",
"prompt": "DialogB Prompt"
},
{
"$kind": "Microsoft.SendActivity",
"activity": "DialogB: ${dialog.value}"
}
]
}
]
}
}
]
},
{
"$kind": "Microsoft.OnIntent",
"intent": "Santa",
"actions": [
{
"$kind": "Microsoft.SendActivity",
"activity": "I love you santa."
}
]
},
{
"$kind": "Microsoft.OnUnknownIntent",
"actions": [
{
"$kind": "Microsoft.SendActivity",
"activity": "In None..."
}
]
}
],
"autoEndDialog": false,
"defaultResultProperty": "dialog.result"
},
"script": [
{
"$kind": "Microsoft.Test.UserSays",
"text": "dialoga"
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "DialogA Prompt"
},
{
"$kind": "Microsoft.Test.UserSays",
"text": "dialogb"
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "DialogB Prompt"
},
{
"$kind": "Microsoft.Test.UserSays",
"text": "testb"
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "DialogB: testb"
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "DialogA Prompt"
},
{
"$kind": "Microsoft.Test.UserSays",
"text": "testa"
},
{
"$kind": "Microsoft.Test.AssertReply",
"text": "DialogA: testa"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ public class SkillConversationIdFactoryTests
[Fact]
public async Task SkillConversationIdFactoryHappyPath()
{
ConversationReference conversationReference = BuildConversationReference();
var conversationReference = BuildConversationReference();

// Create skill conversation
string skillConversationId = await _skillConversationIdFactory.CreateSkillConversationIdAsync(
var skillConversationId = await _skillConversationIdFactory.CreateSkillConversationIdAsync(
options: new SkillConversationIdFactoryOptions
{
Activity = BuildMessageActivity(conversationReference),
Expand All @@ -53,6 +53,36 @@ public async Task SkillConversationIdFactoryHappyPath()
Assert.Null(deletedConversationReference);
}

[Fact]
public async Task IdIsUniqueEachTime()
{
var conversationReference = BuildConversationReference();

// Create skill conversation
var firstId = await _skillConversationIdFactory.CreateSkillConversationIdAsync(
options: new SkillConversationIdFactoryOptions
{
Activity = BuildMessageActivity(conversationReference),
BotFrameworkSkill = this.BuildBotFrameworkSkill(),
FromBotId = _botId,
FromBotOAuthScope = _botId,
},
cancellationToken: CancellationToken.None);

var secondId = await _skillConversationIdFactory.CreateSkillConversationIdAsync(
options: new SkillConversationIdFactoryOptions
{
Activity = BuildMessageActivity(conversationReference),
BotFrameworkSkill = this.BuildBotFrameworkSkill(),
FromBotId = _botId,
FromBotOAuthScope = _botId,
},
cancellationToken: CancellationToken.None);

// Ensure that we get a different conversationId each time we call CreateSkillConversationIdAsync
Assert.NotEqual(firstId, secondId);
}

private static ConversationReference BuildConversationReference()
{
return new ConversationReference
Expand Down