microsoft / botframework-sdk

Bot Framework provides the most comprehensive experience for building conversation applications.
MIT License
7.48k stars 2.44k forks source link

Disabling buttons in Adaptive cards in Teams after user selection - C# #5472

Closed Vigneshramkumar closed 5 years ago

Vigneshramkumar commented 5 years ago

Hello Team,

Greetings for the day. We are developing Chatbot using Bot framework V4 in C#. Also we have integrated it with MS Teams. Currently we are using adaptive cards for getting inputs from user. Frequently users are trying to click the buttons in adaptive cards from old messages in bot. Is there any way to disable buttons from an adaptive card response in MS Teams after user clicked on any button in the card response. So that we shall avoid such issues. Thanks in advance for your response.

image

Regards, Vignesh.

CoHealer commented 5 years ago

@stevkan, please assist

v-kydela commented 5 years ago

@Vigneshramkumar - In any channel, you can have a bot ignore old cards by having the bot remember the card by storing some metadata associated with the card. In order to distinguish between cards, you will have to inject some metadata into the submit action that's different for each card.

However, the Teams channel is special because it allows you to edit and delete messages. Do you want to leave the old cards in place so that they're still visible but without clickable buttons? Or do you want to delete the old cards from the conversation entirely?

I recommend leaving a comment or reaction on Michael Richardson's AdaptiveCardPrompt project. Demonstrating that customers need this functionality will help it get released sooner.

Vigneshramkumar commented 5 years ago

@v-kydela - Thanks for your response. We want to have the old messages in the Teams conversation, but we need to disable the buttons in the cards (will not allow the users to click the old cards buttons again)

EricDahlvang commented 5 years ago

There is currently no "disabled" setting in the schema for Adaptive Card buttons. Teams does support UpdateActivity types though.

You can use UpdateActivity as part of the reply logic for the Adaptive Card, and change the previous card to exclude the buttons (replace them with text or something). This seems to be best or only way to accomplish a goal of not allowing "the users to click the old cards buttons again".

v-kydela commented 5 years ago

@Vigneshramkumar - Have you had a chance to try Eric's suggestion involving UpdateActivityAsync?

Vigneshramkumar commented 5 years ago

@EricDahlvang and @v-kydela - Thanks for your suggestion. Sorry, I was not there for past two days. I will try the above one.

I will list down the actions needs to be done for removing the button in the previous adaptive card, kindly correct me if i am wrong.

  1. For the first time when displaying the original adaptive card, we need to have that conversation ID and message ID to be saved.
  2. Once the user clicks on that button, we need to create our new card to be displayed without that button and call UpdateActivityAsync method for updating the previous one.

For example: Conversation ID and message ID for the displayed adaptive card is Con and Message. Then the updateActivityAsync will be as follows. connector.Conversations.UpdateActivityAsync(Con, Message, NewCardReply).

Kindly correct me in case of any misunderstanding.

Regards, Vignesh.

v-kydela commented 5 years ago

@Vigneshramkumar - You are mostly correct, but it's actually a little easier than what you're describing.

For the first time when displaying the original adaptive card, we need to have that conversation ID and message ID to be saved.

You need to save the ID from the resource response when you send the activity and use that as the message ID, but you don't need to save the conversation ID. Every message your bot receives from the user will contain the conversation ID you need, including the message that gets sent when the user clicks on a submit action. Refer to my latest blog post for more info: https://blog.botframework.com/2019/07/02/using-adaptive-cards-with-the-microsoft-bot-framework/

connector.Conversations.UpdateActivityAsync(Con, Message, NewCardReply)

You can actually call UpdateActivityAsync through the turn context directly, and it will automatically extract the conversation ID and the activity ID from whatever you put in the activity object that you pass to it.

If you want to modify a card so that it just loses its submit actions, you'll also need to save whatever information you need to recreate the card when you're updating the activity. You might be able to get the card from the saved activity ID using a REST API call, but I like to avoid extra calls so I'm giving each of my adaptive cards a "card ID" that I can use to load that card's JSON at any time. I'm storing the card ID and the activity ID as a tuple using a state property accessor like this:

public static IStatePropertyAccessor<(string CardId, string ActivityId)> CardStateAccessor { get; internal set; }

When I send the activity containing the card I make sure to save the resource response ID (which is used as an activity ID) along with the card ID. Note that in order to get a useful resource response, your activity must only contain the card attachment and the text property must be null.

var response = await turnContext.SendActivityAsync(reply, cancellationToken);
await BotUtil.CardStateAccessor.SetAsync(turnContext, (cardId, response.Id), cancellationToken);

Now the tricky part is updating the activity in response to the user clicking the submit action. If you want to make sure the card's input fields don't change then you'll need to repopulate them in your updated card using the activity's value property, but that's optional. Here's some sample code that iterates through the card to create a modified card and then sends it to Teams as an update:

private async Task HandleSubmitActionAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
    var activity = turnContext.Activity;

    if (activity.ChannelId == Channels.Msteams)
    {
        var savedActivity = await BotUtil.CardStateAccessor.GetAsync(turnContext, () => (null, null), cancellationToken);

        if (savedActivity.ActivityId != null )
        {
            var oldCard = BotUtil.CreateAdaptiveCard(savedActivity.CardId);
            var newCard = oldCard.DeepClone();
            var payload = JObject.FromObject(activity.Value);

            foreach (var item in oldCard.Descendants().Where(descendant => descendant.Type == JTokenType.Object))
            {
                var elementType = (string)item["type"];
                var inputId = (string)item["id"] ?? string.Empty;
                var newCardEquivalent = newCard.SelectToken(item.Path);

                // Optionally fill in the card's input values with what the user already entered
                if (elementType?.StartsWith("Input.") == true && payload.ContainsKey(inputId))
                {
                    newCardEquivalent["value"] = payload[inputId];
                }

                // Remove submit actions
                if (elementType == "Action.Submit")
                {
                    newCardEquivalent.Remove();
                }
            }

            var attachment = new Attachment()
            {
                ContentType = BotUtil.CreateContentType(activity),
                Content = newCard,
            };

            var update = activity.CreateReply(activity.Text);

            update.Attachments = new List<Attachment> { attachment };
            //update.Conversation = activity.Conversation;
            update.Id = savedActivity.ActivityId;

            await turnContext.UpdateActivityAsync(update, cancellationToken);
        }
    }
}
CoHealer commented 5 years ago

@Vigneshramkumar, resolving as closed. If this does not resolve you issue please comment .

yuriisanin commented 4 years ago

@CoHealer @v-kydela Looks like it's doesn't work with Telegram adaptive cards. code:

var totalyNewCard = cardBuilder.CreateCard(orderResult.Data);

var reply = MessageFactory.Attachment(new Attachment
 {
      ContentType = AdaptiveCard.ContentType,
      Content = totalyNewCard
 });

reply.Id = savedActivity.ActivityId;
var response = await turnContext.UpdateActivityAsync(reply, cancellationToken);

Method returned ResourceResponse.Id that equals to saveActivity.ActivityId, but nothing changed in Telegram. Project: .NET Core 2.2 Bot Framework 4.6.0-preview1

yuriisanin commented 4 years ago

@Vigneshramkumar, resolving as closed. If this does not resolve you issue please comment .

https://github.com/microsoft/botframework-sdk/issues/5472#issuecomment-547803337