No time to manage your calendar? Let Power Automate assist your response to calendar invites using Microsoft Graph connectors

Hi there! I would like to share a solution about a scenario/challenge that I personally experienced in the past when I was a consultant.

Consultants or anyone who works with customers day to day bases may know that we spend the entire day with our customers to plan/work together on our projects.

Meetings after meetings -recurring everyday-, we almost never have time to check our e-mails. Most of us also miss the new meeting invites for the next day and never get a chance to say “I have a conflict in my calendar”.

So, we may end up having meetings conflicting in the calendar and unable to attend half of it. Sounds familiar?

What if we build a flow in Power Automate using out of the box Microsoft Graph connectors to check calendar availability, every time when there is a new calendar event received? May our flow be able to send a accept/tentative/decline response automatically according to user’s calendar status? Yes, of course it can!

Power Automate has hundreds of Microsoft Graph powered out of the box connectors to get e-mails, post messages to Teams, send a calendar invite and many more. However, not all M365 datasets are available in this collection of built-in connectors. If you are interested in using any M365 dataset in your project, Microsoft Graph Power Automate Tutorial shows how to build a custom connector and consume it in the Power Automate flow.

For this example, We have the right out of the box connectors to design the scenario. Process will be similar to the flow shown below:

Let’s build our flow in Power Automate

Go to https://flow.microsoft.com and select Create on the left hand side menu and choose Automated flow.

Give a name to your flow and choose your flow trigger as When a new event is created (V3). Select Create.

In the flow, add the following connectors and fill the fields as follows:

  • When a new event is created (V3)
  • *Calendar id: [Calendar]
  • Get Event (V3)
  • *Calendar id: Calendar
  • *Item id: [Id] (Dynamic content/When new event is created (V3))
  • Get calendar view of events (V3)
  • *Calendar id: Calendar
  • *Start time: [Start time] (Dynamic content/Get Event (V3))
  • *End time: [End time] (Dynamic content/Get Event (V3))

  • Filter array
    *From: [value] (Dynamic content/Get calendar view of events (V3))
  • *Choose a value: [Id] (Dynamic content/Get calendar view of events (V3))
  • *Filter: is not equal to
  • *Choose a value: [Id] (Dynamic content/Get Event (V3))
  • Condition
    *Choose a value: length(body(‘Filter_array’))
    (Expression)
  • *Condition: is less than or equal to
  • *Choose a value: 0

  • IF YES
  • Respond to an event invite (V2)
    *Event Id: [Id] (Dynamic content/Get Event (V3))
  • *Response: Accept
  • *Comment: Looking forward to talk to you soon!
  • *Send Response: Yes
  • Post a message as the Flow bot to a user (Preview)
  • *Recipient: { your recipient e-mail here }
  • *Message: [Subject] (Dynamic content/Get Event (V3)) organized by [Organizer] (Dynamic content/Get Event (V3)) is accepted and added in your calendar.
  • *Headline: Meeting accepted!
  • IF NO
  • Get calendar view of events (V3)
    *Calendar Id: Calendar
  • *Start Time: [End time] (Dynamic content/Get Event (V3))
  • *End Time: addDays(body(‘Get_event_(V3)’)?[‘end’],1)
    (Expression)
  • Condition
  • *Choose a value: length(body(‘Get_calendar_view_of_events_(V3)_2’)?[‘value’])
  • (Expression)
  • *Condition: is greater than or equal to
  • *Choose a value: 5
  • IF YES
  • Respond to an event invite (V2)
    *Event Id: [Id] (Dynamic content/Get Event (V3))
  • *Response: Decline
  • *Comment: Hi, unfortunately there is a conflict in my calendar. So, I won’t be able to join this meeting today. Can we meet any other day?
  • *Send Response: Yes
  • Post a message as the Flow bot to a user (Preview)
  • *Recipient: { your recipient e-mail here }
  • *Message: [Subject] (Dynamic content/Get Event (V3)) organized by [Organizer] (Dynamic content/Get Event (V3)) is declined.
  • *Headline: Meeting declined!
  • IF NO
  • Send email with options
    *To: Organizer (Dynamic content/Get Event (V3))
  • *Subject: Tentative: Can we reschedule?
  • *User Options: Yes, No
  • Post a message as the Flow bot to a user (Preview)
  • *Recipient: { your recipient e-mail here }
  • *Message: [Subject] (Dynamic content/Get Event (V3)) organized by [Organizer] (Dynamic content/Get Event (V3)) is conflicting with other meetings. We realized you have some availabilities in your calendar and asked [Organizer] (Dynamic content/Get Event (V3)) to reschedule.
  • *Headline: Reschedule requested!

Test the scenarios!

Select Save and Test on the right hand side bar and choose I’ll perform the trigger action to test your flow.

The flow is tracking the following account’s calendar: Mod Administrator (admin@M365x716122.onmicrosoft.com)
The user who sends the calendar invites: Ayca Bas (aycabas@M365x716122.onmicrosoft.com)

Scenario 1: No Conflict

Ayca Bas sends a calendar invite to the Mod Administrator. If there is no conflict in Mod Administrators’ calendar, Ayca Bas will receive an Accepted response, and Flow bot will notify Mod Administrator about the taken action.

Ayca Bas receives:

Mod Administrator receives:

Scenario 2: Conflict, calendar is not busy for the day

Ayca Bas sends a calendar invite to the Mod Administrator. If there is a conflict in Mod Administrators’ calendar but there are less than 5 events for the day, Ayca Bas will receive a Tentative response, requesting to reschedule the meeting. Flow bot will notify Mod Administrator about the taken action.

Ayca Bas receives:

Mod Administrator receives:

Scenario 3: Conflict, calendar is busy for the day

Ayca Bas sends a calendar invite to the Mod Administrator. If there is a conflict in Mod Administrators’ calendar and there are more than 5 events for the day, Ayca Bas will receive a Decline response. Flow bot will notify Mod Administrator about the taken action.

Ayca Bas receives:

Mod Administrator receives:

Solution package.zip file can be found under the following repository: My Calendar Manager.

Microsoft Graph + Power Automate = M365 data with no code!

Microsoft Graph provides a single endpoint to get connected with M365 data. We know that Power Automate has many built-in MS Graph components to access Outlook, Planner, Calendar and more. But, think about calling Microsoft Graph API with custom filters from Power Automate directly! It is even more exciting and gives us more flexibility and numerous automation possibilities with no-code. Today, our scenario is to get “members” of a particular group in 3 quick steps.


Step 1: Register an application in Azure Active Directory

  • Go to Azure Portal.
  • Under Azure Active Directory, go to App Registration and click on New Registration.
  • Give a name to your app, choose the access level and hit Register button.
  • Go to API permissions tab, click on Add a permission, select Microsoft Graph and then Application permissions, add the following permissions: “Group.Read.All”, “Group.ReadWrite.All”, “User.Read.All”, “GroupMember.ReadWrite.All”, “User.ReadWrite.All”, “Directory.ReadWrite.All”.
  • After creating the permissions, we need to grant consent to the application to allow the application to access Graph API.
  • Go to Certificates & secrets and create a new client secret and copy in a notepad.
  • Finally, go to Overview tab, copy Application (client) ID and Directory (tenant) ID to a notepad, we will need it later.

Step 2: Build Power Automate Flow

  • Go to Microsoft Power Automate Portal.
  • Click on Create tab and choose Instant flow.
  • Give a flow name and choose When an HTTP request is received as a trigger of the flow, then click Create.
  • Extend When a When an HTTP request is received block and click on Use sample payload to generate schema, then add the following schema:
{
    "type" : "object",
    "properties" : {
       "groupId" : {
          "type" : "string"
       }
     }
}
  • Then click Save.
  • Click New Step and add Initialize variable with the following details:
    • Name: application_id
    • Type: string
    • Value: AAD App – Application (Client) Id
  • Click New Step and add Initialize variable with the following details:
    • Name: directory_id
    • Type: string
    • Value: AAD App – Directory (Tenant) Id
  • Click New Step and add Initialize variable with the following details:
    • Name: secret
    • Type: string
    • Value: AAD App secret
  • Click New Step and add HTTP with the below details:

Step 3: Test the Flow with Microsoft Graph Explorer and Postman

  • Go to Microsoft Graph Explorer.
  • Sign-in with your account.
  • Find all groups under my organization under Sample queries and click on it.
  • Copy one of the group ids for the testing purposes, we will use it in.
  • You may use Postman for testing purposes, and specify the following details:
    • Method: Post
    • Endpoint: copy it from the first step of your flow (When a HTTP request is received)
    • Header:
      • Key: Content-type
      • Value: application/json
    • Body: { “groupId” : “copied-groupid-from-GraphExplorer“}
  • Run test from Power Automate and send the post request from Postman.

After the successful flow, we should receive the output with all the members’ information in Json format under HTTP block.

Cheers.

– Ayca

Build PowerApps in a couple of minutes to impress friends and family!

Yesterday, my friends were coming over for dinner. I wanted to make it very special for them, so I planned to cook amazing Turkish dishes that they have never tried before. Let me be very honest with you, cooking 5 different dishes was not easy at all. Then, why not go one step further and create a dinner menu for them, so they taste the complete Turkish Restaurant vibe 🙂 (Such a great excuse to deep dive into PowerApps!)

If you are searching for quick ways to build a mobile/tablet app, PowerApps is the answer! I spent only couple of minutes to build this dinner menu app and everyone was so surprised, loved it, felt very special! I shared all the details about how to build a Power App in couple of minutes to impress friends and family. Try and let me know about the results.

Build PowerApps with no code

Go to PowerApps website and create Canvas app from blank.

Give a name to your app and choose Phone as a format.

Welcome Page

Let’s rename Screen1 as Welcome Page.

Start designing the page by choosing required properties under Insert tab. I will insert below items:

  • Text Label: Welcome message
  • Text Label: Description for the guests
  • Button: Navigation to the Menu Page
  • Text Label: My name as a footer

Click on the first Text Label and edit settings from the panel on the right side. You can rename Label1 as Welcome Label by clicking edit icon on the right side.

  • Text (Double-click to edit on the panel): Hoşgeldiniz (Welcome in Turkish)
  • Font: Dancing Script
  • Font size: 50
  • Font wight: Bold
  • Text alignment: Align Center
  • Size Width: 560
  • Size Height: 90
  • Color: Custom (Hex: D4AF37)

Then, the second Text Label and edit settings from the panel on the right side. You can rename Label2 as Description Label by clicking edit icon on the right side.

  • Text (Double-click to edit on the panel): Welcome my lovely friends! :)Get ready for a wonderful …..
  • Font: Open Sans
  • Font size: 26
  • Font weight: Normal
  • Text alignment: Align Center
  • Position X: 40
  • Position Y: 160
  • Size Width: 560
  • Size Height: 775
  • Color: Dark Grey
  • Border style: Solid
  • Border thickness: 2
  • Border Color: Custom (Hex: D4AF37)

Next, go to Button and edit settings from the panel on the right side. You can rename Button1 as Menu Button by clicking edit icon on the right side.

  • Text: View Menu
  • Position X: 180
  • Position Y: 895
  • Size Width: 280
  • Size Height: 70
  • Color: White
  • Color Fill: Custom (Hex: D4AF37)

Finally, the third Text Label and edit settings from the panel on the right side. You can rename Label3 as Footer Label by clicking edit icon on the right side.

  • Text: Chef: Ayça Baş
  • Font: Dancing Script
  • Font size: 30
  • Font weight: Bold
  • Text alignment: Align Center
  • Position X: 40
  • Position Y: 995
  • Size Width: 560
  • Size Height: 125
  • Color: Custom (Hex: D4AF37)
Menu Page

Let’s create a new screen for the menu. Choose Blank template and rename it as Menu Page.

Before we start designing the Menu Page, let’s create the navigation from Welcome Page Menu Button.

Click on View Menu button and go to Advanced tab. To enable navigation, insert below script under OnSelect action:
Navigate(‘Menu Page’)

Design the page by choosing required properties under Insert tab. I will insert below items.

  • Back Arrow: Navigation to the Welcome Page
  • Text Label: Title
  • Text Label: Header
  • Text Label: Description

First of all, click on Back arrow and edit properties from the panel on the right side. You can rename Arrow1 as Back Button by clicking edit icon on the right side.

  • Position X: 40
  • Position Y: 25
  • Size Width: 65
  • Size Height: 65

For the navigation, go to Advanced tab and insert below script under OnSelect action:
Navigate(‘Welcome Page’)

Secondly, click on the first Text Label and edit settings from the panel on the right side. You can rename Label4 as Menu Title Label by clicking edit icon on the right side.

  • Text: Menu
  • Font: Dancing Script
  • Font size: 50
  • Font wight: Bold
  • Text alignment: Align Center
  • Position X: 0
  • Position Y: 0
  • Size Width: 640
  • Size Height: 120
  • Color: White
  • Color Fill: Custom (Hex: D4AF37)

Then, the second Text Label and edit settings from the panel on the right side. You can rename Label5 as Header Label by clicking edit icon on the right side.

  • Text: Appetizers
  • Font: Dancing Script
  • Font Size: 40
  • Font Weight: Bold
  • Font Style: Underline
  • Text alignment: Align Center
  • Position X: 0
  • Position Y: 195
  • Size Width: 640
  • Size Height: 65
  • Color: Dark Grey

Finally, the third Text Label and edit settings from the panel on the right side. You can rename Label6 as Dish Description Label by clicking edit icon on the right side.

  • Text: Zeytinyağlı Biber Dolması
  • Font: Open Sans
  • Font Size: 25
  • Font weight: Normal
  • Text alignment: Align Center
  • Position X: 0
  • Position Y: 270
  • Size Width: 640
  • Size Height: 45
  • Color: Dark Grey

Go to Advanced tab and insert below script under OnSelect action to enable redirect link: Launch(“https://en.wikipedia.org/wiki/Dolma”)

Add couple of more header labels and dish description labels. Repeat the same steps on above until you build the complete menu.

For both pages, click on any empty space of the page and go to Properties tab on the right side. You can either change the background color or add a background image. I will choose one of images on my desktop for the final look of my app and choose Image Position as Fill.

Don’t forget to save and publish your app. You can also share your app with friends, family and colleagues via e-mail. Let me know if your guests like it! 🙂

Finally, our app is ready! PowerApps is always the quick solution for such scenarios. If you are interested in learning more about Power Platform, check out the related posts.

Cheers!

Microsoft Graph API Custom Connector for Power BI

Hello there, developers! Today, we will build Microsoft Graph API custom connector for Power BI by using M Language. For those who have never heard M Language before, here is a quick ramp-up video:

Deep Dive into the M Language

In this scenario, our purpose is to build a custom connector to get Office 365 data via Graph API and use it as a data model in Power BI. So, we will be focusing on getting Microsoft Teams’ all provisioned team list with members through the connector.

Lets start with pre-requisites

Create AAD Registered Graph Application

Login Azure Portal and go to Azure Active Directory on the left side of the menu, then select App registrations and create New registration.

Give a name to your app, select preferred supported account type for your organization.
Make sure to select Public client/native (mobile/desktop) for Redirect URI and place the following redirect URL: “https://oauth.powerbi.com/views/oauthredirect.html“, then click register.

Once our app is registered, go to API Permissions > Add a permission and choose Microsoft Graph.

Select below permissions below for both Delegated Permissions and Application Permissions:

User.Read
User.Read.All
User.ReadWrite.All
Group.Read.All
Group.ReadWrite.All

Then, click Grant Admin Content for -your organization-.

Finally, go to Overview from the left side of the menu and copy Application (client) ID to a notepad. We will need this id later.

Build the connector

After successfully registering the app in Azure Active Directory, we are ready to create a Data Connector Project in Visual Studio.

  • Add Registered App Client ID in MyGraphConnector project

    As you already copied your Application (Client) ID to a notepad, go ahead and save it as client_id.txt file.
    Add your existing client_id.txt file in MyGraphConnector project, rename and remove txt extension from the file name.
    Finally, click on client_id and change the Build Action setting from Properties as Compile.

  • Configure OAuth for Graph API in MyGraphConnector.pq

    Let’s set the variables first:
section MyGraphConnector;
// TODO: make sure to set AAD client ID value in the client_id file
client_id = Text.FromBinary(Extension.Contents("client_id")); 
redirect_uri = "https://oauth.powerbi.com/views/oauthredirect.html";
token_uri = "https://login.microsoftonline.com/organizations/oauth2/v2.0/token";
authorize_uri = "https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize";
logout_uri = "https://login.microsoftonline.com/logout.srf";
windowWidth = 720;
windowHeight = 1024;

Define Registered App scope:

scope_prefix = "https://graph.microsoft.com/";
scopes = {
   
    "Group.Read.All",
    "Group.ReadWrite.All",
    "User.Read",
    "User.Read.All",
    "User.ReadWrite.All"
  
};
Value.IfNull = (a, b) => if a <> null then a else b;
GetScopeString = (scopes as list, optional scopePrefix as text) as text =>
    let
        prefix = Value.IfNull(scopePrefix, ""),
        addPrefix = List.Transform(scopes, each prefix & _),
        asText = Text.Combine(addPrefix, " ")
    in
        asText;

OAuth Implementation:

// StartLogin builds a record containing the information needed for the client
StartLogin = (resourceUrl, state, display) =>
    let
        authorizeUrl = authorize_uri & "?" & Uri.BuildQueryString([
            client_id = client_id,  
            redirect_uri = redirect_uri,
            state = state,
            scope = "offline_access " & GetScopeString(scopes, scope_prefix),
            response_type = "code",
            response_mode = "query",
            login = "login"
        ])
    in
        [
            LoginUri = authorizeUrl,
            CallbackUri = redirect_uri,
            WindowHeight = 720,
            WindowWidth = 1024,
            Context = null
        ];
// FinishLogin is called when the OAuth flow reaches the specified redirect_uri. 
FinishLogin = (context, callbackUri, state) =>
    let
        // parse the full callbackUri, and extract the Query string
        parts = Uri.Parts(callbackUri)[Query],
        // if the query string contains an "error" field, raise an error
        // otherwise call TokenMethod to exchange our code for an access_token
        result = if (Record.HasFields(parts, {"error", "error_description"})) then 
                    error Error.Record(parts[error], parts[error_description], parts)
                 else
                    TokenMethod("authorization_code", "code", parts[code])
    in
        result;
// Called when the access_token has expired, and a refresh_token is available.
Refresh = (resourceUrl, refresh_token) => TokenMethod("refresh_token", "refresh_token", refresh_token);
Logout = (token) => logout_uri;
// code:       Is the actual code (authorization_code or refresh_token) to send to the service.
TokenMethod = (grantType, tokenField, code) =>
    let
        queryString = [
            client_id = client_id,
            scope = "offline_access " & GetScopeString(scopes, scope_prefix),
            grant_type = grantType,
            redirect_uri = redirect_uri
        ],
        queryWithCode = Record.AddField(queryString, tokenField, code),
        tokenResponse = Web.Contents(token_uri, [
            Content = Text.ToBinary(Uri.BuildQueryString(queryWithCode)),
            Headers = [
                #"Content-type" = "application/x-www-form-urlencoded",
                #"Accept" = "application/json"
            ],
            ManualStatusHandling = {400} 
        ]),
        body = Json.Document(tokenResponse),
        result = if (Record.HasFields(body, {"error", "error_description"})) then 
                    error Error.Record(body[error], body[error_description], body)
                 else
                    body
    in
        result;

Graph API Connector Implementation:

//Graph API URL to get all provisioned team list with members 
url= "https://graph.microsoft.com/beta/groups?$expand=members&$filter=resourceProvisioningOptions/Any(x:x eq 'Team')";
//
// Exported function(s)
//
[DataSource.Kind="MyGraphConnector", Publish="MyGraphConnector.UI"]
shared MyGraphConnector.Feed = () =>
    let
        source = OData.Feed(url, null, [ ODataVersion = 4, MoreColumns = true ])
      
      in
        source;
  
//
// Data Source definition
//
MyGraphConnector = [
    TestConnection = (dataSourcePath) => { "MyGraphConnector.Feed" },
    Authentication = [
        OAuth = [
            StartLogin=StartLogin,
            FinishLogin=FinishLogin,
            Refresh=Refresh,
            Logout=Logout
        ]
    ],
    Label = "My Graph Connector"
];
//
// UI Export definition
//
MyGraphConnector.UI = [
    Beta = true,
    ButtonText = { "MyGraphConnector.Feed", "Connect to Graph" },
    SourceImage = MyGraphConnector.Icons,
    SourceTypeImage = MyGraphConnector.Icons
];
MyGraphConnector.Icons = [
    Icon16 = { Extension.Contents("MyGraphConnector16.png"), Extension.Contents("MyGraphConnector20.png"), Extension.Contents("MyGraphConnector24.png"), Extension.Contents("MyGraphConnector32.png") },
    Icon32 = { Extension.Contents("MyGraphConnector32.png"), Extension.Contents("MyGraphConnector40.png"), Extension.Contents("MyGraphConnector48.png"), Extension.Contents("MyGraphConnector64.png") }
];

  • Update MyGraphConnector.query.pq
MyGraphConnector.Feed()

  • Run the project

Run the application in Debug mode. You should be able to receive below app:

Select your Credential Type as OAuth2, and paste your accessToken for the login url https://login.microsoftonline.com/organizations/oauth2/v2.0/token. (To learn how to get accessToken: https://docs.microsoft.com/en-us/graph/auth-v2-user)

Click Login, then type your credentials. After the successful login, click on Set Credential. You can go to Credentials tab to check if your credentials set properly.

Finally, press CTRL + F5 to get the data with connector.

  • Create MyGraphConnector.mez package

    If your app is successfully getting the data as above, let’s run the project in Release mode to create release package.

Copy your MyGraphConnector.mez from
[Your Project Folder]\MyGraphConnector\MyGraphConnector\bin\Release.

Paste it under
C:\Users\[Your User Name]\Documents\Power BI Desktop\Custom Connectors.

Test your connector in Power BI Desktop

Open your Power BI Desktop and select Get Data, click More. MyGraphConnector.Feed (Beta) should appear under Online Services. Select and connect.

Sign in with your credentials and connect.

All provisioned teams list with members should appear in Power BI as data table.


Below is the list of all properties we are able to get with the following Graph API:

graph.microsoft.com/beta/groups?$expand=members&$filter=resourceProvisioningOptions/Any(x:x eq ‘Team’)


You can get the source code from here: https://github.com/aycabas/Graph-API-Custom-Connector. I hope you find the post useful. Let me know about your experience, share your questions and comments below.

Cheers!

How to get Microsoft Teams call quality data from Power BI?

I often get this question these days during Covid-19. We all have the same situation, whatever is our job, we are at home trying to work as efficient as possible to make sure that everything is on track. Business continues virtually, so is the education! The new way of things work brought us new kind of challenges such as auditing and monitoring the meetings. Most of our workspace activities (chatting, meeting, screen sharing, file sharing etc.) are running on Microsoft Teams now, often company’s departments responsible of auditing are looking for solutions to get more data from Microsoft Teams.

To help addressing many of the questions, Microsoft released Call Analytics and Call Quality Dashboards (CQD).

Call Analytics is available in the Microsoft Teams admin center to see detailed information about the devices, networks, and connectivity related to the calls and meetings for each user in a Microsoft Teams tenant account.
Call Quality Dashboard data is available with many dimensions and measurements to analyze and customize on Power BI with a provided connector and templates. All we need to do is:

  1. Download Power BI query templates for Microsoft CQD from GitHub here.
  2. Create a new folder called “Power BI Desktop under Documents folder.
  3. Create a new folder called “Custom Connectors” under Power BI Desktop folder. Final destination should be Documents\Power BI Desktop\Custom Connectors.
  4. Extract CQD-Power-BI-query-templates.zip and copy MicrosoftCallQuality.pqx under Documents\Power BI Desktop\Custom Connectors.

Now, it’s time to enable custom connector extensibility settings in Power BI Desktop and run Call Quality Dashboards.

Developers like us can enable new data sources with custom data extensions called custom connectors.  To be able to use non-certified custom connector, Data Extensions settings should be enabled to allow any extension to load.

In Power BI Desktop, select File > Options and settings > Options > Security.

Under Data Extensions, select (Not Recommended) Allow any extension to load without validation or warning

Select OK, and then restart Power BI Desktop.

When Power BI Desktop restarted, click on “Get Data” and Select “More.”

Microsoft Call Quality Connector should appear under Online Services.

Select “Microsoft Call Quality (Beta)” and click Connect.

Microsoft Call Quality Connector requires Azure Active Directory Global Admin Consent to retrieve the data from tenant.

Login with a Global Admin account and admit the consent.

After the login, click connect and select connection settings as DirectQuery.

As a result, you will see long list of data under the fields. Select any field, drag and drop to the page to test the data. Feel free to build your own dashboard by adding some visualizations.

You may also leverage from the built-in dashboards available under CQD-Power-BI-query-templates.zip.

Feel free to test any of the dashboards listed above.

Make sure to edit permissions by clicking Data source settings under Transform Data and sign in with your global admin account.

CQD Teams Utilization Report is shown below as an example.

Call Quality Dashboards are quite useful for tracking call counts per month/day, even drilling down by conference or user for more information. Also, it is easy to customize if you are looking for something different.

Keep in mind that CQD is to get more insights about the calls, so it doesn’t contain Microsoft Teams details for tenant such as team names, member/owner names, policy information etc. To get Microsoft Teams tenant data and use it in Power BI, we will need to build our own custom connector by using Microsoft Graph API. I will create a separate post to walk you through on that.

I hope you enjoy reading the post. Looking forward to get your comments.

Cheers!