5. September 2018
MS Planner ex- and import of plans
Microsoft Planner is a nice project planning tool which still has some shortcomings but also a lot of benefits and we are giving it a try. However there are two topics somewhat hampering our progress: To the best of my knowledge there is no way to backup a plan and also no possibility to have something like a „template“ plan which you could then use to populate new plans with buckets and tasks. The latter is the number two topic if you look at the top ideas in the planner uservoice forum while the former has no real traction there, so it might be something only we need. But then, with a simple ex- and import functionality both could be addressed: It could be used as template functionality if you do an ex- and immediate import of a plan and it could be used as backup if you take the result of the export and just store it somewhere. Seems easy and straightforward enough, and it actually is:
Planner itself doesn’t have an API but with Microsoft’s general strategy of integration everything in their Graph API, that makes a lot of sense. There you will find a part about planner but be warned that this is currently in beta, which means it is a preview and subject to change. If you are willing to accept that, you basically need to take the following steps:
- Get an application in you Azure AD with the proper permissions
- Authenticate against the AAD to get an auth token
- Use the Graph API to identify the group and plan you want to export
- Again use the Graph API to export the plan, buckets, tasks and so on
- If you also want to import, once more use the Graph API to import the same data into a new group and plan
If implemented the tool in C# / .NET Core using VS Code, so if you want to look at the source, try it yourself or even send my a pull request, just go to https://github.com/tfenster/plannerexandimport.
The details, I: The AAD application
The AAD application is used to connect to the Graph API. For a quickstart on the topic, see this nice documentation. It will need the permission to read groups (Group.Read.All or „Read all groups“) and write to groups (Group.ReadWrite.All or „Read and write all groups“), but only as delegated permissions, which means that the application can only see and create information and data which the logged in user would also see and be able to create. The application has an ID which you need to provide when authenticating with AAD.
The details, II: Authentication to get a token
As I wrote, the application itself can’t access anything but instead only gets delegated permissions of the logged in user. That means that we also need to log in and get a token which together with the application will allow access to the Graph API. There are a number of possible ways to achieve that but as I wanted my app to be as capable of being automated and as platform independent as reasonably possible, I decided to go with a console based approach: The sample here shows you how it is done and as I only adapted that to my needs, I won’t go into too much detail here (see https://github.com/tfenster/PlannerExAndImport/blob/master/Planner.cs#L205-L231 if you want the code). The basic approach from a user perspective is that you get a URL and a token, you open that URL, enter the token and allow the application to work with your credentials. That works very reliably.
The details, III: Get groups and plans
Now we have everything in place to access the Graph API, so let’s get groups and plans: First we need an HTTP client that we connect to https://graph.microsoft.com/beta/groups and add the auth token as bearer token in the authorization request header (see https://github.com/tfenster/PlannerExAndImport/blob/master/Planner.cs#L191-L203). Then we can query the endpoint for groups, e.g. by name. I’ve implemented this by using the amazing „Paste JSON as Code“ extension for VS Code which allows you to copy some JSON and then paste C# code you can use to get (de)serialize it. I implemented a generic GraphRepsonse class (see https://github.com/tfenster/PlannerExAndImport/blob/master/JSON/GraphResponse.cs) to further reduce the need to create specific code for the different requests and responses. With that in place, the implementation is basically two lines: ask the user for the name and then do a GET request with some filtering (see https://github.com/tfenster/PlannerExAndImport/blob/master/Planner.cs#L144-L145). As we might have multiple matches, the user gets a list of results and can select the right one. With the id of that group, we then query the plans endpoint, get a list of all plans (see https://github.com/tfenster/PlannerExAndImport/blob/master/Planner.cs#L165) and then again ask the user to select the right one.
The details, IV: Export buckets, tasks, etc.
With all that preparation done we can now finally do what we set out to in the beginning: Export the content of our plan. The buckets can be queried through „plans/<plan-id>/buckets“ and the tasks with „plans/<plan-id>/tasks“. We also need the task details from „tasks/<task-id>/details“ for the check lists inside of tasks (see https://github.com/tfenster/PlannerExAndImport/blob/master/Planner.cs#L26-L54). As I find it easier to handle in json that way, I put everything into a tree structure with tasks below buckets and task details below tasks. After that we now have our plan with all the content as json and can either export it and use it as backup or provide it as input for an import.
The details, V: Import everything into another plan
If the user decides to also import everything, he is again presented with the same workflow to get the target plan (it needs to pre-exist, the tool is not able to create new plans). The imports are POST and PATCH requests (see https://github.com/tfenster/PlannerExAndImport/blob/master/Planner.cs#L57) to the same endpoints with two important things to note:
- Importing fails if you just throw the exported json at the endpoints because some data can’t be imported at all. I’ve implemented something very similar to the solution explained here so that some fields are never serialized and therefore never deserialized and imported. Others like OrderHint need to be reset to the default, in this case “ !“ (see e.g. https://github.com/tfenster/PlannerExAndImport/blob/master/Planner.cs#L77). Because of that we need to reverse the order of items before import because everything is added at the top which would lead to reversely ordered lists if we didn’t reverse the order before (see e.g. https://github.com/tfenster/PlannerExAndImport/blob/master/Planner.cs#L70).
- I saw some errors when I tried to set the task details too quickly, because the task itself might not be created yet. The proper solution for that probably would be to check for the existence of the task, but as timing is not that critical in this case, I decided to just wait for two seconds (see https://github.com/tfenster/PlannerExAndImport/blob/master/Planner.cs#L105). Seems a bit hacky, but works quite well
With that you know how to ex- and import plans from Planner and my tool will easily allow you to do so. You will need to provide the name of the Azure tenant as well as the client id for the application. Then, if no previously cached token exists, the application will proceed with authorization or just reuse the existing token. After that you will go through the steps as outlined here.
A followup post will explain my build and release pipeline from GitHub repo to Docker image in the Docker hub but as there is a need to update that before sharing it, that might take a bit of time.