Querying the Microsoft Graph with Python

One of my colleagues mentioned to me that data from MyAnalytics (which is feature of Viva Insights within Microsoft 365) is now accessible via the Beta endpoint of the Microsoft Graph. If you aren’t familiar, you can find out more about MyAnalytics here.

I was particularly excited as MyAnalytics has a wealth of Microsoft 365 usage data, which it analyzes to provide users with personalised insights based on their work patterns and behaviours, for example:

Clicking Learn more on each card provides additional guidance:

I was really interested to examine the data returned by the Beta Graph endpoint for MyAnalytics. Looking at the documentation, it provides two key pieces of functionality:

Activity statistics returns statistics on the following data points for the previous week (Monday to Sunday) for a user. It’s currently not possible to specify a particular week to query, it will simply return data from the previous week.

  • Calls (Teams)
  • Chats (Teams)
  • Emails (Exchange)
  • Meetings (Exchange)
  • Focus – this is explained here

If I take emails as an example, this returns the following properties:

…and here are the returned properties for meetings:

Productivity and self-improvement are two areas of immense interest to me, using the MyAnalytics data returned from the Graph I could envisage creating some custom reports to track my work patterns over time and then act on this – for example, the data could highlight that I’ve spent more time working outside of working hours recently or that I’m starting to attend more recurring meetings.

As a side note: Outlook settings are used to determine a user’s working hours.

The next step for me was to create a Python Web app (using Flask) to retrieve a subset of this information from the Graph (I always love to overcomplicate things!).

I took the Flask-OAuthlib sample from https://github.com/microsoftgraph/python-sample-auth and tweaked this to my needs, my updated script can be found below and on GitHub.

This script could be tweaked to perform other Graph queries if needed

import uuid
import json
import flask
from flask_oauthlib.client import OAuth
CLIENT_ID = ''
CLIENT_SECRET = ''
REDIRECT_URI = 'http://localhost:5000/login/authorized'
AUTHORITY_URL = 'https://login.microsoftonline.com/organizations'
AUTH_ENDPOINT = '/oauth2/v2.0/authorize'
TOKEN_ENDPOINT = '/oauth2/v2.0/token'
RESOURCE = 'https://graph.microsoft.com/'
API_VERSION = 'beta'
SCOPES = ['Analytics.Read']
APP = flask.Flask(__name__)
APP.secret_key = 'development'
OAUTH = OAuth(APP)
MSGRAPH = OAUTH.remote_app(
    'microsoft', consumer_key=CLIENT_ID, consumer_secret=CLIENT_SECRET,
    request_token_params={'scope': SCOPES},
    base_url=RESOURCE + API_VERSION + '/',
    request_token_url=None, access_token_method='POST',
    access_token_url=AUTHORITY_URL + TOKEN_ENDPOINT,
    authorize_url=AUTHORITY_URL + AUTH_ENDPOINT)
@APP.route('/')
def login():
    """Prompt user to authenticate."""
    flask.session['state'] = str(uuid.uuid4())
    return MSGRAPH.authorize(callback=REDIRECT_URI, state=flask.session['state'])
@APP.route('/login/authorized')
def authorized():
    """Handler for the application's Redirect Uri."""
    if str(flask.session['state']) != str(flask.request.args['state']):
        raise Exception('state returned to redirect URL does not match!')
    response = MSGRAPH.authorized_response()
    flask.session['access_token'] = response['access_token']
    return flask.redirect('/graphcall')
@APP.route('/graphcall')
def graphcall():
    """Confirm user authentication by calling Graph and displaying some data."""
    endpoint = 'me/analytics/activityStatistics'
    headers = {'SdkVersion': 'sample-python-flask',
               'x-client-SKU': 'sample-python-flask',
               'client-request-id': str(uuid.uuid4()),
               'return-client-request-id': 'true'}
    graphdata = MSGRAPH.get(endpoint, headers=headers).data
    data = str(graphdata).replace("'",'"')
    datadict = json.loads(data)
    summary = []
    i = 0
    while i < 5:
        if datadict["value"][i]["activity"] == "Focus":
            i += 1
        else:
            summary.append("Activity Type: " + datadict["value"][i]["activity"] + " / Date: " + datadict["value"][i]["startDate"] + " / After Hours " + datadict["value"][i]["afterHours"])
            i += 1
    return str(summary)  
@MSGRAPH.tokengetter
def get_token():
    """Called by flask_oauthlib.client to retrieve current access token."""
    return (flask.session.get('access_token'), '')
if __name__ == '__main__':
    APP.run()

This script (Flask Web app) does the following:

  • Prompts the user to authenticate to a M365 tenant (and requests access to the ‘Analytics.Read’ and ‘User.Read’ scopes in the Graph)
  • Queries the me/analytics/activityStatistics endpoint
  • Returns the following information for each activity type for the first day in the reporting period (excluding Focus)
    • Date (“startDate”)
    • Activity Type (“activity”)
    • Time spent after hours on the activity (afterHours)

If you take a closer look at the script, you’ll see it takes the raw JSON output from the Graph, converts this to a Python dictionary and then iterates through the first day of the weeks data for each activity type (excluding Focus) and outputs this as a string – it’s certainly not pretty, but this is more of a proof of concept to get me started 😀.

Before running this script, you’ll need to do a few things:

  • Install the pre-requisites (pip install -r requirements.txt)
  • Register an application in Azure AD, here is a walkthrough of how to do this
  • In addition to the above, add the Analytics.Read permission (example below) – this is required to get access to the MyAnalytics data
  • Update the CLIENT_ID and CLIENT_SECRET variables (using the values obtained when registering the app in Azure AD)
  • Run the script using “python app.py”
  • Launch a browser and connect to http://localhost:5000

You should then (hopefully!) see the following:

A sign in page:

Once authenticated, you should see the following screen – which is requesting specific permission to the users data.

Once you’ve clicked Accept, the delightful screen below should be displayed which includes the raw output. The test tenant I used to create the script has no usage hence the important data (After Hours) reports zero, in a real-world scenario this would be a cumulative value in seconds spent on each activity after hours.

I’ll likely write more about this as my experimentation continues…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s