Tested on:
Dynamics 365 CE version 9.1, PSA solution version 3.6, Unified Interface
Edit 9.9.2019:
Features described in this blog post have been deprecated and the Flow on this post no longer works as originally intended. Refer to this post for announced changes.
My previous post about Creating To-Do tasks from D365 PSA’s Resource Assignments with Flow got me thinking about some of the other collaboration tools we have on the Office side. This led me to think about the possibilities Planner has to offer in conjunction with managing projects with Dynamics 365 Project Service Automation. Would Planner be a good tool for managing a project’s tasks on a more granular level? PSA doesn’t really offer good tools for collaborating on project task level, but then again that’s what all the different Office tools are about, right?
I decided to give this a try to see what it would take to create a Planner plan with a bucket and tasks when a Project is created in PSA. Pulling this off definitely wasn’t as easy as creating and updating records in the CDS world. Before we dissect the Flow that creates a Planner plan, bucket and tasks, I want to give kudos to MVP Timo Pertilä for his article How to provision team with Flow in which he describes the process of registering an Azure AD Application. That same has to be done prior to building the Flow covered on this blog post. Personally I chose to register an app using the App registrations (Preview) experience. Additional information about that can be found here.
If you are new to Microsoft Graph, this video about Planner APIs by Principal Program Manager Eray Chou can also be helpful. A third resource I want to mention is a blog post about the Do until action in Flow by MVP Kent Weare. Kudos to Kent as well. His post helped me figure out a way around a limitation in a Flow action for Planner.
A Planner plan, bucket and tasks from a PSA Project with Flow
Our goal is short and simple: When a Project with Project Tasks is created in PSA, a Planner plan with a bucket and tasks matching those in PSA are created. While our goal is simple, there are several small details that have to be taken into consideration when building the Flow. I’ll share these details at the end of the article together with an image of the entire Flow.
Step 1: Firing the Flow off and initial actions
The Flow is fired off when a new Project record is created. The Get User actions will get the ID of user who is creating the Project record and firing off the Flow. The user’s UPN (User Principal Name) is needed later in the Flow. In this example that will match a user’s email.
Step 2: Variables
The next step in the Flow is to initialize variables for the Project’s name and for details related to the created Azure AD Application. A variable containing the Project’s name is needed in a Do until loop later on. The variables related to the AAD Application are used in the HTTP GET and POST actions in the next step.
Step 3: Creating an Office 365 Group
Next, an Office 365 Group will be crated. It’s worth mentioning that creating an Office 365 Group also automatically create a Planner plan (Edit 9.9.2019: A Planner plan is not automatically created. Refer to this article by Microsoft’s Brian Smith. Changes described in the article affect the Flow described in this post). The JSON for an O365 Group is pretty simple but I did spend a few moments considering what a good value for mailNickname would be. As my instance has a custom field for a Project Number, created with MVP Jonas Rapp‘s Auto Number Manager for XrmToolBox, I decided to use a value in that field as a mailNickname.
The HTTP POST action will use the variables from the previous step. A delay of some seconds is needed until a group is actually created. I tried the Flow without the delay several times and desirable results were not produced until a delay was used. You can try different values; I ended up with 30 seconds to play it safe.
A HTTP GET action will get us the ID of the group that was just created. I’ve used mailNickname to identify the group I’m looking for. The URI of the HTTP GET is:
https://graph.microsoft.com/v1.0/groups?$filter=mailNickname eq ‘@{triggerBody()?[‘d2d_projectnumberc’]‘
Next, the JSON returned by the HTTP GET will be parsed. When a Compose action with dynamic content for an id is chosen, Flow wants to create an Apply to each. This is fine for us. We now have the Id for the group that was just created.
The final step related to the newly created O365 Group is to add the user firing off the Flow as both an Owner and a Member to the group. To add the user as an owner, a HTTP POST action will be used. The Body for the request can be found from Microsoft’s documentation here. The URI used in the action is:
https://graph.microsoft.com/v1.0/groups/@{outputs(‘Compose_from_Parse_JSON_to_get_ID’)}/owners/$ref
Step 4: Do until
This is a step I spent the most time with. The List my plans action action refuses to include the created Planner plan before the plan is opened in a browser. In other words before we can create a bucket and tasks, we have to first open the plan so that we can get its Title and ID. If you know another way to pull this off, I would love to hear alternative approaches.
To go around the limitation of the List my plans action, a Do until loop gives some solace. When the variable named PlanBody (Initialize variable – Project’s name for Planner plan’s body from step 2) contains the name of the created Project, the loop is exited. Using the List my plans, Parse JSON, Compose, and Set variable actions, the loop is exited when a Planner plan is opened.
The Do until is one of the most mysterious actions I’ve used in Flow to date. There are several posts in the community about it but it wasn’t until I read MVP Kent Weare‘s post about the action that I understood how it works. I won’t go through the details as Kent explains them very clearly on his post. I encourage you to read through it here. Huge Kudos to Kent! I adapted what Kent describes on his post to this Flow and came up with a way to get the desired results. The values for Count, Timeout and Delay are up to you. These depend on your processes and ways of working.
Step 5: Creating a bucket and tasks
We’re approaching the finish line. The Do until loop is now history and a List my plans action will now contain the Planner plan that was created together with the O365 Group. After listing plans, Parse JSON is used, followed by a Compose for both the title of the plan and the ID of the plan. If the output from Compose for title contains the name of the Project, a Scope is entered in which a bucket and tasks are created.
The Create a bucket uses the ID from the Compose for ID action. Project Tasks are listed next with the List records action. This way we have a list of all the Project Tasks that are related to the Project that was created.
The Apply to each action is fairly straightforward. A Get records action gives us access to the Project Task entity’s fields. The Create a task action then creates Planner tasks based on what Project Tasks are created.
The Start Date Time and Due Date Time fields caused a headache. CDS stores date&time values as UTC and getting them just right in Planner tasks took some figuring out. This part was more trial and error and I ended up with a conclusion that on a Planner task’s Due Date Time, dynamic content from a Project Task‘s Due Date field can be used. However the Planner task’s Start Date Time field refused to work with dynamic content from a Project Task‘s Start Date field. Growing inpatient, I didn’t investigate this further as a simple expression adding 1 day to the original date value on the Project Task solved the issue:
addDays(body(‘Get_Project_Tasks’)?[‘msdyn_scheduledstart’],1,’yyyy-MM-dd’)
Flow considerations
Earlier on this article, I mentioned that there were several small details that I ended up spending time on. These details are:
- It takes some seconds for an Office 365 Group to be created. This is why there is a 30 second delay in the Flow.
- A Planner plan is automatically created when Flow creates an O365 Group.
- A user should be set both as an Owner and a Member in the created O365 Group. Creating an O365 Group with Flow doesn’t automatically make a user an owner of the created plan.
- Planner APIs don’t support Application permissions. A HTTP GET action will result in an Unauthorized error.
- The output of the List my plans action doesn’t include any plans that have not been opened by the user in whose context the Flow is running. This is why a Do until is used. During this loop, the user has to open the Planner plan that has been created so that the Flow will exit the loop and create a bucket and tasks. If the loop times out, a bucket and tasks will not be created.
- The List my plans action will only produce results for the user who has created the Flow. Even if another user is added as an owner of the Flow, the List my plans action will still only return results for the maker of the Flow. This is due to the connection that is created to Planner when the Flow is initially created.
- If the Flow has a Scope of User, it will only fire off when the maker of the Flow creates a new record. Adding another user as an owner doesn’t change this. Flow has parity with “classic CRM workflows” in this sense.
- The Flow doesn’t create tasks consistently if a List records action for Project Tasks is immediately after the trigger. This is because it takes a bit of time for PSA to create a WBS with tasks on it. The bigger the WBS, the longer it takes. Consider using a delay before listing Project Tasks, if your WBS has hundreds of tasks.
As always, this Flow can be downloaded from the TDG Power Platform Bank here. Remember that you need to create an AAD app and set values to the variables. I hope this blog post gets you forward with using Planner as a tool for more granular task management for PSA’s Projects.
Disclaimer:
All my blog posts reflect my personal opinions and findings unless otherwise stated.
Hi, for the plan creation issue – how about doing it the other way around:
Create the plan first through the api, this will then trigger group creation as part of the Create plannerPlan.
You can then wait for the group id based on title, etc.
Just a thought – haven’t tested it yet.