question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Skipping prompt steps leads to later prompts not waiting for input

See original GitHub issue

Version

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:closed
  • Created 3 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
Deon-vcommented, Oct 1, 2020

@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

0reactions
Deon-vcommented, Oct 5, 2020

Thanks @v-kydela

You can close the issue.

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found