Show your colleagues with Microsoft Graph Toolkit

Show your colleagues with Microsoft Graph Toolkit

One of the common requirements for collaborative apps is showing your colleagues. Here are two ways to do it using Microsoft Graph Toolkit.

The easiest way to connect to Microsoft 365

Using Microsoft Graph Toolkit is the easiest way to connect your app to Microsoft 365. Toolkit's authentication providers simplify letting users to login into your app with their Microsoft 365 account. With the toolkit's components, showing data from Microsoft 365 in your apps comes down to a single line of code.

Microsoft Graph Toolkit contains many components to show data from Microsoft 365. From specialized components like Agenda or People to the generic Get component - there is always a way to retrieve and show the data that you need.

Show your colleagues

One of the common scenarios when building collaborative apps is to show colleagues of the current user. Whether to quickly see related work or to assign a task, seeing others in the team is a pattern we see many apps use.

List of people displayed in the web browser

Since organizational information is typically stored in Microsoft 365, you can get the information about the current user's colleagues using Microsoft Graph. To get this information, you'll need to run 3 Graph calls:

  1. Get information about the current user (/me)
  2. Get information about the current user's manager (/me/manager)
  3. Get information about the current user's manager direct reports (/users/[userId]/directReports)

After you retrieved the current user's manager's direct reports (basically their team), to get their colleagues, you need to exclude the current user from the list. This is exactly why you'd start with getting the information about the current user.

Here are two ways how you'd build this using the Microsoft Graph Toolkit.

Use the Get component

One way to show the colleagues of the current user is to use the Get component to get the necessary information:

<mgt-get resource="/me">
  <template data-type="default">
    {{storeId(this.id)}}
    <mgt-get resource="/me/manager">
      <template data-type="default">
        <mgt-get resource="/users/{{this.id}}/directReports">
          <template data-type="default">
            <mgt-people people="{{excludeMe(this.value)}}" show-max="20"></mgt-people>
          </template>
          <template data-type="loading">
            Loading information about my colleagues...
          </template>
          <template data-type="error">
            The following error has occurred while loading information about my colleagues: {{ this }}
          </template>
        </mgt-get>
      </template>
      <template data-type="loading">
        Loading information about my manager...
      </template>
      <template data-type="error">
        The following error has occurred while loading information about my manager: {{ this }}
      </template>
    </mgt-get>
  </template>
  <template data-type="loading">
    Loading my information...
  </template>
  <template data-type="error">
    The following error has occurred while loading my information: {{ this }}
  </template>
</mgt-get>
<script>
  let meId;
  document.querySelector('mgt-get').templateContext = {
    storeId: id => {
      meId = id;
      return '';
    }
  };
  // /me
  document.querySelector('mgt-get').addEventListener('templateRendered', e => {
    if (e.detail.templateType !== 'default') {
      return;
    }

    // /me/manager
    e.detail.element.querySelector('mgt-get').addEventListener('templateRendered', e1 => {
      if (e1.detail.templateType !== 'default') {
        return;
      }

      // /users/<id>/directReports
      e1.detail.element.querySelector('mgt-get').templateContext = {
        excludeMe: people => people.filter(p => p.id !== meId)
      };
    });
  });
</script>

Let's break it down. We start with retrieving the information about the current user (<mgt-get resource="/me">). Once we have the data, we need to store the ID of the current user. We do this using the storeId help function that we define later. This is necessary because as of writing this article, the Get component doesn't support passing additional information to child components and templates.

Next, we retrieve the information about the current user's manager (<mgt-get resource="/me/manager">), followed by retrieving their direct reports (<mgt-get resource="/users//directReports">). Notice how we pass the manager's user ID into the directReports resource (``).

After we retrieved the list of direct reports, we pass them for rendering using the People component (<mgt-people people="{{excludeMe(this.value)}}"...). To filter out the current user, we use another helper function named excludeMe.

To store the ID of the current user, we define a custom function named storeId which we attach to the templateContext of the first Get component. Then, to exclude the current user from the list of direct reports and build the list of colleagues, we define another function named excludeMe, which we need to attach to the templateContext of the People component. The trick here is, that the People component is available to use only after the three parent Get components have rendered, which is why we need to delay extending its context using the templateRendered events.

While it's more than one line of code, the sample is complete with reporting progress and catching errors on each step. If you find it too elaborate, there is also another approach using the Microsoft Graph client exposed by the toolkit.

Use the Graph client

Another approach to show colleagues of the current user involves using the Microsoft Graph client exposed by Microsoft Graph Toolkit:

<div id="colleagues">
  Loading my information...
</div>
<script>
  mgt.Providers.onProviderUpdated(async () => {
    if (mgt.Providers.globalProvider.state !== mgt.ProviderState.SignedIn) {
      return;
    }

    const graphClient = mgt.Providers.globalProvider.graph.client;
    const colleaguesElement = document.getElementById('colleagues');
    try {
      const me = await graphClient.api('me').get();
      colleaguesElement.innerHTML = 'Loading information about my manager...';
      const myManager = await graphClient.api('me/manager').get();
      colleaguesElement.innerHTML = 'Loading information about my colleagues...';
      const ourTeam = await graphClient.api(`users/${myManager.id}/directReports`).get();
      const myColleagues = ourTeam.value.filter(p => p.id !== me.id);
      colleaguesElement.innerHTML = '<mgt-people show-max="20"></mgt-people>';
      const mgtPeople = colleaguesElement.querySelector('mgt-people');
      mgtPeople.people = myColleagues;
    }
    catch (ex) {
      colleaguesElement.innerHTML = `The following error has occurred while loading information about my colleagues: ${ex}`;
    }
  });
</script>

We start with defining a container div where we'll render the data. Next, we jump straight into JavaScript. Using an event handler for the providerUpdated event we delay executing the script until the user signed in to our app. Next, we retrieve an instance of the Graph client and the container element. Here we start retrieving the data from Graph using .api(...).get() calls. Because we're working in code, we have the full flexibility for handling the data, passing it between the calls, and reporting the current state to the UI.

Finally, after we retrieved the direct reports and filtered out the current user, we programmatically add an instance of the People component to the container element. Next, we take the list of colleagues and pass it to the component using its people property.

By using the async/await construct, we're able to simplify the code and wrap it in a single try..catch statement, and avoid working with chained promises.

The verdict

If you prefer the declarative approach, you'll find using the Get component more convenient. It has a clearer separation between the code and the presentation. On the other hand, it's more elaborate than using the Graph client. One more benefit that you should consider is that by default, data retrieved using the Get component is automatically cached which would help you speed up your app. When using the Graph client, you'd need to implement caching yourself.

Which approach do you prefer?

Comments

comments powered by Disqus