0

My code has a simple card carousel which has action button like below:


 actions = [
        {
            "type": "Action.Submit",
            "title": "Qualify",
            "data": { "action" : "qualify_lead" }
        },
        {
            "type": "Action.OpenUrl",
            "title": "Retire",
            "url": "{viewUrl}"
        },
        {
            "type": "Action.ShowCard",
            "title": "Add Note",
            "card":   this.noteCard(item.LeadId, "leads")
        }
       ]

I am having a method to handle qualify_lead action like below

async qualifyLead(context:any){
        console.log("in qualifyLead:" + JSON.stringify(context.activity))
        await context.updateActivity(this.successCard('Lead is qualified'))
    }

All I am doing on purpose is to replace entire carousel with a simple text message. But it fails with error:

Error: BotFrameworkAdapter.updateActivity(): missing activity.id

Where do i get this ?

I am using google firebase for this and the wrapper code is like below

const {
    TurnContext,
    TeamsActivityHandler,
    CardFactory,
    AttachmentLayoutTypes,
    ActionTypes
} = require('botbuilder');

class TeamsConversationBot extends TeamsActivityHandler {
    constructor() {
        super();
        this.leadState = 
          this.conversationState.createProperty('leadCarouselState');

        this.onMessage(async (context:any, next:any) => {
            TurnContext.removeRecipientMention(context.activity);
            let msg = context.activity.text
            const action = context.activity.value
            let objNum = ''
            let keyword = ''


            if(msg === undefined && action === undefined)
                msg  = 'help'
            else if(msg !== undefined){
                msg = msg.trim().toLowerCase()
                if(msg.indexOf("help") > -1)
                    msg = 'help'
                else{

                   if(msg.startsWith('lead')){

                        msg = 'lead'
                    } 
                }
            }

            switch (msg) {
                case 'lead':
                        await this.lead(context, userKey, platform)
                        break;
                case 'qualify_lead':
                        await this.qualifyLead(context)
                        break;
            }
            await next();
        });
    }

Moblize IT
  • 1,140
  • 2
  • 18
  • 44

2 Answers2

1

I'm not sure exactly what this.successCard('Lead is qualified') does, but presumably it returns an Activity. To my knowledge, in order for this Activity to replace another one, you need to set it's Id property to match the previous message. That means that, when you send the previous message (i.e. the card), you need to capture the reference that's returned from the send method on the context (e.g. into bot state), and then use it for this new activity.

Hilton Giesenow
  • 9,809
  • 2
  • 10
  • 24
  • sorry yes successCard is just returning an activity. not sure at what point and where do i store the activity id of the card i send? – Moblize IT Feb 04 '20 at 21:07
  • 1
    So instead of passing "this.successCard('Lead is qualified')" directly into "context.updateActivity", you need to first store it into a variable, so you can set the id on it before calling "context.updateActivity". However, that's the easy part - the hard part, are you're asking, is storing the activity id because you store it on the previous interaction, and need it on this interaction. As a result, you'll need to implement state management for your bot, which you might not have running already. – Hilton Giesenow Feb 05 '20 at 09:24
1

As I explained in my answer to your other question, you need to save the activity ID in bot state and then apply it to the update that you're sending. The Bot Framework can't update an activity unless you tell it which activity to update, and you do that using an activity ID.

This was the part that saves the ID:

const dict = await this.carouselState.get(turnContext, {});

dict[batchId] = {
    [KEYACTIVITYID]: response.id,
    [KEYLEADS]: leads
};

And this was the part that applies it to the updated activity:

            const update = this.createCarousel(batchId, leads);
            update.id = info[KEYACTIVITYID];
Kyle Delaney
  • 11,616
  • 6
  • 39
  • 66
  • mine is a firebase functions code. how the above variables like dict will persist between first card sent and then coming again to the flow on hitting action button? In your above answer what is response ? where is it coming from? only thing that comes in is turnContext – Moblize IT Feb 05 '20 at 21:39
  • @MoblizeIT - Did you read any of the links I gave you about bot state? – Kyle Delaney Feb 06 '20 at 17:49
  • now i did and got a lot of clarity. However, in the constructor i have a line to create a porerty like this this.conversationState.createProperty('carouselState'); when i try to deploy the code i get error TypeError: Cannot read property 'createProperty' of undefined which means this.conversationState is not defined. I have updated my question to add that line for you to see what i am doing – Moblize IT Feb 07 '20 at 06:52
  • @MoblizeIT - You can find examples of how to construct bot state objects in the [samples](https://github.com/microsoft/BotBuilder-Samples/blob/master/samples/javascript_nodejs/13.core-bot/index.js#L69) – Kyle Delaney Feb 07 '20 at 17:28
  • it says // For local development, in-memory storage is used. // CAUTION: The Memory Storage used here is for local bot debugging only. When the bot // is restarted, anything stored in memory will be gone. so what is the option for non local in my case? – Moblize IT Feb 09 '20 at 07:40
  • @MobilizeIT - As you read in the links I gave you, it's typical to use blob storage or Cosmos DB – Kyle Delaney Feb 10 '20 at 17:29
  • so basically i dont see a point of a git sample using a option that is meant for local testing only. ideally, i need to persist the state myself between two messages of the same chat session. Kind of confused why so much circus of creating a map etc. if ultimately i need a db or my own storage to persist the data – Moblize IT Feb 12 '20 at 05:34
  • @MoblizeIT - Once you've created a storage resource in Azure, it's very easy to replace the bot's memory storage class with a different storage class. But seeing as the question of how to implement storage is well-documented and unrelated to your question, are you going to accept my answer? – Kyle Delaney Feb 12 '20 at 18:05
  • does teams persist a session for the same user in a continuous chat? i am looking if i can keep the activity id in the session object rather using a db just for this purpose? – Moblize IT Feb 21 '20 at 22:14
  • @MoblizeIT - Once you receive the activity ID from the resource response, you could immediately update the activity so that the submit action data contains the activity ID. You should post new questions in their own posts, however. It's a good idea to accept answers so that people can see you're a good user. – Kyle Delaney Feb 24 '20 at 21:11
  • i really need to be sure the answer really makes sense to me instead of keep posting multiple questions for the exact same thing. otherwise, other users might think i am a spammy poster. – Moblize IT Feb 25 '20 at 03:11
  • when i am posting the activity containing activity array, i can capture activity id. however, how can i update immediately without user clicking any of the submit action button? – Moblize IT Feb 25 '20 at 03:13
  • 1
    You just call Update Activity after calling Send Activity on the same turn. If this is difficult to understand then go the easy route and just use bot state like I explained. I do not recommend updating an activity on the same turn that you sent it, but you asked for a way to do that. Please refer to the documentation in order to learn how to use the Bot Framework: https://learn.microsoft.com/en-us/azure/bot-service/bot-service-overview-introduction – Kyle Delaney Feb 25 '20 at 16:45