microsoft / botbuilder-js

Welcome to the Bot Framework SDK for JavaScript repository, which is the home for the libraries and packages that enable developers to build sophisticated bot applications using JavaScript.
https://github.com/Microsoft/botframework
MIT License
685 stars 279 forks source link

Triggering an Action.Execute on an adaptive card sent by a chat bot on Teams mobile apps caused a "Something Went Wrong" error. #4430

Closed ingmaramzan closed 1 year ago

ingmaramzan commented 1 year ago

First of all, I am not sure whether to raise this on Teams', botbuilder's, or adaptive card's repositories on github.

Versions

Package: botbuilder@4.11.0 Nodejs: v16.19.0 Browser: Version 109.0.5414.120 (Official Build) (64-bit) Teams Android Client: 1416/1.0.0/2023012702/0115

Describe the bug

I have a chatbot that functions as a notification bot that sends an adaptive card notification to our users. On the card, the user can reply to the notification by typing in the Input.Text field and then clicking a send button that triggers an Action.Execute, our Nodejs bot will then process the activity by sending notifications to corresponding user and then updating the card with a new card. This works perfectly on Web and Desktop app but in the Teams Android App the card will briefly shows an error message saying "Something went wrong" before our card is finally updated.

These is the card that I send:

{
      "type": "AdaptiveCard",
      "appId": process.env.MicrosoftBotAppId,
      "body": [
        {
          "type": "TextBlock",
          "text": message,
          "wrap": true
        },
        {
          "id": "history_title",
          "type": "TextBlock",
          "text": cardString.pastEvents,
          "wrap": true
        },
        {
          "type": "Container",
          "spacing": "None",
          "items": [
            {
              "id": "last_trail_name",
              "type": "TextBlock",
              "text": lastTrail ? cardString.action(lastTrail.userName, lastTrailAction) : "",
              "spacing": "None",
              // "size": "Small",
              "wrap": true
            },
            {
              "id": "last_trail_comment",
              "type": "TextBlock",
              "text": lastTrail ? `${lastTrail.comment.replace(/<\/?[^>]+(>|$)/g, "")}` : "",
              "spacing": "None",
              // "size": "Small",
              "wrap": true
            },
            {
              "id": "last_trail_date",
              "type": "TextBlock",
              "text": lastTrail ? `${lastTrailDateString}` : "",
              "spacing": "None",
              "isSubtle": true,
              "size": "Small",
              "wrap": true
            }
          ]
        },
        {
          "type": "Container",
          "items": [
            {
              "id": "second_last_trail_name",
              "type": "TextBlock",
              "text": secondLastTrail ? cardString.action(secondLastTrail.userName, secondLastTrailAction) : "",
              "spacing": "None",
              // "size": "Small",
              "wrap": true
            },
            {
              "id": "second_last_trail_comment",
              "type": "TextBlock",
              "text": secondLastTrail ? `${secondLastTrail.comment.replace(/<\/?[^>]+(>|$)/g, "")}` : "",
              "spacing": "None",
              // "size": "Small",
              "wrap": true
            },
            {
              "id": "second_last_trail_date",
              "type": "TextBlock",
              "text": secondLastTrail ? `${secondLastTrailDateString}` : "",
              "spacing": "None",
              "isSubtle": true,
              "size": "Small",
              "wrap": true
            }
          ]
        },
        {
          "id": "comment",
          "isRequired": true,
          "type": "Input.Text",
          "placeholder": cardString.commentPlaceholder,
          "isMultiline": true
        },
        {
          "id": "success-msg",
          "type": "TextBlock",
          "text": success ? cardString.commentSentAlert : "",
          "spacing": "None",
          "isSubtle": true,
          "size": "Small",
          "wrap": true,
          "color": "good"
        }
      ],
      "actions": [
        {
          // "tooltip": isPremium ? cardString.send : cardString.premiumTooltip,
          // "isEnabled": isPremium, 
          "type": "Action.Execute",
          "verb": "comment",
          "title": cardString.send,
          "data": data,
        },
        {
          "type": "Action.OpenUrl",
          "title": cardString.goToTicketButton,
          "url": deeplink
        }
      ],
      "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
      "version": "1.4"
    }

And this is the bot that processed the action:

class Bot extends TeamsActivityHandler {
    constructor() {
      super();

      this.onMembersAdded(async (turnContext: TurnContext, next: () => Promise<any>) => {
        const gettingStartedUrl: string = 'https://www.teamswork.app/gettingstarted';
        const membersAdded = turnContext.activity.membersAdded;
        for (let cnt = 0; cnt < membersAdded.length; cnt++) {
          if (membersAdded[cnt].id !== turnContext.activity.recipient.id) {
            const welcomeMessage = "Welcome message"

            // add conv references
            if (await this.storeConversationReference(turnContext))
              await turnContext.sendActivity(welcomeMessage); // Only send notification if conversation reference store (one time)
          }
        }

        // By calling next() you ensure that the next BotHandler is run.
        await next();
      });
    }
    // Listener for activity e.g. comment activity on notification card
    async onInvokeActivity(turnContext: TurnContext): Promise<InvokeResponse<any>> {
      // Listen for activity invocation and check if the comment action on adaptiveCard has been called
      const activity: Activity = turnContext.activity;
      // context.log('activity: ', activity);
      // turnContext.sendActivity("Hello World!");
      let card: object;

      if (activity.name === "adaptiveCard/action") {
        // context.log("action activity");
        const action = activity.value.action;
        // context.log("action : ", action);
        if (action.verb === "comment") {
          const userName: string = activity.from.name;
          const userId: string = activity.from.aadObjectId;
          const data: IActionData = action.data;
          // context.log('data: ', action.data);
          const date: Date = new Date();
          const tenantId: string = activity.conversation.tenantId;
          const comment: string = data.comment;
          const lang: string = data.lang || "en";

          // assignee Ticket == user commennt
          if ((data.ticket.assigneeId == userId) && (data.ticket.firstTimeResponse == "")) {
            this.updateTicket(data.ticket)
          }
          // Check for ticketId
          if (!!!data.ticket) {
            context.log("No ticketId found, aborting");
            // throw ("No ticketId found, aborting")
          }

          // Construct new audit trail / comment
          let newComment: IAuditTrail = {
            userName: userName,
            userId: userId,
            ticketId: data.ticket.id,
            action: "commented",
            comment: comment,
            date: date,
          }

          // initialize parameters for card to be sent
          const cardContent: ICardContent = {
            userName: userName,
            type: "comment",
            action: "commented",
            comment: comment,
            isRequestor: false,
          }

          // Prepare response message
          const encodedDeeplink: string = constructDeeplink(data.ticket, data.ticketingAppUrl, data.channelId, data.entityId);
          const message: string = constructMessage(data.ticket, data.cardContent, encodedDeeplink, lang);
          const isPremium: boolean = await getPremium(tenantId);
          // const card = invokeSuccessResponse(encodedDeeplink, message, data);       

          // Save audit trail
          const insertCommentEndpoint: string = process.env["InsertCommentEndpoint"];
          try {
            await cosmosService.insertItem(insertCommentEndpoint + encodeQueryParams({ instanceId: data.ticket.instanceId }), { comment: newComment });
          } catch (error) {
            context.log(error);
          }

          // Send request to SendCardNotification API
          try {
            // Notification recipient id array
            let recipientIds: string[] = [];
            if (data.ticket.requestorId)
              recipientIds.push(data.ticket.requestorId)
            if (data.ticket.assigneeId)
              recipientIds.push(data.ticket.assigneeId)

            // If ticket have custom fields get people from them
            if (data.ticket.customFields) {
              // Get instance so we can get customFieldSetting
              const instanceContainer: Container = ticketDatabase.container(process.env["InstanceContainer"]);
              const { resource: instance } = await instanceContainer.item(data.ticket.instanceId, tenantId).read<IInstance>();

              // Create array for user who will receive the notification
              let activePersonaCustomFields: ICustomField[] = [];
              if (instance.customFieldsLeft)
                activePersonaCustomFields.push(...instance.customFieldsLeft);
              if (instance.customFieldsRight)
                activePersonaCustomFields.push(...instance.customFieldsRight);
              activePersonaCustomFields = activePersonaCustomFields.filter((value) => value.type.key === "6_peoplepicker");
              activePersonaCustomFields = activePersonaCustomFields.filter((value) => value.isReceiveNotification);

              if (activePersonaCustomFields.length > 0) {
                for (const fields of activePersonaCustomFields) {
                  if (data.ticket.customFields[fields.id]?.length > 0) {
                    const personas: IPersonaProps[] = data.ticket.customFields[fields.id];

                    for (const element of personas) {
                      recipientIds.push(element.id)
                    }

                  }
                }
              }
            }

            // Remove message sender from recipient list
            recipientIds = recipientIds.filter((id) => id !== userId);

            // Get unique notification recipient array
            const uniqueRecipientIds: string[] = [... new Set(recipientIds)];

            context.log("Sending notifications");
            for (const id of uniqueRecipientIds) {
             await this.sendNotification(cardContent, id, tenantId, data);
            }

            card = NotificationCard(encodedDeeplink, message, data, true, isPremium, lang);
            const cardAttachment: Attachment = CardFactory.adaptiveCard(card);
            const activityPayload = MessageFactory.attachment(cardAttachment);
            activityPayload.id = turnContext.activity.replyToId;

            context.log("Updating cards");
            await turnContext.updateActivity(activityPayload);

          } catch (error) {
            context.log(error)
          }
        }
      }
      const cardRes = {
        statusCode: StatusCodes.OK,
        type: 'application/vnd.microsoft.card.adaptive',
        value: card
      };
      const res = {
        status: StatusCodes.OK,
        body: cardRes
      };
      return res;
    }
  }

It actually does not matter what I do on the card, even if I do nothing and then only return the InvokeResponse, the error still happens.

To Reproduce

Steps to reproduce the behavior:

  1. Create an azure bot using the bot that I put here
  2. Have the bot send the card
  3. On the Teams mobile app open the message sent by the bot
  4. Type any message on the card's text field and then hit the send button
  5. The error happens just before the card is updated

Expected behavior

The card is updated without any error message shown.

Screenshots

WhatsApp Image 2023-02-10 at 17 00 37

ingmaramzan commented 1 year ago

After messing around a bit more with the issue, I think the issue happens when: A user clicks on an Action.Execute button of an adaptive card that has a text field on it. The card is sent by a chatbot and the action is done on Teams android app. It does not matter whether a bot is handling the action or not, the "Something went wrong" message will appear.

I think this issue is more suitable to be raised in the adaptive card repo.

For now I have changed my card and bot to use Action.Submit instead, because it works fine.

ramfattah commented 1 year ago

Thanks @ingmaramzan, I'm looking into this.

ingmaramzan commented 1 year ago

Another thing to note is that I am connected on two devices/clients in desktop and in android.

ramfattah commented 1 year ago

Thanks for the update @ingmaramzan

ramfattah commented 1 year ago

@ingmaramzan, is the issue specific to Teams?

ingmaramzan commented 1 year ago

I haven't checked anywhere else, so I can only say for Teams android.

ramfattah commented 1 year ago

Hi @ingmaramzan ,

I've notified the Microsoft "Teams" team about this issue. Will report back as soon as there are updates. Thanks.

ramfattah commented 1 year ago

Hi @ingmaramzan,

Teams engineers were unable to reproduce this issue.

Tested below NodeJS sample using Adaptive card with Action.Execute: https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-sequential-flow-adaptive-cards/nodejs

Could you please test this sample at your end and let us know if you get the same error for this sample also?

https://user-images.githubusercontent.com/38049078/219520016-13e41b88-5451-44b5-a535-88006fde2a25.mp4

ramfattah commented 1 year ago

Hi @ingmaramzan, any status update on this?

ramfattah commented 1 year ago

closing due to inactivity.