A Microsoft Teams Messaging Extension with Authentication and access to Microsoft Graph III

A Microsoft Teams Messaging Extension with Authentication and access to Microsoft Graph III

I recently started to deep-dive in Microsoft Teams development and I believe it will become a big thing especially around “integration”. But what’s one of the main points when it comes to integrate systems, it’s all about authentication. This is missing in the very basic starter documentation and that’s for reasons. Outside the SPFx world it’s not one of the easiest tasks.

My use case scenario is:
Assume you have some documents out there that need a review from time to time and those that are “due” you wanna share inside Microsoft Teams. So a user can select inside his channel from all retrieved due documents to post them as a card into the current channel. From there you can view them and even confirm the review.

Search Based Messaging Extension to review documents

This is a little series. In this third part part we will have a look how to deal with the search parameters.

As we can see from aboves screenshot so far we were only retrieving all documents matching our query but omitting the existence of the search field. Let’s see how we can use that.

First a short look into our manifest:

"commands": [
        {
          "id": "documentReviewMessageMessageExtension",
          "title": "Document Review Message",
          "description": "Add a clever description here",
          "initialRun": true,
          "parameters": [
            {
              "name": "parameter",
              "description": "Description of the parameter",
              "title": "Parameter"
            }
          ],
          "type": "query"
        }
      ]

“initialRun”: true means that the command is immediately executed. In part II in our OnQuery function we had something like this:

if (query.parameters && query.parameters[0] && query.parameters[0].name === "initialRun") {
            // initial run

   ...
} else {
            // the rest
   ...        
}

As we didn’t really care for the difference in Part II we were doing exactly the same in both branches of the If which of course is … but you see it’s detectable if it’s the initialRun (without parameter, in case enabled by manifest) or not.

So let’s modify our code a bit from Part II. In case of the “initialRun” we still want to retrieve all relevant documents from Graph but this time we will also store them to a local variable. And in case we have no “initialRun” we will simply use the parameter value and and filter our documents from the variable with the retrieved parameter value:

public async onQuery(context: TurnContext, query: MessagingExtensionQuery): Promise<MessagingExtensionResult> {
        const attachments: MessagingExtensionAttachment[] = [];
        const adapter: any = context.adapter;
        const magicCode = (query.state && Number.isInteger(Number(query.state))) ? query.state : '';        
        const tokenResponse = await adapter.getUserToken(context, this.connectionName, magicCode);

        if (!tokenResponse || !tokenResponse.token) {
            // There is no token, so the user has not signed in yet.
            // Omitted for brevity (see Part II)        
        }
        let documents: IDocument[] = [];
        if (query.parameters && query.parameters[0] && query.parameters[0].name === "initialRun") {
            const controller = new GraphController();
            const siteID: string = process.env.SITE_ID ? process.env.SITE_ID : '';
            const listID: string = process.env.LIST_ID ? process.env.LIST_ID : '';
            documents = await controller.getFiles(tokenResponse.token, siteID, listID);
            this.documents = documents;
        }
        else {
            if (query.parameters && query.parameters[0]) {
                const srchStr = query.parameters[0].value;
                documents = this.documents.filter(doc => 
                    doc.name.indexOf(srchStr) > -1 ||
                    doc.description.indexOf(srchStr) > -1 ||
                    doc.author.indexOf(srchStr) > -1 ||
                    doc.url.indexOf(srchStr) > -1 ||
                    doc.modified.toLocaleString().indexOf(srchStr) > -1 
                );
            }            
        }
        documents.forEach((doc) => {
            const today = new Date();
            const nextReview = new Date(today.setDate(today.getDate() + 180));
            const minNextReview = new Date(today.setDate(today.getDate() + 30));
            const card = CardFactory.adaptiveCard(
                {
                   // Create card for each document.
                   // Omitted for brevity (see Part II)
                });            
            const preview = {
                   // Create preview for each document.
                   // Omitted for brevity (see Part II)            };
            attachments.push({ contentType: card.contentType, content: card.content, preview: preview });
        });
        
        return Promise.resolve({
            type: "result",
            attachmentLayout: "list",
            attachments: attachments
        } as MessagingExtensionResult);        
    }

I reduced the code a bit as the Graph and AdaptiveCard stuff is still the same than in Part II. You can also watch the full code in my github repository.

Now our solution works with both an “initialRun” but also a further filtered run as you can see from the screenshots:

Markus is a SharePoint architect and technical consultant with focus on latest technology stack in Microsoft 365 Development. He loves SharePoint Framework but also has a passion for Microsoft Graph and Teams Development.
He works for Avanade as an expert for Microsoft 365 Dev and is based in Munich.
In 2021 he received his first Microsoft MVP award in M365 Development for his continuous community contributions.
Although if partially inspired by his daily work opinions are always personal.

7 thoughts on “A Microsoft Teams Messaging Extension with Authentication and access to Microsoft Graph III

  1. Dear Markus

    I am using your blog to create a search messaging extension in Teams. Everything works great, however I couldnt’t figure out one thing that when an user performs an action from the adaptive card, like in your case “Reviewed” button, I want to update the adaptive card with the status like “The document has been reviewed by this user”, so that everybody in the channel aware that this user reviewed and no other user clicks on the Review button again.

    Could you please guide me on how to achieve this?

    Thanks
    Chandra

    Like

    1. Hi Chandra,
      thanks for your great and substantial question. I did not caover that so far to keep my post/series simple but came across this a bit.
      Unfortunatey I am a bit “away” from the topic caus I am currently “off” for parental leave
      BUT
      what I know is essential in updating an adaptive card:
      The card must be posted by same account than the one updating it.
      So if a user posted it and another user presses Reviewed this is an issue afair.
      If you ensure that the post as well as the update is done by your BOT account you have in the backend it should work and that should be the best solution.
      Hope this helps to fulfill your requirement
      Markus

      Like

Leave a comment