Generate organizational structure with the Graph API and markmap in 10 minutes

From time to time, I was thinking about how to generate an organizational hierarchy with the Graph API. Visualization of the organizational structure makes it easier to identify relationships between users inside the organization.

Thanks to the Graph API, getting relationships between users is easy. The challenge is how to visualize relationships between managers and users.

My idea was to find a simple way for describing the relationships between users inside the organization and let some tool generate a chart for me. I've decided to try the markmap.

Markmap

Markmap is a combination of Markdown and mindmap. It parses Markdown content and extracts its intrinsic hierarchical structure and renders an interactive mindmap, aka markmap.

The easiest way to use the markmap is to load your Markdown content into the demo page.

Another option is to use the markmap command line tool

$ npx markmap-cli markmap.md

or if you want to install the package

$ npm install -g markmap-cli
$ markmap markmap.md

Example of the Markdown file

---
markmap:
  colorFreezeLevel: 2
---
# NHL
## Eastern Conference
### Metropolitan
- Carolina Hurricanes
- Columbus Blue Jackets
- New Jersey Devils
- New York Islanders
- New York Rangers
- Philadelphia Flyers
- Pittsburgh Penguins
- Washington Capitals
### Atlantic
- Boston Bruins
- Buffalo Sabres
- Detroit Red Wings
- Florida Panthers
- Montréal Canadiens
- Ottawa Senators
- Tampa Bay Lightning
- Toronto Maple Leafs
## Western Conference
### Central
- Arizona Coyotes
- Chicago Blackhawks
- Colorado Avalanche
- Dallas Stars
- Minnesota Wild
- Nashville Predators
- St. Louis Blues
- Winnipeg Jets
### Pacific
- Anaheim Ducks
- Calgary Flames
- Edmonton Oilers
- Los Angeles Kings
- San Jose Sharks
- Seattle Kraken
- Vancouver Canucks
- Vegas Golden Knights

and its visualization

Organizational structure

When enumerating users in the Graph API, you can use the $expand query parameter to return a manager for the users:

GET /users?$expand=manager

To return only specific properties of the manager, use nested $select:

GET /users?$expand=manager($select=id,displayName)

Solution

Let's combine all together and create a simple application that uses the Graph .NET SDK to generate a markdown file with the organization structure.

Create an application in Azure AD

  1. Register an application in Azure AD

  1. Create a new client secret

  1. Add the User.Read.All application permission

  1. Grant admin consent

  1. Copy tenant id, client id and client secret

.NET application

  1. Create a console application and add Microsoft.Graph and Azure.Identity NuGet packages
  2. Create an instance of GraphServiceClient
var clientSecretCredentials = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredentials);
  1. Get all users and include id of the manager
var response = await graphClient.Users.GetAsync(x =>
{
    x.QueryParameters.Select = new[] { "id", "displayName", "givenName", "mail", "jobTitle", "officeLocation", "city", "department" };
    x.QueryParameters.Expand = new[] { "manager($select=id)" };
});

var users = new List<User>();
var pageIterator = PageIterator<User, UserCollectionResponse>.CreatePageIterator(graphClient, response, (user) =>
{
    users.Add(user);
    return true;
});

await pageIterator.IterateAsync();

note: Use a filter to exclude guest users, room calendars, and other entities you don't want to include in the chart.

  1. Group users by a manager
var managerUsers = users.Where(x => x.Manager != null).GroupBy(x => x.Manager.Id);
  1. Create a StreamWriter and write some header lines of the markmap file
using var file = new StreamWriter(outputPath);
await file.WriteLineAsync("---");
await file.WriteLineAsync("markmap:");
await file.WriteLineAsync("  colorFreezeLevel: 6");
await file.WriteLineAsync("---");
await file.WriteLineAsync("# Organization");
  1. Iterate through all users, starting with users without a manager
foreach (var manager in users.Where(x => x.Manager == null))
{
    await file.WriteLineAsync($"## {manager.DisplayName}");
    await WriteUsersAsync(manager.Id, 0);
}

async Task WriteUsersAsync(string managerId, int level)
{
    var mgUsers = managerUsers.FirstOrDefault(x => x.Key == managerId);
    if (mgUsers == null || !mgUsers.Any())
    {
        return;
    }
    var indent = new string(' ', level);
    foreach (var item in mgUsers)
    {
        await file.WriteLineAsync($"{indent}- {item.DisplayName}");
        await file.WriteLineAsync($"{indent}  {item.JobTitle} ({item.Department})");
        await WriteUsersAsync(item.Id, level + 2);
    }
}

Build and run the console app. The 'hardest' part is done.

View markmap

Now, open the demo page and copy the content of generated Markdown file.

Conclusion

The markmap provides the capability to easily and interactively visualize Markdown files. The Graph API simplifies a way to retrieve data to be visualized. Combination of the Graph API and the markmap allows us to quickly visualize the organizational structure.

The source code can be found in the GitHub repo.

0
Buy Me a Coffee at ko-fi.com
An error has occurred. This application may no longer respond until reloaded. Reload x