Skipping prompt steps leads to later prompts not waiting for input
See original GitHub issueVersion
4.10.3
Describe the bug
When working with nested dialogs I have found that if a dialog that is branched off from the main dialog and uses waterfall steps has a step skipped, in my case a prompt step(file/choice), then the steps that follow do not wait for their respective inputs from the user.
To Reproduce
In my project I have a main Dialog that is registered with my DI on startup
// My second Dialog
services.AddSingleton<SecondDialog>();
// The Dialog that will be run by the bot.
services.AddSingleton<MainDialog>();
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddSingleton<IBot, MyBot<MainDialog>>();
and sent to my MyBot<T> : ActivityHandler where T : Dialog
How I start my main Dialog from MyBot(With Parameters)
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var messageObj = turnContext.Activity;
if (messageObj.Text.ToUpper().Trim() == "Run Second Dialog".ToUpper())
{
var dialogSet = new DialogSet(DialogStateProperty);
dialogSet.Add(Dialog);
var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
await dialogContext.BeginDialogAsync(Dialog.Id, new SecondDlgRunInfo
{
a = new List<Guid>(),
b = new List<string>(),
c = new List<Guid>()
}, cancellationToken);
await ConversationState.SaveChangesAsync(dialogContext.Context, false, cancellationToken);
}
}
My MainDialog
public class MainDialog : ComponentDialog
{
private readonly UserState _userState;
private readonly IStatePropertyAccessor<UserProfile> _userProfileAccessor;
public MainDialog(UserState userState, AuthenticationDialog authDlg, SecondDialog secondDlg)
: base(nameof(MainDialog))
{
_userState = userState;
_userProfileAccessor = userState.CreateProperty<UserProfile>("UserProfile");
AddDialog(authDlg);
AddDialog(secondDlg);
AddDialog(new WaterfallDialog("MainWaterFallDialog", new WaterfallStep[]
{
InitialStepAsync,
FinalStepAsync,
}));
InitialDialogId = "MainWaterFallDialog";
}
private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userProfile = await _userProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);
if (!userProfile.Valid)
{
return await stepContext.BeginDialogAsync(nameof(AuthenticationDialog), userProfile, cancellationToken);
}
else if (stepContext.Options is SecondDlgRunInfo info)
{
info.UserId = userProfile.UserId;
return await stepContext.BeginDialogAsync(nameof(SecondDialog), info, cancellationToken); ;
}
//More Logic
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userInfo = (UserProfile)stepContext.Result;
if (userInfo != null)
{
var accessor = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
await accessor.SetAsync(stepContext.Context, userInfo, cancellationToken);
}
return await stepContext.EndDialogAsync(null, cancellationToken);
}
}
My Second Dialog where the issue happens
public class SecondDialog : ComponentDialog
{
private const string DlgInfo = "value-dlgInfo";
public SecondDialog() :
base(nameof(SecondDialog))
{
AddDialog(new WaterfallDialog("AutomationRunWaterfallDialog", new WaterfallStep[]
{
UserEnabledStepAsync,
AskChoiceStep1Async,
Choice1StepAsync,
AskChoice2StepAsync,
Choice2StepAsync,
FileStepAsync,
FileChoiceStepAsync,
ConfirmStepAsync,
ConfirmChoiceStepAsync
}));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
AddDialog(new AttachmentPrompt(nameof(AttachmentPrompt)));
InitialDialogId = "SecondDlgWaterfallDialog";
}
private async Task<DialogTurnResult> UserEnabledStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
//Get User data
if (user.Disabled)
{
//User Diabled Prompt
string message = @"Your account has been Diabled or Expired.
Please contact your administrator to rectify this.";
await CardResponses.SendUnauthorizedCardAsync(stepContext.Context, cancellationToken, message);
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
return await stepContext.NextAsync(cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> AskChoiceStep1Async(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
bool haschoice = false;
if(haschoice)
{
//Waits fine
return await stepContext.PromptAsync(nameof(ChoicePrompt), new PromptOptions
{
Choices = ChoiceFactory.ToChoices(choice),
Prompt = ChoicesToCard(choice),
Style = ListStyle.None, // We're displaying the choices ourselves so we don't want ChoicePrompt to do it for us
});
}
else
{
////////ISSUE////////
//After this the Choice2StepAsync does not wait for input from AskChoice2StepAsync
return await stepContext.NextAsync(new FoundChoice { Value = "###"}, cancellationToken: cancellationToken);
}
}
private async Task<DialogTurnResult> Choice1StepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync(new Activity { Type = ActivityTypes.Typing }, cancellationToken);
var response = (FoundChoice)stepContext.Result;
//Do some parsing of result
if (valid)
{
input.val = response.Value;
return await stepContext.NextAsync(cancellationToken: cancellationToken);
}
else if (response.Value.ToUpper() == "Cancel".ToUpper())
{
await CardResponses.SendCardNotification(stepContext.Context, cancellationToken, "Run Automation Cancelled", "This conversation will now end...");
await CardResponses.SendHelpCardAsync(stepContext.Context, cancellationToken);
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
}
private async Task<DialogTurnResult> AskChoice2StepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync(nameof(ChoicePrompt), new PromptOptions
{
Choices = ChoiceFactory.ToChoices(secondChoices),
Prompt = CreateHeroCardActivity(secondChoices),
Style = ListStyle.None, // We're displaying the choices ourselves so we don't want ChoicePrompt to do it for us
});
}
private async Task<DialogTurnResult> Choice2StepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// do some validation and end the dialog if failed else next step
return await stepContext.NextAsync("", cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> FileStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
bool HasFileUpload = false;
if (HasFileUpload)
{
return await stepContext.PromptAsync(nameof(AttachmentPrompt),
new PromptOptions
{
Prompt = MessageFactory.Text($"Please upload the required files?"),
}, cancellationToken: cancellationToken);
}
////ISSUE////////
//After this the ConfirmChoiceStepAsync does not wait for input from ConfirmStepAsync
return await stepContext.NextAsync(new List<Attachment>(),cancellationToken: cancellationToken);
}
private async Task<DialogTurnResult> FileChoiceStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync(new Activity { Type = ActivityTypes.Typing }, cancellationToken);
List<Attachment> attachments = (List<Attachment>)stepContext.Result;
if (attachments.Count())
{
string message = @"You did not supply a file, this conversation will now end...";
await CardResponses.SendCardNotification(stepContext.Context, cancellationToken, "No Files", message);
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
else
{
//download file
return await stepContext.NextAsync("", cancellationToken: cancellationToken);
}
}
private async Task<DialogTurnResult> ConfirmStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var card = new HeroCard
{
Title = "Confirmation",
Text = "Everything looks good, do you want to continue?",
Buttons = new List<CardAction>
{
new CardAction(ActionTypes.PostBack, "Yes", null, "Yes", "Yes", "Yes"),
new CardAction(ActionTypes.PostBack, "No", null, "No", "No", "No"),
}
};
return await stepContext.PromptAsync(nameof(ChoicePrompt), new PromptOptions
{
Choices = ChoiceFactory.ToChoices(new List<string> { "Yes", "No" }),
Prompt = MessageFactory.Attachment(card.ToAttachment()) as Activity,
Style = ListStyle.None, // We're displaying the choices ourselves so we don't want ChoicePrompt to do it for us
});
}
private async Task<DialogTurnResult> ConfirmChoiceStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var response = (FoundChoice)stepContext.Result;
await stepContext.Context.SendActivityAsync(new Activity { Type = ActivityTypes.Typing }, cancellationToken);
//Finish dialog
return await stepContext.EndDialogAsync(cancellationToken: cancellationToken);
}
}
Expected behavior
I would expect that if I go to a next step, and still supply a valid result type that the following prompt would all wait as expected.
Screenshots
None
Additional context
I’m using this in a Service Fabric Stateless Service
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (2 by maintainers)
Top Results From Across the Web
java - Why is my program skipping a prompt in a loop?
I assume that it is waiting for input at input.nextLine() but the prompt does not appear. My guess is that there is a...
Read more >Bypass the yes/no prompt in 'apt-get upgrade'
But I really want to be able to just type in my alias and let it do its thing and not have to...
Read more >Run a command without making me wait
Before running the command, you can append & to the command line to run in the background: long-running-command &. After starting a command, ......
Read more >7.17 — std::cin and handling invalid input
This means that instead waiting for us to enter an operation, the input prompt is skipped, and we get stuck in an infinite...
Read more >Getting ChatGPT to stop and wait for user input
If you're giving a prompt that has a complex list of steps, the first of which is asking the user for a prompt,...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
@v-kydela I have re-produced the issue here to mirror my project as closely as possible without all the external/internal api calls.
There are a few dialogs in here, but the main one to look at is DoThingsDialog.
To reproduce follow the dummy authdialog by sending the bot a message. Once authenticated send “help” to the bot and you will have a card presented with options, choose the “Do Things” option.
Follow the dialog prompts, when the prompt asks if you want to upload a file, choose no, from there the file prompts are skipped and a confirmation prompt is presented, if you choose to upload a file, the confirmation prompt works fine, if you choose to not upload a file then the final step of the DoThingsDialog does not actually wait for you to confirm or decline with the presented confirmation.
SkipIssue.zip
Thanks @v-kydela
You can close the issue.