Recently I came across Microsoft Graph API through Power BI Custom data connectors documentation. Amazed by the amount of information provided by this API. I didn’t find an appropriate use case right away, but later I found a very interesting one “a Power BI template to find all Power BI users and their Groups”. In this post, I am going to cover a bit about Graph API and how to extract data into Power BI, more like an antecedent for my next blog post.

Microsoft Graph API is a RESTful Web API; we can use this to get access to data from the Microsoft Cloud services like Active Directory, Sharepoint, Onedrive and much more. You can find all documentation here – https://developer.microsoft.com/en-us/graph/docs/concepts/overview. What one of the main benefits of this API compares to many other Microsoft API’s is it has a single endpoint https://graph.microsoft.com.  I could use this as a data source in my Power BI Desktop, publish the Model to Power BI service and schedule the data source. Currently, we can not schedule Power BI custom data connectors, but once this feature gets implemented, I think it is better to access Graph API using custom data connector as documented here –  https://github.com/Microsoft/DataConnectors/tree/master/samples/MyGraph

For now, if you want you can extract data from this API using OData Connector. The best place I found useful to start with Microsoft Graph API is the Graph Explorer – https://developer.microsoft.com/en-us/graph/graph-explorer

Microsoft Graph now supports two versions. I found beta useful as it has more data than version v1.0. You can switch between versions easily in Graph explorer and see what data available in each version. A user needs the right level of permission to get access to the correct data, and these permissions are at very granular level. I don’t think; I understood permissions very well if you want you can find permissions information here – https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference

You can use the Graph Explorer, to see what data is available to view, modify permissions or just to get an overview of the API.

Access Microsoft Graph API using Power BI

Microsoft Graph has a unified OData endpoint, so I can just use this as OData Feed data source connection, and with few simple clicks, I could import data int Power BI Desktop.

I just copied the API URL from Graph Explorer, pasted in the OData feed data source. I used organizational account authentication. That’s all; I can see all users 🙂

You can download a simple template here – https://1drv.ms/u/s!Avm7gbgZtlMlqCk02OFlwX2ANOfs 

Summary:

This is a fascinating data source, and it gets more interesting with Power BI. As I mentioned earlier, I started playing with this API to get an overview of all Power BI users available. One can get a lot of insights from this data source, hope you find it useful.

Prathy 🙂

 

9 comments
1 FacebookTwitterPinterestEmail

SharePoint is a killer; front end looks nice and beautiful. Once you start exploring behind the screens, that is when it shows the true colours. Whenever you view a list in the SharePoint mostly, you are looking at a SharePoint list View, so it does not show many underlined columns, not even columns with their real names. When you use SharePoint list as a source for a Power BI model, apparently it extracts all the information from the backend, which means you see far more columns than you expected and most of the times column names look obscure.

For example, look at following columns, how anyone can interpret s6hu meant to be column Jan-17

Original Name  Display Name
Title AI Number
cybf Aug-17
Notes1 Notes
s6hu Jan-17
a6ua Feb-17
dnxw Mar-17
uv0g Apr-17
yqkw May-17
ContentType Content Type

If you have faced this problem, want a quick fix, then just download this template. It should rename columns with display names wherever possible, or you may want to read rest of the post, you will know how I created the Template.

SharePoint List Power BI Template

Step 1:

First I created two parameters one for SharePoint URL and one for SharePoint list ID.

[ Description = "Sharepoint List URL" ] SPListURL = null meta [IsParameterQuery=true, Type="Any", IsParameterQueryRequired=true];
[ Description = "Sharepoint List ID" ] ListID = null meta [IsParameterQuery=true, Type="Any", IsParameterQueryRequired=true];

If you are not sure about getting List ID follow this URL or download this template – Get Sharepoint List ID’s. I used following M Code in the Get Sharepoint List ID’s template.

#"SharepointList IDs" = let
    Source = SharePoint.Tables(SPListURL, [ApiVersion = 15])
in
    Source;

Bingo, you get all lists from the given SharePoint site.

Step 2:

Once I know the URL and ID, I created another query with following M Code

 SPList = let
//SPListURL is the URL of sharepoint list
Source = SharePoint.Tables(SPListURL, [ApiVersion = 15]),
/* ListID is the ID of Sharepoint list */
SPList = Source{[Id=ListID]}[Items],
#"Keep only FieldValuesAsText column" = Table.SelectColumns(SPList,{"FieldValuesAsText"}),
RecordColumnNames= Record.FieldNames(#"Keep only FieldValuesAsText column"{0}[FieldValuesAsText]),
ExpandColumn= Table.ExpandRecordColumn(#"Keep only FieldValuesAsText column", "FieldValuesAsText",RecordColumnNames)
in
ExpandColumn;

I navigated to the list I want to import data from, chose FieldValuesAsText column only. FieldValuesAsText has text representation of all list data. Then I used function Record.FieldNames to get all values from Record. This makes it easy for future proofing. If any user changes columns in the future, Model will be clever enough to know the new column names. Then Expanded FieldValuesAsText column with the RecordColumnNames list.

Step 3:

I referenced the previous query and this time extracted all available column names using function Table.ColumnNames. Later I am going to use this query to join the query from Step 4.

 ListColumnNames = let
    Source = SPList,
    ColumnsNames= Table.ColumnNames(Source ),
    #"Converted to Table" = Table.FromList(ColumnsNames, Splitter.SplitByNothing(), null, null, ExtraValues.Error)
in
    #"Converted to Table";

Step 4:

I created another query with the same source as step 1 but in this query, I navigated to field ContentType and then Fields. Apparently, this is where SharePoint hiding all its secrets 🙂

 Title = let
    //SPListURL is the URL of sharepoint list
    Source = SharePoint.Tables(SPListURL, [ApiVersion = 15]),
    /*    ListID is the ID of Sharepoint list       */
    SPList = Source{[Id=ListID]}[Items],
    #"Removed Other Columns" = Table.SelectColumns(SPList,{"ContentType"}),
    ContentType1 = #"Removed Other Columns"{0}[ContentType],
    Fields = ContentType1[Fields],
    #"Removed Other Columns1" = Table.SelectColumns(Fields,{"EntityPropertyName", "InternalName", "StaticName", "Title"})
in
    #"Removed Other Columns1";

Step 5:

I think this step is where the important stuff is.

First, I did a left outer join with ListColumnNames (Step3) and Title (Step 4) on EntityPropertyName and Column1. Then expanded and removed unnecessary columns. I ended up with Origin Name and Title (Display Name name in the SharePoint list). Then I created a custom column with a List of original column names.

In step 6, I want to rename all columns based on the display columns, but if we have duplicate columns or if there is already a column with the same name, Table.RenameColumns function will fail.

So I created a custom column with a list of original column names to see if the column already exists. Then I used List.Contains to check if the column already exists and I filtered out all columns which already exists.

In the end, I left again with Original Name and Title but excluded duplicates and columns already exist in the SharePoint List, then I transformed rows to list using Table.TransformRows function.

RenamedColumns = let
    Source = Table.NestedJoin(ListColumnNames,{"Column1"},Title,{"EntityPropertyName"},"Title",JoinKind.LeftOuter),
    #"Expanded Title" = Table.ExpandTableColumn(Source, "Title", {"EntityPropertyName", "Title"}, {"EntityPropertyName", "Title.1"}),
    #"Renamed Columns" = Table.RenameColumns(#"Expanded Title",{{"Column1", "Original Name"}, {"Title.1", "Title"}}),
    #"Filtered Rows" = Table.SelectRows(#"Renamed Columns", each ([EntityPropertyName] <> null)),
    #"Removed Other Columns" = Table.SelectColumns(#"Filtered Rows",{"Original Name", "Title"}),
    AddListColumns= Table.AddColumn(#"Removed Other Columns", "Custom", each Table.ColumnNames(SPList)),
    CheckNameAlreadyExists = Table.AddColumn(AddListColumns,"TF",each List.Contains([Custom],[Title])),
    #"Filter already existing column names" = Table.SelectRows(CheckNameAlreadyExists, each [TF] = false),
    #"Removed Duplicates" = Table.Distinct(#"Filter already existing column names", {"Title"}),
    #"Removed Other Columns1" = Table.SelectColumns(#"Removed Duplicates",{"Original Name", "Title"}),
    MakeAList= Table.TransformRows(#"Removed Other Columns1", Record.FieldValues)
in 

    MakeAList;

Step 6:

The final step for the template!

Again I referenced my query from Step1, SPList. Then used function Table.RenameColumns to rename columns as display names.  That’s all! Your lovely SharePoint list is ready for the transformations and analysis.

 #"Sharepoint List" = let
    Source = SPList,
    RenameColumns= Table.RenameColumns(Source,RenamedColumns,MissingField.UseNull)
in
    RenameColumns;

Summary :

Lately, one slogan I have heard among people is “We are in digital transformation”. The first place they start or probably the easiest place to start is moving all important Excel workbooks to SharePoint Online then analyse them using Power BI. But it was not that simple to make it work, at least not for me. I could write stories about it. I hope I will find some time to write about my adventures of successfully scheduling a Power BI Model with SharePoint list data source…

Sometimes you face irritating problems, If a user changes a column, it breaks the model, because of renaming columns to friendly names as it looks like in the Sharepoint List. I can’t imagine the frustration of a user when a model brakes with a name change error. Luckily I did not work with really wide lists. But with the very first model I designed with SP list, I realised this issue. My best friend google betrayed me, and I left with no choice other than exploring all the metadata I extracted from Sharepoint List.

This Template helped me to speed up the process. Now I don’t need to create Models for users; if they want they can design their own models, the way they want. It’s a win win 🙂

It’s not only me; there are other are others had similar issues

https://community.powerbi.com/t5/Desktop/Column-Name-Changes-when-Sharepoint-list-is-imported-in-PBI/td-p/183814

https://community.powerbi.com/t5/Issues/Sharepoint-list-connector-pulling-in-incorrect-column-names/idi-p/138924

I hope this helps someone out there. This is just a workaround; it worked great for me, hope it works for you too 😊

Sharepoint Power BI Template  – https://1drv.ms/u/s!Avm7gbgZtlMlqCHE55X7yJaDuBx0

Until next time,

Prathy 🙂

 

6 comments
2 FacebookTwitterPinterestEmail

(Ideally, I would like to do this as a Power BI custom data connector. I have tried, for some reason, my Visual Studio has been crashing when I click on connect to PBI Service using OAuth2. Once I succeed, it would become another blog post)

Why Calling Power BI service using Power BI Desktop?

We all know Power BI is a great tool and easily adaptable. Using Power BI Desktop and service, we can create models then we can publish or save them to Power BI Service. In service, we have workspaces, dashboards, reports, datasets, workbooks and apps; Power BI users create and/or access all these features based on their access levels. Which means all your users will be creating and/or accessing content from Power BI service. Which is great, but how can I answer questions like below:

  • Number of workspaces
  • Who has access to that particular workspace and their level of access
  • Number of Dashboards
  • Number of Reports
  • Dataset Last Refresh time
  • Average refresh time of X data set? ( You can plan, do you need premium or not based on this value)
  • How many Gateways ( For me, it’s not working at the moment)

And much more

Of course, I can get this information by going to Power BI service but how about getting answers to all those questions in one report.With extensive Power BI documentation, we know we could use Power BI API to Embedded Power BI reports or do data refresh. So I thought to call same Power BI API using Power BI Desktop to get the information I need for documenting my Power BI.

Calling Power BI service using Power BI Desktop

Before we dwell into this, let’s understand my approach for API Access. So when using Power BI API, the user will be able to see whatever they have access to. When a user creates a workspace in Power BI mean they are creating an office 365 group, which means they are creating loads of things in Office 365. I will talk more about this in another blog post. For now, just assume I have an SA User. The only user who can create workspaces, which means this user has access to every single workspace in my Power BI tenant. Instead of using an OAuth authentication, I am using the hard coded username and password for my API calls. As I mentioned earlier, I will talk more about Power BI workspaces and my so-called SA User in another blog post (Power BI publishing Strategy)

To call Power BI API, first I need to register an application for Power BI. You can create an application using this URL – https://dev.powerbi.com/apps. You can find more information about how to create and edit an App here – https://powerbi.microsoft.com/en-us/documentation/powerbi-developer-register-app/.

Don’t forget to save your Client ID and Client Secret safe; I still don’t know how to find client secret using Azure Portal (Maybe I have not looked well enough!)

Then in Power BI Desktop, I created two parameters one for Client ID and one for Client Secret. Next, I created a query called getToken with following M

GetToken:

let
    //App Details
    client_id = ClientID,
    client_secret=ClientSecret,

    //Authentication
    
    //URI's
    redirect_uri = "http://localhost:13526/redirect",
    token_uri = "https://login.windows.net/common/oauth2/token", 
    authorize_uri = "https://login.windows.net/common/oauth2/authorize",
    resource="https://analysis.windows.net/powerbi/api",
    
    //User Details  
    username="xxxxxxxxxxxxxxx.com",
    password="xxxxxxxxx",
    

   tokenResponse = Json.Document(Web.Contents(token_uri,          
                                [
                                    Content=
                                    Text.ToBinary(Uri.BuildQueryString(
                                    [
                                        client_id = client_id
                                        ,client_secret=client_secret
                                        ,username=username
                                        ,password=password
                                        ,resource=resource
                                        ,grant_type = "password"
                                        ,redirect_uri = redirect_uri
                                    ]))
                                    ,Headers=
                                        [Accept="application/json"]
                                        , ManualStatusHandling={400}
                                ])),
                                access_token = tokenResponse[access_token],
    token = tokenResponse[access_token]
in
    token

By using above query, I get an access token to call Power BI API using the given user access details. Then it’s a matter of calling various Power BI API GET operations. There is a plenty of documentation available, but Power BI Apiary was most useful one for me. By going to http://docs.powerbi.apiary.io/#introduction/what-can-i-do-with-the-power-bi-api? You can quickly see different operations you could do with Power BI API. I want to start with workspaces.

List all Groups / List all Workspaces:

Once up on time Workspaces were called Groups, may be that’s why in API they are still called Groups. The GET call for Groups is https://api.powerbi.com/v1.0/myorg/groups. I created a query called Workspaces with following M Code to get workspaces data.

let
    getgroups_uri ="https://api.powerbi.com/v1.0/myorg/groups",
    GetGroups = Json.Document(Web.Contents(getgroups_uri, 
                [
                Headers=
                    [
                    Authorization="Bearer "& refToken
                    ], 
                ManualStatusHandling={400}
                ]
                )),
    value = GetGroups[value],
    #"Converted to Table" = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"id", "isReadOnly", "name"}, {"id", "isReadOnly", "name"}),
    #"Renamed Columns" = Table.RenameColumns(#"Expanded Column1",{{"id", "Workspace ID"}, {"name", "Workspace name"}})
in
    #"Renamed Columns"

List all Datasets:

Next all datasets under each workspace. Datasets GET request need GroupID query parameter. Hence I created a function called fnGetdatasets with following M Code

(groupid as text)=>

let
getdatasets_uri =" https://api.powerbi.com/v1.0/myorg/groups/"&groupid&"/datasets",

 GetGroups = Json.Document(Web.Contents(getdatasets_uri ,

 [

 Headers=

 [

 Authorization="Bearer "&getToken

 ],

 ManualStatusHandling={400}

 ]

 )),

 value = GetGroups[value],

 #"Converted to Table" = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error)

in

 #"Converted to Table"

Then created a new query with following M code to get all datasets under each workspace.

let

 Source = Workspaces,

 #"Invoked Custom Function" = Table.AddColumn(Source, "fnDatasets", each fnDatasets([id])),

 #"Expanded fnDatasets" = Table.ExpandTableColumn(#"Invoked Custom Function", "fnDatasets", {"Column1"}, {"Column1"}),

 #"Expanded Column1" = Table.ExpandRecordColumn(#"Expanded fnDatasets", "Column1", {"id", "name", "addRowsAPIEnabled", "configuredBy", "isRefreshable", "isEffectiveIdentityRequired", "isEffectiveIdentityRolesRequired", "isOnPremGatewayRequired"}, {"id.1", "name.1", "addRowsAPIEnabled", "configuredBy", "isRefreshable", "isEffectiveIdentityRequired", "isEffectiveIdentityRolesRequired", "isOnPremGatewayRequired"})

in

 #"Expanded Column1"

All Reports:

Again Reports API needs Workspace ID to get reports. There are two ways you can get Reports data. However, I went for the one who needs GroupID. I created function and then called that function for each GroupID

fnReports Function:
(groupid as text)=>
let
getdatasets_uri ="https://api.powerbi.com/v1.0/myorg/groups/"&groupid&"/reports",
GetGroups = Json.Document(Web.Contents(getdatasets_uri , 
[
Headers=
[
Authorization="Bearer "&refToken
], 
ManualStatusHandling={400}
]
)),
value = GetGroups[value],
#"Converted to Table" = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error)
in
#"Converted to Table"

Reports Query:

let
getgroups_uri ="https://api.powerbi.com/v1.0/myorg/groups",
GetGroups = Json.Document(Web.Contents(getgroups_uri, 
[
Headers=
[
Authorization="Bearer "& refToken
], 
ManualStatusHandling={400}
]
)),
value = GetGroups[value],
#"Converted to Table" = Table.FromList(value, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
#"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"id", "isReadOnly", "name"}, {"id", "isReadOnly", "name"}),
#"Renamed Columns" = Table.RenameColumns(#"Expanded Column1",{{"id", "Workspace ID"}, {"name", "Workspace name"}}),
#"Invoked Custom Function" = Table.AddColumn(#"Renamed Columns", "fnReports", each fnReports([Workspace ID])),
#"Expanded fnReports" = Table.ExpandTableColumn(#"Invoked Custom Function", "fnReports", {"Column1"}, {"Column1"}),
#"Expanded Column2" = Table.ExpandRecordColumn(#"Expanded fnReports", "Column1", {"id", "modelId", "name", "webUrl", "embedUrl", "isOwnedByMe", "isOriginalPbixReport", "datasetId"}, {"Report.id", "Report.modelId", "Report.name", "Report.webUrl", "Report.embedUrl", "Report.isOwnedByMe", "Report.isOriginalPbixReport", "Report.datasetId"})
in
#"Expanded Column2"

Like this, I can also get List of users under each workspace, Dataset refresh history. In the documentation I can see, I can get all gateways and much more information about Gateways – https://msdn.microsoft.com/en-us/library/mt809129.aspx. I think this feature is not available yet, as it is not shown in the apiary and I get an error when I call this service.(Something for future!)

A sample report below:

Summary:

I have to admit, it was not easy. The main difficulty for me was too much information out there. I struggled to understand which API to use, what redirect URL and headers etc. I don’t know much about API’s. All these headers and scopes are very new to me; I still don’t understand why or how they work. So part of the problem was me and part was various documentation about Power BI Embedded. However, at the end of the day, I learned something new. By calling API, I can answer many questions with one refresh in Power BI Desktop.

This post is already too long; I will create a nicer looking template with all queries and post it soon. It’s pretty difficult to get much time to blog during school summer holidays. I hope I create a template soon and also hope this gives some ideas and help someone out there.

Some References for Power BI API :

https://msdn.microsoft.com/en-us/library/mt147898.aspx

http://docs.powerbi.apiary.io/#introduction/what-can-i-do-with-the-power-bi-api?

https://powerbi.microsoft.com/en-us/documentation/powerbi-developer-get-azuread-access-token/

Till next time,

Prathy 🙂

13 comments
2 FacebookTwitterPinterestEmail
Dynamically update Offset value for API calls using Power BI:

If you are a Power BI user group organiser, then it’s kind of obligatory to create a Power BI dashboard on Meet up data. Like many, I thought I would create a Power BI template for London PUG Power BI Meet Up (That’s another blog post!). However, pulling data from API services is never fun. When I started importing data from Meet Up API, at first it was seamless; quite quickly I pulled members data from London PUG Meetup site. Not far after that, I realised, I only had 200 members. I increased Page value to 1500, but still, still, I could see only 200 rows. After a bit of searching, I found this forum – https://stackoverflow.com/questions/35121504/get-more-than-200-group-members-from-the-meetup-api  .

Based on this, I could get only 200 results.  So I need to find a way to call the API with a dynamic offset value using Power Query. The total number of Offset value depends upon the page size and number of members. So first I need to find the total number of members in London PUG Meetup Group then calculate how many offsets I needed. Like any other language, there are many ways I could do this but I decided to go for List.generate() function.

Dynamically update Offset value for API calls using Power BI:

I read so many nice things about M function List.Generate(), but I don’t think I have ever used it properly. There are some really nice blog posts explaining list.Generate() function functionality. These are my favourite: PowerPivotPro, DataChant, Excel Inside and Excelando. If you want to understand the function, these blog posts are a good place to start. What I am showing in this blog post is to show how we can use this with API. Very similar to what they mentioned in their blog posts just a different data source, but I kinda have to write this post so I can remember for my reference.

Here is the M code I used :

let
    URL = "https://api.meetup.com/"&MeetupID&"/members?&sign=true&photo-host=public&page=200&offset=",

    //Get total number of memebers
    TotalNumber = let
                    Source = Json.Document(Web.Contents("https://api.meetup.com/2/members?&sign=true&photo-host=public&group_urlname=London-PUG&key=" & APIKey)),
                    #"meta" = Source[meta],
                    #"Converted to Table" = Record.ToTable(#"meta"),
                    #"Filtered Rows" = Table.SelectRows(#"Converted to Table", each ([Name] = "total_count")),
                    Value = #"Filtered Rows"{0}[Value]
                in
                Value,

    //Find number of calls and Round up
    APICalls = Number.RoundUp(TotalNumber /200),

    //List.Generate - To create the dynamic URL 
    LG=List.Select(List.Generate(
                        ()=>[i=-1,newURL = null], 
                        each [i] <= APICalls,
                        each [i=[i]+1,newURL=URL & Number.ToText(i)&"&Key="&APIKey],
                        each try CallAPIURL([newURL]) otherwise null
            ), each _ <> null),

    
    #"Converted to Table" = Table.FromList(LG, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandListColumn(#"Converted to Table", "Column1"),
    #"Expanded Column2" = Table.ExpandRecordColumn(#"Expanded Column1", "Column1", {"id", "name", "status", "joined", "city", "country", "localized_country_name", "state", "lat", "lon", "photo", "group_profile", "bio"}, {"id", "name", "status", "joined", "city", "country", "localized_country_name", "state", "lat", "lon", "photo", "group_profile", "bio"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Expanded Column2",{{"joined", Int64.Type}}),

    //Added custom column to update UNIX date time to normal date format
    #"Added Custom" = Table.AddColumn(#"Changed Type", "DateJoined", each #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0,[joined]/1000)),

    #"Removed Columns" = Table.RemoveColumns(#"Added Custom",{"joined"}),
    #"Expanded photo" = Table.ExpandRecordColumn(#"Removed Columns", "photo", {"id", "highres_link", "photo_link", "thumb_link", "type", "base_url"}, {"photo.id", "photo.highres_link", "photo.photo_link", "photo.thumb_link", "photo.type", "photo.base_url"}),
    #"Expanded group_profile" = Table.ExpandRecordColumn(#"Expanded photo", "group_profile", {"role", "intro"}, {"group_profile.role", "group_profile.intro"})
in
    #"Expanded group_profile"

I created two Parameters, one for MeetupID and another one for API Key. Then I kind of used a subquery to find TotalMembers. I divided the Total number with 200 to calculate the number of API calls I needed. After that List.Generate() handles all the hard work.

It’s not only Meetup API, there are many API services which use filters like pages and offset, and List.Generate() works pretty well.

Prathy 🙂

 

 

3 comments
0 FacebookTwitterPinterestEmail

So far I have seen many Power BI usage metrics demo’s or read blog posts about usage metrics, but not actually looked in detail. Recently I started exploring App Workspaces to create some standards for how to design, publish and collaborate with Power BI reports. In that process, I thought to look at Report Usage metrics by presuming I will be able to get Power BI Reports metadata. I don’t think it benefited for what I was looking for, but I saw Report GUID!  I remember seeing this before when I was working filters in Power BI Report URL.

By looking at the URL I thought, I could use the URL for Click Through and Drill Through.  So I started exploring to see if there is any possibility of Drill Through functionality. In a nutshell, Yes! You can just point to a report URL from Power BI Report and it will open that report when you click on it, like Click Through. If you also give filters like mentioned here – https://powerbi.microsoft.com/en-us/documentation/powerbi-service-url-filters/, it can work as Drill Through too. That gives you an idea, however, If you want to see what other things I explored or did with this then carry on reading…

Drill Through Reporting in Power BI:

 

By looking at my Report URL, I can see It formed with few Id’s, which looks like GroupID, ReportID and ReportSection. Getting GroupID and ReportID is doable but what is this ReportSection? Report Section, it does have a pattern, the very first tab is Report Section, the second one is Repost Section 1 and goes on. Oh Yeah, I also understood that I better call each tab as Report Sections than tabs. (Group ID are Office 365 Group Id’s, and Report ID’s are available through Power BI Usage Metrics)

Even if you understand Report section was based on the position, it could be a bit tedious process to figure out the position of Report Section. It is not actually the position; it’s based on which section created when. At this point, I could easily guess, this info hidden somewhere in Power BI files.

Report Section:

One of my good friend Malden, who is also very talented, once showed me how easily he was getting content from Excel by renaming as zip, he also mentioned it works with many Microsoft applications. So I thought to rename Power BI file to Zip file. For my convince after unzipping, there was a welcoming folder called Report.

Inside Report folder, there were two files

By opening these files in Notepad, I noticed Layout was a Json file type, and  LinguisticSchema was XML. Also, these two files have some information about Reports.

One useful tool I knew which could pull data from these file types is Power BI. Naturally, I renamed files with respective file types and started importing into Power BI.

For both of them, I got an error. I thought something wrong with the file format. Then I checked both files with XML Format Checker and Jason Format checker. Both of them were fine. However, when I copied content from those files and saved as new copy using Notepad, they were ok to import into Power BI desktop. (No idea why. I didn’t want to dig deeper, I must have done something wrong).

 

So, once I imported data into Power BI, I found a new terminology called POD. Each Report section was called like PODx. It might be some programming terminology, which I don’t know. Whatever, after bit of clean up this is how data in my queries look like

I can join these two queries to get which Report belongs to what section.

At this stage, I can easily create URL using GroupID, ReportID and RepostSection. To make it bit more interesting (at least for me ), we can use query parameters to get ReportID and GroupID.

Using query parameters to create Drill Through URL:

I created two parameters one for GroupID and one for ReportID. Then I created two queries by returning those parameter values so that I can use them for my Drill Through URL and edit parameters from the designer.

Then I created a calculated column under Layout Query (Query with Report Section information)

ReportURL = "https://app.powerbi.com/groups/"&MAX(GroupID[GroupID])&"/reports/"&MAX(ReportID[ReportID])&"/"&'Layout'[ReportSection]

Another calculated column as follows to form a full URL with filters

RegionURL =

Var RegURL = CALCULATE(MAX(Layout[ReportURL]),Layout[Report Name]="Region")

return

SUBSTITUTE(RegURL&"?filter=Schools/Region eq '"& CALCULATE(MAX(Schools[Region]))&"'"," ","%20")



Now I have a drill through URL for each Region.

And, I can create a report like below to drill through on each Region

And it works 🙂

But what if you want to drill through to an app?

Drill through to a published App:

APP Workspaces URL – https://app.powerbi.com/groups/me/apps/8a9deb71-100d-4c43-9585-5d7738d92caa/reports/18728640-81c2-4d24-8dbf-436a51d5416a/ReportSection

In the above URL, we have AppID and ReportID; ReportID is same as App Workspaces. Just instead of GroupID, we need to use AppID and small changes in the URL. I didn’t manage to find a way to query AppID other than copying from URL.

So to the PowerBI Model, we can add another Query Parameter saying APPID, and change Drill Through URL based on the sharing.I don’t want to make this post any longer If you really want, I am sure you can write that DAX expression.

Summary:

Now the basic question, does anyone want to do it? May be No! One benefit I can see is, when Microsoft bring Drill through functionality into Power BI, it could be limited to work with in a model or with in a Workspace. But this way we can point to any URL in any workspace. Another benefit for me, it is nice to know how Power BI Service URL was structured and few other things like Report folder in Power BI file and knowing Report Section terminology 🙂

In summary, if you have a burning Drill through the problem, then this could be handy. Few quick points, works only in Power Bi Service. Filters does not work with Public publish. There is no Back button like we expect with any drill through functionality. You can point to any report in any workspace. When you click on the URL, it opens a new window/tab for each URL. It was slow for me but it could be just my internet connection. If you have more than one report, then you can use Usage Statistics and get ReportID for all reports. I am sure there must a way to get Office 365 group ID’s using Power Shell.

To be frank, very soon Microsoft is going to release Drill through functionality, so there is no point of going through all this pain other than if you love looking into Power BI files 🙂

 

Prathy 🙂

5 comments
1 FacebookTwitterPinterestEmail

Recently one of my business users came to me asking “how can I get a Clickable Card in Power BI”? He wants a card where he can show the measured value, but when he clicks on that, he wanted to cross filter other visuals. To be fair, A valid ask, but not something I have done before. After deep thought, I came up with a work around, so thought to write a quick blog post for future reference.

Let’s understand the need, assume we are looking at Products data, and this is how report looks like

In the above report, if we have 50000 rows instead of 5, it would be difficult to find those two products which are causing significant loss. Of course, we can click on the chart on the right and interact, but that’s not the point here. So how can we make that card clickable? Well, for now, we don’t have an inbuilt clickable visual. So the question is how can we get something which looks like the card but interactive.  (I am sure there are several ways of doing this, but I have gone for Treemap visual.)

Let’s see how I created a Clickable Card:

First I created a calculated table

LossTable =
CALCULATETABLE (
ADDCOLUMNS (
SUMMARIZE ( Products, Products[Product] ),
"Prod", "Prod",
"LossCount", CALCULATE ( COUNT ( Products[Profit/Loss] ), Products[Profit/Loss] = "L" )
),
Products[Profit/Loss] = "L"
)

Then I created a relationship between Products table and a new Loss table, also I have hidden LossTable for users.

Replaced existing card with a treemap, Product column on Group and Sum of Loss count on values. As I had same values for all rows in Prod column, as expected there was only one tile in Treemap.

Now I have a clickable Card look-alike, which can interact with other visuals.

Hope this helps someone out there!

Prathy 🙂

2 comments
0 FacebookTwitterPinterestEmail

I am a huge OneNote fangirl, most of the times I start my working day by opening OneNote. But yesterday I accidentally discovered that I could Embed Power BI content into OneNote. In fact not only OneNote there are several other sources we can embed into OneNote – https://support.office.com/en-us/article/Embed-content-in-OneNote-fd5abf7d-abd4-4902-8e5f-93088c45b11d?ui=en-US&rs=en-US&ad=US&=fromAR1

The Sad part is we can only embed publicly published Power BI reports, which mean anyone on the internet can view these reports. (You can learn more about Power BI publish here  – https://powerbi.microsoft.com/en-us/documentation/powerbi-service-publish-to-web/ )

How Power BI looks in One Note?

Once you have your Power BI embedded URL ready, just copy and paste it into One Note. Initially, OneNote shows a bright white refresh button with dimmed yellow Microsoft Power BI background and a hyperlink text with Power BI.When you click on the refresh button, that’s when you can see Power BI content just like you see on any other website or blog with all the interactivity and full-screen options.

This could be handy for documentation, but I don’t think I want to publish my reports to the public for documentation purpose. If we have token functionality like Embedding with Non-Power BI users then probably we can use One Note as Power Note.

However, Yesterday, I was very excited to come across this and decided to write a quick blog post for fellow OneNote lovers.

Prathy 🙂

 

3 comments
0 FacebookTwitterPinterestEmail

In my recent talk for PASS BA Marathon, I showed few formatting tricks in Power BI with my super slow laptop. I promised to write a blog post but due to some other priorities, I didn’t manage to publish this blog post in time, well better late than never!

You can watch recorded Session here:

or just some highlights here:

Adding Gif images to Power BI:

People who knew me in person, I am sure they know I like pictures. However, so many pictures on a dashboard are undoubtedly not a good idea. Stephen Few mentioned, “Art has its place but not on the Dashboard”. Here, I am not at all recommending to put loads of GIF’s on the dashboard, but if you want to go crazy with your dashboard or want to get people attention, it’s not a bad idea. Well as Stephen Few mentioned in another article, It’s nice to highlight what needs attention.

Adding GIF images is quite easy. First I created a measure with value 0. I named this measure as BGGif, so I know I have to hide it from users, and it will be easy to identify in the model. It is not mandatory to create a measure, you can use an existing one, but creating a separate one makes it easy. Then I placed Line Chart( you can use other charts, but Line Chart makes it a bit easy). Then I went to format options of Line Chart, under Plot Area, for Add Image option I chose the GIF image. Based on the data colours, there is a chance of spotting a Lil dot; if you don’t want it, just move BGGif value to tool tips. I like the idea of putting Gif images; you can create some cool ones like below 🙂  I read or heard using Plot Area of charts for gif images, I don’t remember where exactly it was. Whoever it was, thank you 🙂

Dynamic Background:

There are several ways we can do this based on our use case. In my demo, I just wanted to show a background in green if the price <=100 if not Red.

First I created a measure with a bit of DAX. Again, I am going to prefix it with BG and make sure I hide it for business users.

PriceBG = if([Sum Of Price]<=100,"http://cdn30.us1.fansshare.com/image/wallpapergreen/pix-for-gt-plain-mint-green-wallpaper-mint-green-plain-background-wallpaper-wallpaper-mint-green-999507947.jpg" , "http://www.publicdomainpictures.net/pictures/200000/velka/plain-red-background.jpg")

In the measure, it is just a URL with Red and Green backgrounds. I changed the data category of measure to Image URL. Then I used Image custom visual to display PriceBG measure. One problem with this approach is if URL brakes, visuals will not work.

Using Unicode:

I first saw this technique when Will Thomson showed at UK Power BI Summit also recently Chris Webb blogged about using Unichar() function.  In my demo, I just copied Emoji’s from the Web and placed them in my measure, but if you want you can use UNICHAR() function as Chris explained.

To show #Beds – I created a measure like below:

#BedsEmoji = if((AirBnB[bedrooms])>=5,"?️ +",

IF(AirBnB[bedrooms]=1,"?️",

IF(AirBnB[bedrooms]=2,"?️ ?️",

IF(AirBnB[bedrooms]=3,"?️ ? ?️️",

IF(AirBnB[bedrooms]=4,"?️ ?️ ?️ ?️","0")))))

And for an overall review, I used Sentiment Analysis, based on sentiment analysis value; I created another measure:

ReviewEmoji = if(SUM([Score])<=.50,"?","?")

These emoji looks great on Power BI Service than PowerBI Desktop. I love the functionality of using strings in DAX measures, it enables us to use Unicode and advantages are unlimited. As I blogged in one of my previous blog posts, you can use these to highlight a specific data point in a chart. Or as Chris blogged, we can use Unicode in many ways.

Hope this blog post gives you some ideas, and again I am sorry for not publishing blog post earlier. You can download the PBI file here – https://1drv.ms/f/s!Avm7gbgZtlMlpx4qFeK1Qu15NcpE and view reports here – https://app.powerbi.com/view?r=eyJrIjoiNzUxYzRmNjUtNDA4Mi00MWI0LWJiNWQtMDRiZWQwZmFjYjE0IiwidCI6IjM4YmU3MjU5LTMyMGQtNDZkYy04YzNiLWNkZDA5OTM1NGExMyIsImMiOjh9

Keep Smiling,

Prathy ?

 

0 comment
1 FacebookTwitterPinterestEmail

For me, Stephen Few has always been the guru of Data visualisations. Recently, while I was reading one of his paper, I came across most talked Bullet Chart and Highlighting a particular value. Then the first thought that came to my mind was, Can I do it in Power BI?

Bullet Chart is not one of the built-in visual in Power BI Desktop, but it is available as a custom visual. In fact, we have two bullet charts available one by Microsoft, and one by SQL BI / Ok Viz and they both are excellent.

Highlight a Specific Data Point:

My idea was to create something similar to following report which was originally shared by Stephen Few at http://perceptualedge.com/articles/misc/WhyMostDashboardsFail.pdf .

As of now, with handy bullet charts and formatting options, highlighting is not something out of the box. However, by using a bit of DAX, I can do something similar.

For this example, I imported top grossing movies data; I just want to see which movie has the max difference when I compare between overseas and worldwide.  Whatever the observations I do on my dashboard, I want to analyse it with the difference measure. Hence that’s the key on my dashboard. So I would like to distinguish that value with others data items.

If you look at the following example, on the right, I can easily see which movie has the most difference. But on the left, having so many categories, it is a challenging task to identify the single category which has the max value.

So what have I done:

First I created a calculated column to calculate the difference

Difference = 'World Wide Gross'[Worldwide]-'World Wide Gross'[Overseas]

Another measure to calculate the MaxDifference of only data which has been selected

MaxDiff = CALCULATE(MAX([Difference]),ALLSELECTED('World Wide Gross'))

Then I created a flag, with a simple if statement to show a circle icon when the difference is equal to Max difference otherwise 0.

Flag = IF([MaxDiff]=sum([Difference]),"? ",0)
Followed by a new column to show the Circle icon, which has the max difference.
Title with Flag = IF([Flag]=0, [Title], CONCATENATE([Flag],[Title]))

I can hide all the measures and columns I don’t need and use the Title with Flag, where ever I want. With Lil bit of DAX, I created following reports.  Now I can easily find the Star Wars movie among many other movies. Thanks to Will Thomson, who showed me the trick of using Unicode characters in the measures.

Power BI Model Download
Till next time,

Prathy 🙂

4 comments
0 FacebookTwitterPinterestEmail