Recipe for delivering App Powered Tasks in Planner.

Planner App Powered Tasks Part 2: Recipe for Cooking up a Planner App-Powered Task Solution


Introduction

This blog post is part of a series discussing Microsoft Planner’s App-Powered Task feature.

In the first blog post, I provided an example of what they are and why you want to use them. 🙂 If you have not read the first post, I invite you to start from there.

In the previous post, we introduced App-Powered Tasks and why you want to use them.

In this post, we will discuss the architecture and process of building an App-Powered Task solution for Microsoft Planner.

An example architecture of an App Powered Task solution looks like the following

The ingredients for the solution are

  • A Microsoft Entra ID Application will be used to create the business scenario plan and manipulate the business scenario planner tasks.
  • An API to create, get, update and complete the planner tasks. I am also using this to create the business scenario plan.
  • A Microsoft Teams App that will be used to action the task. In my example, I am using a SPFx teams app to achieve this.
  • A Planner Plan
  • A Team within Microsoft Teams that hosts the Planner Plan.

Anyway, let’s get to work.

First, create a Microsoft Entra ID application.

  • Browse to https://portal.azure.com
  • Open the Microsoft Entra ID application.
  • Click on App Registrations and create a new Entra ID Application.
    • You will need to have at least the Application Developer Entra ID role (check)
    • Give it a great name
    • For this application, it only needs to exist in a single tenant.
  • Ensure that it has the following permissions
    • BusinessScenarioConfig.ReadWrite.All (Delegated)
    • BusinessScenarioConfig.ReadWrite.OwnedBy (Application)
  • Next, create a Client Secret using the Certificates & secrets option.
  • Please make sure you save the Client Secret as we will need that shortly.
  • Click on Overview and record the Client Id as we will need that too.

Now that we have the Microsoft Entra ID Application, the next step is to set up a Microsoft Team.

If you already have a Microsoft Team setup then use this.

Once we have a Microsoft Team to host the Planner plan, we will need to get the identifier for the group.

You can retrieve the Id of the group using the Microsoft Entra ID blade in the Azure Portal.

  • browse to https://portal.azure.com
  • Click on Manage -> Groups -> All Groups
  • Search for the Group.
  • Record the object Id, which is also the Group ID, we will need that again shortly.

Now that we have the Entra ID application and Microsoft 365 Group, next you’ll need the code.

Getting the code

The code can be found in my GitHub repository, there are two parts to the code, which will go into a bit more detail shortly. We have the API which is an Azure Function and the user interface which is a Teams Toolkit created SharePoint Framework webpart. The web part will display the custom task interface for the user.

You can find the GitHub repository here:

Clone the GitHub repository to local your machine.

Get the code running

In this blog post, we are going to run everything locally, so that you can get the code working, debug it, and tweak it so it works for you.

  • First, open the Azure Function Project in Visual Studio 2022.
  • Second, open the Read Receipt SharePoint Framework webpart in Visual Studio Code.

Read Receipt Azure Function Setup

To setup and get the Azure Function working you will need to configure the Azure Function with the following settings so that it can use the Entra ID application that you setup and Microsoft Team.

Open the local.settings.json file and update the file with the following settings:

  • ClientId uses the Client Id for the Microsoft Entra ID application you created.
  • ClientSecret uses the Client Secret that you created previously.
  • TenantId use the directory Tenant Id that is found in the Azure Portal, Entra ID Application as shown below.
  • Next, configure the Group ID using the value you got from the Microsoft Team.
  • BusinessScenarioName – the name of the Business Scenario
  • PlannerName – the name of the Planner Plan
  • SharePointHostName – the prefix of your SharePoint Online URL. e.g acme from acme.sharepoint.com
  • TeamsTaskAppId – the Application ID for your Teams App
  • InDeveloperMode – set to true
  • TestUserPrincipalName – provide your username/email address here for your test user.

In the Repository is a folder called Postman, which contains the Postman Collection that we will use to run the Azure Function functions to set up the Business Scenario Microsoft Planner Plan.

You will need Postman, which can be downloaded from https://www.postman.com/

Once you have downloaded and installed Postman grab the Postman collection that I have created for you. This is found in GitHub, in the Postman folder.

Run the Read Receipt Azure Function To Setup Planner Plan

From Visual Studio 2022.

  • Find the Timer Function called CreateConnectSyncPlanFunction.
  • Put a breakpoint if you want to follow the code.
  • Press F5 to run and debug the Azure Function on your computer.
  • This will create a plan using the configuration settings you specified in the local.settings.json
  • All being well the plan has been created in your Microsoft Team.
  • Please check that it was created ok by going into Planner and looking for the Plan with the name you provided.

Setup the Read Receipt Microsoft Teams App

Now that we have the Plan created, we need to get the user interface setup within Microsoft Teams.

The configuration of the Teams App is using the various env.local, env.dev and env.prod files to manage the app IDs etc. I have left the local and Dev IDs as to the ones that I have been using.

Hopefully, that will be ok, and it will make life a bit easier for you to get the solution up and running.

In order to use these just change the .env.local.txt to .env.local and .env.dev.txt to env.dev. I have removed the tenant Id as you don’t need that and it will get in the way.

The API needs to know these IDs so that when the tasks are created then the right link is provided to the user to take them into our custom user interface.

Use Teams Toolkit to get up and running by doing the following:

  • From VS Code’s Terminal use npm -i to install all the dependencies.
  • Rename the /env/env.local.txt to /env/env.local
  • Rename the /env/env.dev.txt to /env/env.dev
  • Start local provision of the Teams App by
    • From Teams Toolkit Plugin
    • Choose to start the local environment with Teams Workbench (local).

If you have not logged in before you’ll be asked to login into your Microsoft 365.

After a few minutes of compiling, you should have a browser fire up, asking you to open the Teams App.

Now you have the Teams App running we are ready to create a task.

Before that let me just talk about deep linking and Microsoft Teams.

A word about Teams Deep Links

I will talk about the problems that I had with Microsoft Teams Deep Links in the next post but let’s go over what we need to set up for the links to work.

The official documentation on deep link URLs within Microsoft Teams is available on learn.microsoft.com.

However, we need to use a stage link which looks like this:

https://teams.microsoft.com/l/stage/{teamsAppId}/0?context={encodedContextObject}

There are two tokens in the link:

  • {teamsAppId} – this is the application ID for your Microsoft Teams App and can be retrieved from the env.* file.
  • {encodedContextObject} – this is the bit of the Url that does all the work.

Let’s break down the encodedContextObject.

The encodedContextObject is an object that is URI encoded to ensure that there are no invalid characters.

The object looks like this:

{
"appId":"{teamsAppId}",
"entityId":"{teamsTabEntityId}",
"contentUrl":"{teamsAppLinkUrl}",
"name":"{teamsAppTaskTitle}",
"openMode":"modal"
}

The object has the following properties:

  • appId – this is the ID for the Teams App that should be opened by the link.
  • entityId – this is the ID for the tab that should be opened by the link.
  • contentUrl – this is the URL that should be used to render the content and will explain further next.
  • name – the name of the dialog to open.
  • openMode – how should the link be opened; I am using a dialog modal to keep the user in the context of Planner.

The contentUrl is one of the more important of the parameters and its value is the key to getting the contents of the dialog to render properly. Here is the example from the code:

We have two in the code base, one for when we are developing and the other when the application is being used normally.

Development link

var teamsAppDevelopmentLinkUrl = $"https://[yourtenantname].sharepoint.com/_layouts/15/TeamsWorkBench.aspx?componentId=fac21461-7439-49d1-a2ce-f5ab67b67a91&subEntityId={teamsAppExternalObjectId}&teams&personal&forceLocale=en-gb&loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js";

Notice the link which has your SharePoint prefix for example https://acme.sharepoint.com but directs the user to TeamsWorkBench.aspx. This allows you to debug the code. It took me a while to work out this was the URL to use and I took it from the Teams Toolkit configuration file.

Additionally, it has the componentId and subEntityId properties.

  • componentId – this is the tab that should be opened by the link.
  • subEntityId – I have added this because I want to ensure that I know which task to open. This is the external object ID that is created and associated with the business scenario task.

Now for the normal, non-development link

var teamsAppLinkUrl = $"https://[yourtenantname].sharepoint.com/_layouts/15/teamshostedapp.aspx?componentId=fac21461-7439-49d1-a2ce-f5ab67b67a91&subEntityId={teamsAppExternalObjectId}&teams&personal&forceLocale=en-gb&loadSPFX=true";

This is similar to the development version of the link, except that we do not have the TeamsWorkbench.aspx mentioned but rather the teamshostapp.aspx instead. The properties are still the same, componentId and subEntityId as mentioned above.

Now that you understand the different links then we can bring it all together to create the encoded Context Object with the following code.

var teamsAppDevelopmentLinkUrl = $"https://ithinksharepointltd.sharepoint.com/_layouts/15/TeamsWorkBench.aspx?componentId=fac21461-7439-49d1-a2ce-f5ab67b67a91&subEntityId={teamsAppExternalObjectId}&teams&personal&forceLocale=en-gb&loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js";
var teamsAppLinkUrl = $"https://ithinksharepointltd.sharepoint.com/_layouts/15/teamshostedapp.aspx?componentId=fac21461-7439-49d1-a2ce-f5ab67b67a91&subEntityId={teamsAppExternalObjectId}&teams&personal&forceLocale=en-gb&loadSPFX=true";
if (inDevelopmentMode)
{
    teamsAppLinkUrl = teamsAppDevelopmentLinkUrl;
}

var contextObject = $"{{\"appId\":\"{teamsAppId}\",\"entityId\":\"{teamsTabEntityId}\",\"contentUrl\":\"{teamsAppLinkUrl}\",\"name\":\"{teamsAppTaskTitle}\",\"openMode\":\"modal\"}}";

var encodedContextObject = System.Uri.EscapeDataString(contextObject);

// the teams link must use a stage link https://learn.microsoft.com/en-gb/microsoftteams/app-powered-tasks-in-planner#example
var teamsAppTaskLink = $"https://teams.microsoft.com/l/stage/{teamsAppId}/0?context={encodedContextObject}";
var encodedTeamsAppTaskLink = Encode(teamsAppTaskLink);

Finally, we have an Encode function which comes from the Microsoft documentation but removes certain key characters from the link to ensure that when it is created as part of the Business Scenario Task with the Microsoft Graph SDK it is valid.

More information can be found here on the links within the Microsoft Learn App Powered Tasks documentation.

Using the URL to Create Your First Business Scenario Task.

We just need to make some tweaks to the code in our API to make sure it’s sending you to the right place.

To be honest, getting the URL right was one of the hardest parts of getting the solution going! 🙂

However, provided you have setup the local.settings.json correctly with the SharePoint Host Name and Teams App Id, everything should get setup correctly.

Now you understand the structure of the URL, do the following to create your first business scenario task.

  • Open Postman
  • Find the CreateBusinessScenarioTask request in the Postman Collection
  • Click on the Body tab and update the properties to use values which make sense for your environment.

Here is an example from my environment.

The following parameters are required:

  • externalId – The externalId is the unique identifier that is used to find the task. Please be aware that the externalId needs to be unique in your plan.
  • userPrincipalName – the user principal name of your test user. bob@domain.com.
  • contentTitle – the title of the content that needs to be read.
  • contentUrl – the URL to the content that needs to be read.

Click on Send and your Azure Function should be fired and a task returned in the response from Postman.

Additionally, now, you have a Microsoft Planner Plan with an App-Powered Task created. If you browse to the Microsoft Planner Plan using https://planner.cloud.microsoft you’ll see the task in the My Tasks view.

The highlighted part of the screenshot above, is an indicator that the task is an app-powered task.

Try out the task, open it and click “Start task” and see if works, make sure you still have the VS Code debugger running.

All being well you will see the task open up in your debug browser.

Congratulations you have created your first App Powered Task App!

I wanted to give you a bit more information on the deep link URLs as this took me a while to get going and so wanted to share my findings.

How do we use these deep links?

The links are created and associated with a Business Scenario Task through the References property. The links are a bit odd to set up for the first time but after you have done one you will soon get used to the pattern as the Graph SDK and Planner Api uses a similar pattern in several areas. The pattern provides the API with a lot of flexibility and extension points for future Planner features.

Well the code here does the work. Notice line 6 and 7 which create the container for these PlannerExternalReferences the PlannerExternalReference is created and setup the .Type is important as this is used by Planner to note whether the task is App-Powered. The PlannerExternalReference is added using the encoded Teams link plus the PlannerExternalReference object which is added to the AdditionalData property.

 // the teams link must use a stage link https://learn.microsoft.com/en-gb/microsoftteams/app-powered-tasks-in-planner#example
 var teamsAppTaskLink = $"https://teams.microsoft.com/l/stage/{teamsAppId}/0?context={encodedContextObject}";
 var encodedTeamsAppTaskLink = Encode(teamsAppTaskLink);

 // setup the external references used by the teams powered app task.
 var plannerExternalReferences = new PlannerExternalReferences();
 var plannerExternalReference = new PlannerExternalReference();

 plannerExternalReference.Alias = teamsAppTaskTitle;
 plannerExternalReference.Type = "TeamsHostedApp";
 plannerExternalReference.PreviewPriority = " !";
 
 plannerExternalReferences.AdditionalData.Add(encodedTeamsAppTaskLink, plannerExternalReference);

 

 // create a task
 var businessScenarioTask = new BusinessScenarioTask()
 {
     Title = teamsAppTaskTitle,
     PercentComplete = 0,
     StartDateTime = DateTime.Now,
     DueDateTime = DateTime.Now.AddDays(1),
     Target = new BusinessScenarioGroupTarget()
     {
         GroupId = groupId,
         TaskTargetKind = PlannerTaskTargetKind.Group
     },
     BusinessScenarioProperties = new BusinessScenarioProperties
     {
         ExternalObjectId = teamsAppExternalObjectId,
         ExternalBucketId = watchBucketName,
     },
     Details = new PlannerTaskDetails
     {
         
         Description = $"Please read the content, {readReceiptTaskResponse.ContentTitle} and confirm that you have read the content",
         References = plannerExternalReferences
     },
     Assignments = taskAssignments

 };

 await graphClient.Solutions.BusinessScenarios[theBusinessScenario.Id].Planner.Tasks.PostAsync(businessScenarioTask);


We create the business scenario task (please note I have stripped out the other code to assign the task etc to help readability.

Finally, we add the task to the Business Scenario plan using the PostAsync function.

Conclusion

I hope that you found this article useful and got the code up and running. Please reach out and raise any issues within the GitHub Repository and I will look and get back to you or add any fixes that you might discover.

In this blog post we introduced App Powered Tasks with Planner and using a SharePoint Framework Teams App and Azure Function we were able to create a Business Scenario Plan, and tasks and set up a task to open up a Teams App that will allow you to put a custom user interface around the Business Scenario Task.

Teams AI Library Blog Series: Episode #3 – Setup your Teams AI Library Application


Introduction

This blog post is part of a series of posts around delivering a Microsoft Teams app that uses Microsoft Teams Toolkit, Teams AI Library, and Azure AI Services to have an Open AI chatbot reason over Microsoft SharePoint content.

If you have not read the previous posts, I would suggest starting at the first post to get your bearings.

In the previous post, we setup the Azure AI and Azure Open AI infrastructure, so do that first.

Now we are getting to the exciting part which is where we create and set up the Teams AI Library application. This involves the following steps:

  • Create your Teams AI Library Project
  • Setup of Teams AI library.
  • Clone Teams AI Library Extension GitHub Repository.
  • Configure the environment variables.

As mentioned previously the solution is using an existing sample and modifying it to use the Teams AI Extension library that I have put together.

Create your Teams AI Library Project

The Microsoft Teams AI library GitHub repository can be found on GitHub and in this example will build on top of the Twenty Questions Bot and repurpose it for our needs.

The reason we are using this sample is that it gives us the scaffolding, I used the chef bot sample as well as inspiration for this sample which uses a local data source to augment its output.

I will be explaining how to setup the application using Microsoft Visual Studio Code.

So, to start, clone the Repository from within Microsoft Visual Studio Code.

Wait for the Git repository to be cloned and open the folder in Visual Studio Code.

Clone Teams AI Library Extension GitHub Repository

Next, get the code from the GitHub Repository.

Currently, I have not published the code to an NPM registry. I started to configure the code base to publish to a GitHub NPM package host but have not finished the setup yet.

So, clone the GitHub repository to your local machine. In the following example, we are cloning it into your c:\dev\teams-ai-library-extensions folder.

This can be done via Visual Studio Code by doing the following:

For details on how to do this using Git CLI, check out Cloning a repository – GitHub Docs.

Setup your Teams Application

Set up the Microsoft Teams application by doing the following.

With the Microsoft Teams AI Library sample Visual Studio code open do the following:

  1. In the root JavaScript folder, install and build all dependencies
    • cd teams-ai/js
    • yarn install
    • yarn build

  • In a terminal, navigate to the sample root.
cd teams-ai/tree/main/js/samples/04.e.twentyQuestions
  • Duplicate the sample.env in this folder. Rename the file to .env.
  • Add your bot’s credentials and any other related credentials to that file. We will need to use the Azure Open AI configuration. So, keep the AZURE_OPENAI_KEY, AZURE_OPENAI_ENDPOINT variables and fill them in appropriately.
  • Add the following new Environment Variables in to your .env file
    • AZURE_SEARCH_ENDPOINT=
    • AZURE_SEARCH_KEY=
    • AZURE_OPENAI_DEPLOYMENTMODEL=
    • AZURE_SEARCH_INDEXNAME=
    • BOT_ID=
    • BOT_PASSWORD=
  • Update config.json and index.ts with your model deployment name.

Install Teams AI Extension NPM Library

Now that you have your application downloaded with dependencies set up then we need to install the Teams AI Extension module using npm.

Based on that the step to clone the Teams AI Extension library has been cloned to c:\dev\teams-ai-extension then install the Teams AI Azure AI Search Extension into your ChefBot application by doing the following:

  • From VS Code.
  • Open Terminal
  • npm install c:\dev\teams-ai-library-extension\teams-ai-azure-ai-search-datasource –save

This will install the library and once installed we can reconfigure the application to use the Azure AI Search Data Source.

Update the index.ts file with the following steps.

Add the import statement.

import {AzureAISearchDataSource, AzureAISearchDataSourceOptions} from 'teams-ai-azure-ai-search-datasource';

At line 120 add the following code:

const dataSourceOptions: AzureAISearchDataSourceOptions = {

    azureOpenAiEndpoint: process.env.AZURE_OPENAI_ENDPOINT!,

    azureOpenAiKey: process.env.AZURE_OPENAI_KEY!,

    azureOpenAiDeploymentId: process.env.AZURE_OPENAI_DEPLOYMENTMODEL!,

    azureSearchEndpoint: process.env.AZURE_SEARCH_ENDPOINT!,

    azureSearchKey: process.env.AZURE_SEARCH_KEY!,

    azureSearchIndexName: process.env.AZURE_SEARCH_INDEXNAME!,  

};

At line 130 replace the code with this:

let dataSource:AzureAISearchDataSource = new AzureAISearchDataSource('teams-ai', dataSourceOptions);

planner.prompts.addDataSource(

    dataSource

);

This will remove the existing data source which is referencing a locally created vector database.

The last step is to update the prompts in Teams AI Library. The Teams AI library system prompt is augmented using prompts which are held in folders in the ./src/prompts folder.

Each folder within this prompts folder can hold a different configuration. The prompt folder that is being used by the application to configure the AI is managed by the action planner in this example.

You will see the following code:

const planner = new ActionPlanner({

    model,

    prompts,

    defaultPrompt: 'chat'

});

The defaultPrompt setting in this example uses the chat folder.

Now, let’s change the prompt to get the result that we want.

However, before we make any changes, let’s explain a bit about the files in this folder. There are two files to check out:

  • config.json
  • skprompt.txt

The config.json is used to configure the way that the prompt works and allows you to change how the interaction with the Azure Open AI Service. There is not a huge amount of information in the Teams AI Library. However, I have found that changing the type to completion improves the results.

Additionally, you may want to update the temperature. The temperature is a number between 0.0 and 1.0 and controls how precise or creative the results are. 0 is precise and 1 is very creative. The temperature will control whether you get a response as it might be that if you have a temperature of 0 and the bot does not know the answer then it will say so. If you have a temperature of 1.0 then you will always get a result back which will include results which are not factually correct.

The other file is skprompt.txt

This is used to configure your AI and augment the prompt to describe how you want it to behave. There are a lot of interesting ways to configure the prompt and I would recommend having a play but also using GitHub and see what other people are doing.

I have found that having a last sentence that states and uses the following information to formulate your response. For example:

The following is a conversation with an AI assistant called Mr Legal
Mr Legal is an expert in Legal and Law matters and is an assistant that is used by legal fee earners within law firms to help them understand how they can tackle their legal matters.
The voice of Mr Legal should be authorative and professional.

Base your response using the following text and include the citations:

Response Formatter Library

The [responseFormatter.ts](https://github.com/SimonDoy/teams-ai-library-samples/blob/main/teams-ai-app-azure-ai-search/src/responseFormatter.ts) libray is from the ChefBot sample and can be found in my the [Teams AI library sample repository](https://github.com/SimonDoy/teams-ai-library-samples/blob/main/teams-ai-app-azure-ai-search/src/responseFormatter.ts).

Copy and paste this into the index.ts file and add the line to line 25

import { addResponseFormatter } from './responseFormatter';

Add the line to line 145

addResponseFormatter(app);

Save the file and that should be the last coding change.

Configuration of Environment Variables

The environment variables are set in the env file which is found in the root of the project alongside the package.json. The environment variables should be updated using the following information:

  • AZURE_OPENAI_KEY – this is the access key found in your Azure Open AI Service settings.
  • AZURE_OPENAI_ENDPOINT – this is the URL that connects to your Azure Open AI Service.
  • AZURE_SEARCH_ENDPOINT – this is the URL that connects to your Azure AI Search Service.
  • AZURE_SEARCH_KEY – this is the access key found in your Azure AI Search Service settings.
  • AZURE_OPENAI_DEPLOYMENTMODEL – this is the name of the GPT deployment that has been created using the Azure Open AI portal
  • AZURE_SEARCH_INDEXNAME – This is the name of the search index that holds the content that your AI bot is going to use to reason over.

Let’s put it all together

Now, all being well your application will work!

So, start running the application using F5 to start Visual Studio Code deploying and debugging your code.

Wait for the application to spin up your infrastructure and bot service.

Eventually your browser will start and open up Microsoft Teams and prompt you to add the app.

Add the app to Microsoft Teams.

You should be presented with a chat. Use the chatbot and start asking for information that is related to the content that you have stored in the index.

Final Sample Code

To help you get your code running I have provided the Teams AI Library Sample Github Repository to allow you to review your code to the original.

Making tweaks

Now, you will want to try different prompts and tweak them to change the response that comes back from the AI platform.

Conclusion

I hope that you find this useful and can get this setup and working with your own data. The extension is not battle-hardened and I am sure there are plenty of issues that can come up with it but please raise issues via GitHub and if I get time, I will look.

One thing to be aware of is that the SharePoint Indexer for Azure AI Search Service is still in beta and there are issues when documents are deleted and documents are renamed as the index process does not pick up these changes, so be careful.

As an alternative the Azure Blob Storage Indexer has been released and has been made generally available.

I will look at putting in a bonus episode to explain how to configure the Azure Blob Storage Indexer but if you cannot wait, take a look at the Microsoft article, [Azure AI Search How to Index Azure Blob Storage](https://learn.microsoft.com/en-us/azure/search/search-howto-indexing-azure-blob-storage).

Happy code and let me know how you got on.