Creating a team, a channel and a D365 PSA tab in Microsoft Teams using Flow

Edited on: 16.9.2019.

This blog post is about enabling collaboration with Microsoft Teams when a Project is created in Dynamics 365 Project Service Automation. The Flow covered on this post will check whether an Account already has a Teams team or not. If a team is present, a new channel for a Project and a PSA tab are created. If a team is not present, the Flow does a bit more and creates an Office 365 Group, a team, a channel and a PSA tab. I will update this post at a later date for scenarios around the new Project service, when it is generally available. But for now, let’s dissect the Flow!

The initial steps of the Flow

The Flow fires off when a Project is created and the msdyn_customer field contains data. This way a team and/or a channel is only created when an Account is related to a Project. An Account Name is needed later on when creating a team so a get action for accounts is needed. A user is needed when a group is created later on in the Flow. I’ve included both the Office 365 Users action and the Azure AD users action for educational purposes as the outputs of the actions differ slightly.

1. Initial actions.

As we move down the Flow, we will hit an action that requires the URL of the environment we are using. There are a few ways to get URL and the initial scope (Demo Scope – indexOf and substring for environment id) is for educational purposes. It shows an alternative approach to getting the URL of an environment.

2. Demo Scope to get environment’s URL.

The next scope is full of compose actions. The first two are used to get the environment’s URL both with and without https://. The Get User record action’s output conveniently contains an environment URL. Using first, last, split in an expression gets the URL with just a single compose. The expressions in the first two compose actions are:
first(split(last(split(body(‘Get_User_record’)?[‘@odata.id’],”)),’/api’))
first(split(last(split(body(‘Get_User_record’)?[‘@odata.id’], ‘https://’)), ‘/api’))

3. Compose actions with expressions to get environment’s URL.

Next, we need the environment id. That is also needed down the line when a tab with PSA is added to a channel. Composing the URL and the id of the environment makes Application Lifecycle Management easier as the values don’t need to be entered manually, when moving the Flow in a solution from development to test and then to production. The expression used in the compose is:
body(‘Get_User_record’)?[‘organizationid’] 

4. Environment id.

The values for the next compose actions require a bit of research. Some values are unique to an environment so be sure the change them, when moving the Flow between environments.

  • App id: The D365 CE or PSA app id is easy to find by opening the model-driven app in question and copying the app id from the URL. This value is unique to an environment.
  • View id and view name: The id for a view that you want to display in the app on the tab is also unique to an environment. An OOTB view called All Projects is used.
  • Channel name: The channel’s name equals the Project’s name.
  • teamsAppId for Dynamics 365: This value only needs to be extracted once as it’s not unique to an environment. The value can be extracted with Graph Explorer and a HTTP GET request. Image 6 illustrates the request’s results. The request used is https://graph.microsoft.com/v1.0/appCatalogs/teamsApps. See this article by Microsoft for more information.
  • Compose body: Compose JSON. Used later on when adding the D365 CE app to a team. The dynamic value used in the JSON is from the previous compose, that holds the teamsAppId.
5. Compose actions.
6. Getting teamsAppId with Graph Explorer.

Next, all teams are listed and a condition checks whether a team exists for the account that is related to the Project. For the condition to work, the string version for the value in needed. If the body of the list includes the name of the account, the condition is true and only a channel and tab with PSA are created. If the condition is false, a group, a team, a channel and a tab with PSA are created. The expression used in the condition is:
string(body(‘List_Teams_to_see_if_the_Account_has_a_Team’))

7. List teams and the condition.

The true side of the condition

To create a channel, a team id is needed. To get our hands on it, a filter array is used and the id is then composed in the apply to each. The name of the channel will be based on the Project’s name. Let’s look at an error handling step for duplicate channel names next.

8. Filter array on true side of condition.

If a channel matching a Project’s name already exists, a new channel can’t be created. Instead of just canceling the Flow, an email notification will be sent. The notification will run after the Create a Channel on yes side has failed.

When a channel is created, a compose action will capture its id. An HTTP POST action will then be used to add the Dynamics 365 app to the existing team. If the app has already been added, the action will fail. This is why the delay that follows runs both when the HTTP POST has succeeded and failed. The delay of 15 seconds is something that you can play around with to see if the delay can be smaller or even non existent. I’ve added it for a bit of extra safety.

9. Handling errors and adding the D365 app to a team.

The next compose JSON is where all the initial compose actions come into play. Adding the D365 app to a channel’s tab required quite a bit of “trial and error”. Kudos to my colleague MVP Vesa Nopanen for helping me figure out the JSON! The easy way out would be to simply skip all the dynamic values but my goal was to make it easy to move the Flow from one environment to another. The image below illustrates all the outputs of the initial compose actions.

10. JSON to add D365 app to a channel’s tab.

The next action on the true side of the condition is to add a tab with D365/PSA to a channel. Note that at the time of writing this, adding a tab only works with Microsoft Graph API Beta! After this, there is one final thing that needs to be done and that is to add the user firing off the Flow to the account’s Office 365 Group. This can be placed earlier in the Flow. I’ve put it at the end because I forgot to add the actions earlier. To get the id of a group, an HTTP GET is needed. The filter that is being used is:
https://graph.microsoft.com/v1.0/groups?$filter=mailNickname eq ‘ replace(body(‘Get_Account_record’)?[‘name’], ‘ ‘, ”) ‘

Next, the JSON from the HTTP GET is parsed. The schema used can be found here. Using a compose for a group’s id throws an apply to each but there should only be a single value that is returned. Based on the group id, the user can be added to the group. These actions wrap up the Flow on the true side. Next, let’s dissect the false side.

Edit 16.9.2019

Dynamic content for the get action has been replaced with a replace expression. A mailNickname can’t have spaces. If an account’s name has them, its mailNickname is nevertheless composed without any spaces in it.

11. Adding a tab with D365/PSA to a channel.
11.1. Getting a group id and adding a user to the group. Edited on 16.9-2019.

Edit: 16.9.2019

If a user already belongs to the group in question, the Add user to group on yes side action will fail. To display the Flow as successful, a terminate action can be placed after the apply to each (terminate actions can’t be inside a loop). Configure run after for the action has been set so that the terminate action only runs after the apply to each has failed.

11.2. Terminate after apply to each to display the Flow as successful.

The false side of the condition

The first action on the false side is to compose a JSON to create a Teams team. The community is full of JSON examples for creating a team so replace the JSON with something that fits your specific requirements. After the compose, an Office 365 Group is created.

{
“memberSettings”: {
“allowCreateUpdateChannels”: true
},
“messagingSettings”: {
“allowUserEditMessages”: true,
“allowUserDeleteMessages”: true
},
“funSettings”: {
“allowGiphy”: true,
“giphyContentRating”: “strict”
}
}

Edit on 13.9.2019: The action that crates a group has an incorrect dynamic value in the Mail Nickname field. If an account consist of a name with a space (for example Beer Company), the Flow will fail. The value Mail Nickname can’t have any spaces. A replace expression can be used to concatenate the string:
replace(body(‘Get_Account_record’)?[‘name’], ‘ ‘, ”)

12. JSON for Teams team and creating an Office 365 Group.

If the creation of a group fails, an error handling step sends a notification email. Group creation can fail if multiple Projects for the same Account are created simultaneously. Concurrency Control for the Flow’s trigger can be adjusted to compensate for this.

13. Concurrency Control for the Flow’s trigger.

When a group has been created, a compose stores its id. The next action is to add the user firing off the Flow to the created group. The JSON that was composed (image 12) will be used in the body of the HTTP PUT action. This creates a new team in the group. The delay is again something you can play around with to see if a smaller count will work for you. The last actions in scope 1 are to list all teams and to then use a filter array to filter out a team that matches the account related to the Project.

14. Actions in scope 1.

The actions in scope 2 are fairly straightforward and partly match those on the true side. Filtering the List Teams with a filter array throws an apply to each. We will then get our hands on a team id that is composed. A channel is then created and its id composed. An HTTP POST is used to add D365/PSA to the created team. There is no need for error handling for the delay action as on the false side a team is always created. The compose for JSON and the HTTP POST for tab match the ones on the true side.

15. Actions in scope 2.

The whole Flow can be seen in the image below. As always, the Flow can be downloaded from the TDG Power Platform Bank here.

16. The flow expanded. Edited on 16.9.2019.
Disclaimer:
All my blog posts reflect my personal opinions and findings unless otherwise stated.

Leave a Reply

Your email address will not be published. Required fields are marked *