Home Power BI
Category:

Power BI

Power BI Bookmarks, Synced Slicers and Selections; these features transformed the way we report using Power BI. Now, most of the business users want reports with actionable interactivity. Well, it has pros and cons; today’s post is not to discuss that but to talk about Synced Slicers.

Synced Slicers were one of the most requested features. As the name says, this functionality enables you to Sync slicers among various Power BI Report pages, and you can manage the properties of it under Sync Slicers Pane.

Creating Synced Slicers:

For me there are two approaches to carry out Sync Slicers:

  1. Slicer First – Create Slicer, then sync to other pages
  2. Report First – Design the report page then sync. If you use the same field on different pages, Sync Slicers automatically detects and come up with a dialogue saying do you want to sync.

I usually prefer to go for Slicer first. If I knew I need Synced Slicers, then I will try to be consistent with the report layout and position of slicers on each page. With Slicer first approach, we create Slicers then using Sync Slicers pane; we push the Synced Slicers to the pages we want. This method helps you to place the slicer exactly at the same place on all the page. Not only place, but it also keeps the formatting too.

If I don’t want to be consistent with formatting and place of the Slicer Viz then I will go for the second method. Where I place the slicer where ever I want then Sync using Synced Slicers Pane.

That’s it, it is straightforward and works like a charm. But what if you want to Sync within a Page? You may ask why would you ever want to Sync within the Page. We will look at it in my next blog post, for now, let’s assume for a valid cause. That’s when Advanced Options comes to the rescue.

Synced Slicers Advanced Options:

You can find advanced options in Sync Slicers window, just below all pages.

As per Microsoft docs:

“This feature lets you create a custom group of slicers to keep synchronized. A default name is provided, but you can use any name you prefer.

The group name provides additional flexibility with slicers. You can create separate groups to sync slicers that use the same field, or put slicers that use different fields into the same group.”

First, let’s look at creating groups to sync slicers that use the same field. The use case Syncing within a page, we can easily use the group functionality to do this.

For example, here I have two slicers, both slicers come from the same field.

If I don’t have them in a group, when I select using one slicer other slicer gets cross filtered like below

But if I keep them in a group and choose Sync field changes to other slicers, I won’t have that problem. This enables me to have two synced slicers within a page.

Next Creating groups to sync slicers that use different fields. This was interesting, You can add slicers with different fields into one group, and all the pages which have a slicer with this group will sync. I didn’t understand it quickly, but with the help of Will Thomson, I understood the beauty of it.

For example, consider having a sales table with Order Date, Delivery Date and Product. You want to find all products which were purchased and delivered on the same day; then you can use this functionality.

For example here I have a table with Order Date, Delivery Date and Product

Order Date

Delivery Date

Product

1 January 2019

2 January 2019

Apples

2 January 2019

10 January 2019

Pine Apple

3 January 2019

3 January 2019

Avocado

12 January 2019

28 January 2019

Apples

15 January 2019

15 January 2019

Pine Apple

If I create a report like this:

If I don’t keep both of those slicers in the same group, by default my report will interact like below:

But if I keep both slicers under one group, my report will behave differently:

An intresting thing to notice here is when two fields are in same group, it basically add the data from one slicers to another one. Sounds a bit tricky but it does work.

We can see if a slicer is Synced using Pages or group by looking at Sync Slicers pane. In the SYNC SLICERS pane, page synced slicers will be highlighted with Yellow tick box and Grouped Synced Slicers will be highlighted with Grey tick. Also, note a slicer can be part of the group and can also be synced page wise. I also noticed Add and Sync all pages in one click. I never knew this exists.

 

Summary:

I use sync slicers all the time. My clients love this functionality. Sync slicers are not great for data analysis but great for story telling. Will I use Slicers grouping functionality? I am not sure. But it’s definitely worth knowing the feature.

Keep Smiling,
Prathy 🙂

4 comments
1 FacebookTwitterPinterestEmail

In my current project, one of the user requirement was to have a filter on the Year Slicer. To explain in detail, we have various measures to show metrics for current and earlier measures. For example 2016,2017 and 2018. In 2016, we always have blank values for Last Year metrics, having empty values don’t tell the story well. So to tell the story, we need to pull three years worth of data but display only two years in the Slicer. The easiest way to handle this situation would have had a visual level filter on the Year slicer.

Power BI Slicers doesn’t support Visual Slicers. However, with the help of Selection Pane and Sync slicers, I did a quick workaround. I created another slicer for Year attribute, let’s call it Slicer-0. Updated the Visual Interactions of the original slicer (let’s call it Slicer-1) to not to filter the Slicer-0. As I have Synced Slicers on all pages, so I synced Slicer-0 on all pages. And I made sure; I updated the Visual Interactions on all Pages. Then hidden the Slicer-0 on all pages

Things to remember:

A hidden slicer means hidden logic in the Model. Whenever I design Power BI Models, I try to keep up a page with Designer Notes by specifying things like this. Basically, things which are helpful during troubleshooting. So if you are using hidden slicers, make sure you note it somewhere.

Another thing to note is, under Filters and Slicers affecting this Visual will show extra slicer information in the Pop-Up. If you want you can disable the Filter Icon under Visual Headers. But I prefer having the Filter option enabled.

Prathy 🙂

1 comment
0 FacebookTwitterPinterestEmail

It has been a while since I written a blog post, even my ten years old daughter started teasing saying “Motionless blog”, so decided to get back to blogging with a short and simple blog post based on a question I received about one of my Power BI portfolio report Global Landslides Data. Few people asked me how I created the slicer in this report,

So how I created the Dynamic Legend in Power BI Visual, Line Chart :

I was using slicer as a button to dynamically choose the legend I wanted to show in the line Chart. In fact, it was relatively straightforward:

First I created an aggregated table like below

Table =
UNION(
//Incidents
SUMMARIZE('Global Landslide Data'
,'Global Landslide Data'[ID]
,'Global Landslide Data'[Date]
,"Slicer","Incidents"
,"Incidents",'Global Landslide Data'[Incidents]
,"Distance",BLANK()
,"Injuries",BLANK()
,"Fatalities",BLANK()
)
,//Distance

SUMMARIZE('Global Landslide Data'
,'Global Landslide Data'[ID]
,'Global Landslide Data'[Date]
,"Slicer","Distance"
,"Incidents",BLANK()
,"Distance",CALCULATE(SUM('Global Landslide Data'[Distance]))
,"Injuries",BLANK()
,"Fatalities",BLANK()
)
,//Injuries

SUMMARIZE('Global Landslide Data'
,'Global Landslide Data'[ID]
,'Global Landslide Data'[Date]
,"Slicer","Injuries"
,"Incidents",BLANK()
,"Distance",BLANK()
,"Injuries",CALCULATE(SUM('Global Landslide Data'[Injuries]))
,"Fatalities",BLANK()
)
,//Fatalities

SUMMARIZE('Global Landslide Data'
,'Global Landslide Data'[ID]
,'Global Landslide Data'[Date]
,"Slicer","Fatalities"
,"Incidents",BLANK()
,"Distance",BLANK()
,"Injuries",BLANK()
,"Fatalities",CALCULATE(SUM('Global Landslide Data'[Fatalities]))
)
)

The logic is to create a table with the DAX function UNION. Each Table expression in UNION function represents a value of slicer. Apart from that slicer related value, all the rest of the values are blanks.  It is key to have them as blanks than zero’s, we don’t see any data.

Then I chose the slicer column as the value in my Slicer visual and allowed slicer to have multiple selections. Then in the visual, I have used all measures as values, so based on the slicer selection it shows the trend line.

You can see it in the report here – https://app.powerbi.com/view?r=eyJrIjoiN2I4YWI4MjMtYmIyYy00ZjRkLWFjYTktZjM1ZjIwODk0ZjkzIiwidCI6IjM4YmU3MjU5LTMyMGQtNDZkYy04YzNiLWNkZDA5OTM1NGExMyIsImMiOjh9

Till next time,

Prathy 🙂

4 comments
2 FacebookTwitterPinterestEmail

One of those days where I spent a reasonable amount of time trying to solve a tricky DAX expression and once I got it; I came to know it was not at all tricky. In fact, it was far too simple to admit the time spent 🙂

When I showed PARALLELPERIOD and SAMEPERIODLASTYEAR measures to my business user, he asked me if he can select random quarters and measure should always get the variance from the earlier period based on the slicer choice than Time intelligence.

This is how my data looked like:

To get the Selected Previous Period value, I first created a variable to get the Current period value

VAR CurrentPeriod = MAX(Table1[Period])

then I created another table variable to get the selected values from the slicer.  I used CalculatedTable with ALLSelected(), so my context works.

VAR Selectedvalues = CALCULATETABLE(VALUES ( Table1[Period] ),ALLSELECTED(Table1))

Then I created another variable to get the earlier period value based on Filter.

VAR PP=CALCULATE(MAX(Table1[Period]),FILTER(Selectedvalues,Table1[Period]<CurrentPeriod))

And finally, in Return statement, I used the Value column with SUM and filter. Here is the full DAX expression of the measure

Previous Period =

VAR CurrentPeriod = MAX(Table1[Period])

VAR Selectedvalues = CALCULATETABLE(VALUES ( Table1[Period] ),ALLSELECTED(Table1))

VAR PP=CALCULATE(MAX(Table1[Period]),FILTER(Selectedvalues,Table1[Period]<CurrentPeriod))

Return

CALCULATE(SUM(Table1[Val]),Table1[Period]=PP)

If you want to try yourself, here is the M Script for Query Editor.

let

Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WCjRU0lEyNFCK1QGyjUBsQwjbGMQ2grBNQGxjpdhYAA==", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type text) meta [Serialized.Text = true]) in type table [Period = _t, Val = _t]),

#"Changed Type" = Table.TransformColumnTypes(Source,{{"Period", type text}, {"Val", Int64.Type}})

in

#"Changed Type"

It was a good exercise, I certainly learned how to not to think when working with DAX! Hope it helps someone out there.

URL to download the PBIX – https://1drv.ms/u/s!Avm7gbgZtlMlwFB99Ti5W2_eYsjU

Keep Smiling,

Prathy 🙂

4 comments
1 FacebookTwitterPinterestEmail
ADDING A VERTICAL LINE OR MARKER TO A CHART IN POWER BI

A business user came to me asking “how can I get a vertical line on Line Chart in Power BI like my Excel report?”. I was like, “It’s straightforward. We have this analytics pane which lets you add so many types of lines 😊 “. But, quite quickly I understood, I cannot draw a vertical line as we do in Excel or SSRS.

Adding a vertical line or marker to a chart in Power BI

Data looks like below:

The user wants a marker, ideally a line to point those events on the chart. I cannot think of an out the box option, but there are some workarounds. Let’s look at adding a line using Combo Chart. This chart clearly shows events as bars and tooltips can define what kind of event it was. I would prefer to change the bar width. It’s a bar; it’s not exactly a line; however, it fits for the purpose.

 

Next using Markers:

With latest Power BI update, we can set line stroke width to zero. So I decided to use that feature to highlight the events on the chart. It’s a simple Line Chart with one axis and two values. One value is to show the Value column and other to show Count of Event. My chart looks like this:

Then I updated X Axis type to Categorical (Otherwise, Markers functionality will not be available). Then under Shapes, set Show marker to On, followed by Customize series option to On. Then I turned off marker for Value column, On for Count of Event.

Then I decreased Stoke width to 0 and increased Marker size to 16. Now the chart looks like this:

Tooltip don’t make much sense, but by using new report tool tip pages functionality we can have much more helpful and user-friendly tooltip like below:

Hope this post helps someone out there
Prathy 🙂
3 comments
3 FacebookTwitterPinterestEmail
REPLACE ALL NUMERIC VALUES IN MULTIPLE COLUMNS USING #POWERQUERY

A quick post more for my future reference. Hoping it helps some one out there!

While I was working on a Power BI report with Commonwealth Games dataset, I came across an issue where I had to replace all numeric values with a text value. There are some very useful blogs posts about replacing multiple values in Power Query. With my issue, I wanted to replace only numeric values. Instead of replacing each column, I tried to replace all!
My data looked like below:

I looked upon all available Replace functions of Text, Lists and Tables. As Imke mentioned on her blog, I decided to go for List.ReplaceMatchingItems. As I wanted to replace only numeric values, I used Value.Is(Value.FromText(_), type number)  to check the Type of value for each row. I replaced value only when my condition returns true. So I combined all required columns into a single column and used Text.ToList function to convert to a List. The condition I used for Type checking works great. But, when I converted the combined columns to Text, then to a List; each character in Text value converted into List as a different List Item. E.g. 12 would convert to {{1},{2}}. When I replace each by checking the data type, I will be replacing 1 and 2 individually, which will cause duplication in the result, like below:

 

I have to admit; I searched so much to figure out a way to covert Text value to a List using a delimiter.I felt silly after I looked at one my earlier post where I was using Text.Split() function. After a good number of failed attempts, I used Text.Split() function and then I used that List for replacements. And my condition Value.Is(Value.FromText(_), type number) works great.

 

Here is the script:

let
    Source = Web.Page(Web.Contents("https://en.wikipedia.org/wiki/2018_Commonwealth_Games")),
    Data4 = Source{4}[Data],
    #"Replaced Value" = Table.ReplaceValue(Data4,"#(cr)#(lf)"," -",Replacer.ReplaceText,{"April", "April2", "4 #(lf)Wed", "5 #(lf)Thu", "6 #(lf)Fri", "7 #(lf)Sat", "8 #(lf)Sun", "9 #(lf)Mon", "10 #(lf)Tue", "11 #(lf)Wed", "12 #(lf)Thu", "13 #(lf)Fri", "14 #(lf)Sat", "15 #(lf)Sun", "Events"}),
    #"Replaced Value2" = Table.ReplaceValue(#"Replaced Value","#(lf)","",Replacer.ReplaceText,{"April", "April2", "4 #(lf)Wed", "5 #(lf)Thu", "6 #(lf)Fri", "7 #(lf)Sat", "8 #(lf)Sun", "9 #(lf)Mon", "10 #(lf)Tue", "11 #(lf)Wed", "12 #(lf)Thu", "13 #(lf)Fri", "14 #(lf)Sat", "15 #(lf)Sun", "Events"}),
    #"Promoted Headers" = Table.PromoteHeaders(#"Replaced Value2", [PromoteAllScalars=true]),
    #"Renamed Columns" = Table.RenameColumns(#"Promoted Headers",{{"April_1", "Type"}}),
    #"Removed Bottom Rows" = Table.RemoveLastN(#"Renamed Columns",1),
    #"Filtered Rows" = Table.SelectRows(#"Removed Bottom Rows", each ([Type] <> null)),
    #"Replaced Value1" = Table.ReplaceValue(#"Filtered Rows", each {"4 -Wed", "5 -Thu", "6 -Fri", "7 -Sat", "8 -Sun", "9 -Mon", "10 -Tue", "11 -Wed", "12 -Thu", "13 -Fri", "14 -Sat", "15 -Sun"} ,each if Value.Is(Value.FromText(_), type number) = true then "_" else "_",Replacer.ReplaceText,{"4 -Wed", "5 -Thu", "6 -Fri", "7 -Sat", "8 -Sun", "9 -Mon", "10 -Tue", "11 -Wed", "12 -Thu", "13 -Fri", "14 -Sat", "15 -Sun"}),
    #"Added Custom" = Table.AddColumn(#"Replaced Value1", "Custom", each Value.Is(Value.FromText([#"15 -Sun"]), type number)),
    ColumnNames = List.Select(Table.ColumnNames(#"Filtered Rows"), each Text.Contains(_,"-")= true),
    CombineColumns = Table.CombineColumns(#"Filtered Rows",ColumnNames ,Combiner.CombineTextByDelimiter(",", QuoteStyle.None),"Merged"),
    #"Added Custom1" = Table.AddColumn(CombineColumns, "Custom", each Text.Split([Merged],",")),
  #"Added Custom2" = Table.AddColumn(#"Added Custom1", "Custom.1", each List.Transform([Custom], each if Value.Is(Value.FromText(_), type number) = true then "GME" else _)),
    #"Removed Other Columns" = Table.SelectColumns(#"Added Custom2",{"April", "Type", "Events", "Custom.1"}),
    #"Extracted Values" = Table.TransformColumns(#"Removed Other Columns", {"Custom.1", each Text.Combine(List.Transform(_, Text.From), ","), type text}),
    #"Split Column by Delimiter" = Table.SplitColumn(#"Extracted Values", "Custom.1", Splitter.SplitTextByDelimiter(",", QuoteStyle.Csv), ColumnNames )
in
    #"Split Column by Delimiter"

This may not be worthy to be a blog post especially when there are already some good posts covering the similar topic, but I spent a fair bit of time on this so thought of writing a post!

Keep Smiling,

Prathy 🙂

0 comment
0 FacebookTwitterPinterestEmail

I love automating things, I have read somewhere, a good developer always tries to automate things, but in my case, I am just lazy, so I try to automate! That is why I started exploring various Power BI related API’s. I was working towards a single resource which I can use to answer questions related to Power BI Tennant. During this process I have written different posts:  Power Bi Template to document the Power Bi Service, HOW TO FIND ALL POWER BI USERS IN AN ORGANISATION? I would recommend reading those posts as they cover how I created Azure Authorization Token a bit more in detail. So, after exploring all those API’s, I still had few unanswered questions like:

  • What’s happening on my tenant?
  • Who is doing what?
  • Users interest in?
  • Should I disable export or print etc.?
  • Does anyone ever users dashboards?
  • Using workspaces is a good idea?
  • Who updated the App?
  • Many more

These questions guided me to Power BI Admin Auditing; it has all the data I wanted. But, I didn’t want to do search and export every single time I wanted to do something, and I am rubbish with PowerShell, that’s out of scope for me, there I went for searching for another API …

Office 365 Management API:

Office 365 management API enables you to do many management tasks using API, like Audit search.

Office 365 Management API and Power BI:

I don’t know why I play with API’s, most of API service providers are very good at sending pointless error information, and I had no special luck with Office 365 API. Among all Office 365 API was a bit forgiving one. The root URL of the API is https://manage.office.com/api/v1.0/{tenant_id}/activity/feed/{operation}. All API requests require an Authorization just like Power BI API. This page  https://msdn.microsoft.com/en-us/office-365/office-365-management-activity-api-reference covers all information about requests and various parameters.

The Activity API supports the following operations:

  • Start a subscription to begin receiving notifications and retrieving activity data for a tenant.
  • Stop a subscription to discontinue retrieving data for a tenant.
  • List current subscriptions
  • List available content and the corresponding content URLs.
  • Receiving Notifications sent by a webhook when new content is available
  • Retrieving content by using the content URL.
  • List notifications sent by a webhook.
  • Retrieve resource friendly names for objects in the data feed identified by guides.

From <https://msdn.microsoft.com/en-us/office-365/office-365-management-activity-api-reference>

To get data from API, you first need to enable a subscription. For this, we need to POST requests using Power BI. This is where I stumbled most, Chris Webb has a fantastic blog post explaining POST Requests using Power BI – https://blog.crossjoin.co.uk/2014/04/19/web-services-and-post-requests-in-power-query/

Calling office 365 API using Power BI/ Power Query

I first created several parameters: TennantID, ClientID,ClientSecret,UserName,Password,publisher identifier. The first thing is to get authorization token. Getting started with Office 365 management API explains how authentication works

let

    //App Details

    client_id = client_id,

    client_secret=client_secret,

    //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://manage.office.com",


    //User Details 

    username=username,

    password=password,
 

   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

To make sure I have a valid connection, I create a query to pull all services on my Office 365

let

    getgroups_uri ="https://manage.office.com/api/v1.0/"&TennantID&"/ServiceComms/Services ",

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

                [

                Headers= [                    Authorization="Bearer "& O365RefToken                    ]
                                  ]

                )),

    value = GetGroups[value],

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

    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"Id", "DisplayName", "Features"}, {"Id", "DisplayName", "Features"})

in

    #"Expanded Column1"

Then starting a subscription. URL to start subscription is https://manage.office.com/api/v1.0/”&TennantID&”/activity/feed/subscriptions/start?contentType=Audit.PowerBI. Below query did not work at all for a while and it started working suddenly, I genuinely don’t know why. But below is the query I used to start the subscription. When I run this script, it returns an error, but subscription was any way enabled.

let
   getgroups_uri ="https://manage.office.com/api/v1.0/"&TennantID&"/activity/feed/subscriptions/start?contentType=Audit.General",

   content = "{

        ""webhook"" : {

        ""address"": ""https://webhook.myapp.com/o365/"",

        ""authId"": ""o365activityapinotification"",

        ""expiration"": """"

        }

    }",

Auth = "Bearer "&O365RefToken,

     GetGroups =  (Web.Contents(getgroups_uri,

            [

                Headers = [#"Authorization"=Auth

            //              ,#"Content-Type"="application/json;charset=utf-8"

            ]
                ,

                Content = Text.ToBinary("")
            ]
                )),

    #"Imported JSON" = Json.Document(GetGroups,65001)
in
    #"Imported JSON"

You can view enabled subscriptions using following script

let
   getgroups_uri ="https://manage.office.com/api/v1.0/"&TennantID&"/activity/feed/subscriptions/list",


 GetGroups = Json.Document(Web.Contents(getgroups_uri, 
                [
                Headers=
                    [
                    Authorization="Bearer "& O365RefToken
                    ], 
                ManualStatusHandling={400}
                ]
                )),
    #"Converted to Table" = Table.FromList(GetGroups, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"contentType", "status", "webhook"}, {"contentType", "status", "webhook"})
in

    #"Expanded Column1"

Office 365 Management API for Power BI Audit Logging:

I am interested only in Power BI Audit, but you can use this to view other Audit logs too. As per API Schema, all Power BI Activities has RecordType 20, So I used following script to pull all Power BI Content.

let
   getgroups_uri ="https://manage.office.com/api/v1.0/"&TennantID&"/activity/feed/subscriptions/list",


 GetGroups = Json.Document(Web.Contents(getgroups_uri, 
                [
                Headers=
                    [
                    Authorization="Bearer "& O365RefToken
                    ], 
                ManualStatusHandling={400}
                ]
                )),
    #"Converted to Table" = Table.FromList(GetGroups, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"contentType", "status", "webhook"}, {"contentType", "status", "webhook"}),
    contentType1 = #"Expanded Column1"[contentType],
    #"Converted to Table1" = Table.FromList(contentType1, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
    #"Renamed Columns" = Table.RenameColumns(#"Converted to Table1",{{"Column1", "Content"}}),
    #"Invoked Custom Function" = Table.AddColumn(#"Renamed Columns", "GetContent", each GetContent([Content])),
    #"Replaced Errors" = Table.ReplaceErrorValues(#"Invoked Custom Function", {{"GetContent", null}}),
    #"Expanded GetContent" = Table.ExpandTableColumn(#"Replaced Errors", "GetContent", {"contentUri", "contentId", "contentType", "contentCreated", "contentExpiration"}, {"contentUri", "contentId", "contentType", "contentCreated", "contentExpiration"}),
    #"Filtered Rows" = Table.SelectRows(#"Expanded GetContent", each ([contentUri] <> null) and ([contentType] = "Audit.General")),
    #"Invoked Custom Function1" = Table.AddColumn(#"Filtered Rows", "Fn_GetURIData", each try Fn_GetURIData([contentUri]) otherwise null),
    #"Added Custom" = Table.AddColumn(#"Invoked Custom Function1", "Custom", each List.Count(Table.ColumnNames([Fn_GetURIData]))),
    #"Removed Errors" = Table.RemoveRowsWithErrors(#"Added Custom", {"Custom"}),
    #"Added Custom1" = Table.AddColumn(#"Removed Errors", "Custom.1", each Table.Contains( [Fn_GetURIData],[Workload="PowerBI"])),
    #"Filtered Rows1" = Table.SelectRows(#"Added Custom1", each ([Custom.1] = true)),
    #"Expanded Fn_GetURIData" = Table.ExpandTableColumn(#"Filtered Rows1", "Fn_GetURIData", {"Id", "RecordType", "CreationTime", "Operation", "OrganizationId", "UserType", "UserKey", "Workload", "UserId", "ClientIP", "UserAgent", "Activity", "ItemName", "WorkSpaceName", "DatasetName", "ReportName", "WorkspaceId", "ObjectId", "DatasetId", "ReportId"}, {"Id", "RecordType", "CreationTime", "Operation", "OrganizationId", "UserType", "UserKey", "Workload", "UserId", "ClientIP", "UserAgent", "Activity", "ItemName", "WorkSpaceName", "DatasetName", "ReportName", "WorkspaceId", "ObjectId", "DatasetId", "ReportId"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Expanded Fn_GetURIData",{{"Content", type text}, {"contentUri", type text}, {"contentId", type text}, {"contentType", type text}, {"contentCreated", type datetime}, {"contentExpiration", type datetime}, {"Id", type text}, {"RecordType", Int64.Type}, {"CreationTime", type datetime}, {"Operation", type text}, {"OrganizationId", type text}, {"UserType", Int64.Type}, {"UserKey", type text}, {"Workload", type text}, {"UserId", type text}, {"ClientIP", type text}, {"UserAgent", type text}, {"Activity", type text}, {"ItemName", type text}, {"WorkSpaceName", type text}, {"DatasetName", type text}, {"ReportName", type text}, {"WorkspaceId", type text}, {"ObjectId", type text}, {"DatasetId", type text}, {"ReportId", type text}, {"Custom", Int64.Type}, {"Custom.1", type logical}}),
    #"Removed Columns" = Table.RemoveColumns(#"Changed Type",{"Content", "contentUri", "contentId", "contentType", "contentCreated", "contentExpiration", "RecordType", "OrganizationId", "UserKey", "Custom", "Custom.1"}),
    #"Duplicated Column" = Table.DuplicateColumn(#"Removed Columns", "UserId", "UserId - Copy"),
    #"Split Column by Delimiter" = Table.SplitColumn(#"Duplicated Column", "UserId - Copy", Splitter.SplitTextByDelimiter("@", QuoteStyle.Csv), {"UserId - Copy.1", "UserId - Copy.2"}),
    #"Changed Type1" = Table.TransformColumnTypes(#"Split Column by Delimiter",{{"UserId - Copy.1", type text}, {"UserId - Copy.2", type text}}),
    #"Removed Columns1" = Table.RemoveColumns(#"Changed Type1",{"UserId - Copy.2"}),
    #"Replaced Value" = Table.ReplaceValue(#"Removed Columns1","."," ",Replacer.ReplaceText,{"UserId - Copy.1"}),
    #"Renamed Columns1" = Table.RenameColumns(#"Replaced Value",{{"UserId - Copy.1", "Name"}, {"WorkSpaceName", "WorkSpace Name"}, {"ReportName", "Report Name"}, {"DatasetName", "Dataset Name"}, {"CreationTime", "Creation Time"}})
in
    #"Renamed Columns1"

And we can create beautiful reports like this

Power BI Template:

I also created a template; you can download the template here –  https://1drv.ms/u/s!Avm7gbgZtlMlvkMAQEGMyQdLsfHu

When you open the template it will ask for loads of the parameters:

  • Get data for last 24 months  – A boolean value to pull data for last 24 hours or the given date. If this value is true, Power BI excludes values given in the next two parameters
  • AuditlogStartTime & AuditLogEndTime -Start Time of the Audit, Audit start and end has to be with 24 hours apart as per API documentation. and date has  to be in below format

YYYY-MM-DD
YYYY-MM-DDTHH:MM
YYYY-MM-DDTHH:MM:SS

  • TennantID – Office 365 Tennant ID
  • PubisherIdentifier – I have not used this, but it is recommended to use this for API calls
  • ClientID – Azure AD Client ID
  • ClientSecret – Azure AD App Client Secret
  • UserName& Password – User who has permission to view Office 365 Audit log

So does it answer my questions:

Well, I still have many unanswered questions, but this does help me in many ways. Using this template I can see the user behaviour, not only how many views etc. but interesting information about user activities. For example, in a certain workspace, if most of the users are exporting the report, then users are not happy with the way I presented the data.  Ideally, I don’t encourage users to be added to the workspace, using this I can see who are adding members to groups. I can see when a Power BI App created or updated. I can see how many users are printing the report, if many people are printing reports, probably I should not use many bookmarks, drill through functionality and add more Titles.

Summary:

There are many things I learned doing this process, PowerQuery Web.Contents can be used to POST and ZoomCharts introduced a logo underneath the visual to encourage users to buy their visuals 🙂

This entire model has many limitations, first of all, I can not schedule it as I am using two different API’s one to get Token and other to call API, but if you have Power BI Report Server, this can be scheduled. Next, data is always only for 24 hours, this is a limitation from API and I can go back to 7 days only.  Users need access to the Azure AD App and make sure Azure AD App has access to Office 365 Management API access. This page explains all steps – https://msdn.microsoft.com/en-us/office-365/get-started-with-office-365-management-apis . 

Hope this helps someone out there

Keep Smiling,

Prathy 🙂

25 comments
4 FacebookTwitterPinterestEmail

Have you ever ended up in a situation where someone already did all the work to get the right brand colours, created a Power BI theme file and left the company. Then you go there to do some quick reports; business wants that long time signed off  theme that they used in other reports, but no one knows where that theme file exists? I have!  I was asked to create Power BI files which looked similar to the ones they had; existing ones had themes and backgrounds, but no one knows where they were.

Where are my Power BI theme file and background images?

By now, many of us know Power BI file can be renamed to a .zip file to see the contents. I previously blogged here how to zip PBI file and how we can use this for Drill Through functionality. So, I was pretty sure; if I imported a Jason file as a theme into Power BI Desktop, it should be saved somewhere inside. I can unzip the  model and find all imported files; I was not wrong 😊

So the steps to see imported files are simple:

  1. Copy the existing Power BI file
  2. Change file type to .zip
  3. Unzip it
  4. In the unzipped file, go to \Report\StaticResources\RegisteredResources
  5. That’s all; that’s where your theme file, background images or any other images you used in the model resides

Hoe this helps!

Prathy 🙂

0 comment
2 FacebookTwitterPinterestEmail

Recently, I worked on an interesting problem. Datasource I was working with was a SharePoint list, working with SharePoint lists always turns out to be a difficult task than I first assumed. On top of that, with this particular data source; data was coming as semicolon delimited text. Basically, there were many columns like Mile Stones and Sub Milestones, their related data and so on.

Initially, when I looked at it, I thought, I could just split columns in a jiffy; apparently, I didn’t think through it. To cut a long story short, I had to find a way to split multiple columns into rows at the same time so that I can visualise it in a Gantt chart format.

Split multiple columns into rows in #PowerQuery:

Here is an example dataset. My data looked something like below; the real data set has far too many columns and much more data in each cell.

Name Milestones Sub Milestones
Prathy m1;M2;M3;M4;M5 s1;s2;s3;s4;s5

If I split Milestones and Sub Milestones one column after other by using Splitter.SplitTextByDelimiter function, this is how my data will look like. There is no way I can find the which sub-milestones belongs to what.( Well, there must be away, but def, not an easy way)

So, instead of splitting each column, I Split each row using function Text.Split and added it as a new column. Text.Split returns a list. Later, I used all these lists to create a column using Table.FromColumns() function which returned a table for me. When I expand the table, I have all my columns split nicely the way I want.

Power Query Script:

let
    Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("i45WCihKLMmoVNJRyjW09jWy9jW29jWx9jUFChQbWhcbWRcbWxebWBebKsXGAgA=", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type text) meta [Serialized.Text = true]) in type table [Name = _t, Milestones = _t, SubMilestones = _t]),
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Name", type text}, {"Milestones", type text}, {"SubMilestones", type text}}),
    #"Added Custom" = Table.AddColumn(#"Changed Type", "Custom", each Text.Split([Milestones],";")),
    #"Added Custom1" = Table.AddColumn(#"Added Custom", "Custom.1", each Text.Split([SubMilestones],";")),
    #"Added Custom2" = Table.AddColumn(#"Added Custom1", "Custom.2", each Table.FromColumns({[Custom],[Custom.1]})),
    #"Expanded Custom.2" = Table.ExpandTableColumn(#"Added Custom2", "Custom.2", {"Column1", "Column2"}, {"Column1", "Column2"}),
    #"Removed Other Columns" = Table.SelectColumns(#"Expanded Custom.2",{"Name", "Column1", "Column2"})
in
    #"Removed Other Columns"

I did this with multiple steps for better readability, but you can fit all statements in a single statement as well. I kind of feel like, there must be an inbuilt function which does this, but I haven’t discovered it yet, if you knew, please do let me know 😊

You can download the sample file here – https://1drv.ms/u/s!Avm7gbgZtlMlvjUCCvb981Ga_AVB

Keep smiling 😊

Prathy

 

 

 

 

 

5 comments
4 FacebookTwitterPinterestEmail

Back in September, I promised I would write another blog post with a beautiful looking Power BI template. Even though I did the model long ago, I did not get a chance to write a post until recently one of my blog readers reminded me. Thanks, Martin 🙂

Quick Recap:

As I mentioned in my previous blog post Calling Power BI API using Power BI Desktop to document Power BI Service, we can get a lot of metadata from Power BI service. I feel like, it is essential to have an overview of what’s happening on your tenant. The only place I know to get this information is Power BI API. I used Power BI API a data source in Power BI, I needed a bearer token to get any data from Power BI Service, so I used “https://login.windows.net/common/oauth2/token” URL to get the token. Which mean I looked at two different URL’s which restricted me to manual refresh in Power BI Desktop than having an automated schedule. Hopefully, it will change shortly.

You can use this template to answer many questions like:

  • Show me all dashboards in my organization
  • Total number of reports in the organization
  • Users who have admin rights
  • Number of refreshes
  • Number of failures
  • Why some refresh failed
  • When was the last time a certain dataset refreshed, if it’s failed why
  • And many more…

Template to Document the Power BI Service:

You can download the template here – https://1drv.ms/u/s!Avm7gbgZtlMlqGprbKHS6zHd0aPf

Above download link has a zip file with PBIT file and a CSV file. CSV file has user details required for authentication. This blog post covers the approach I used to call the API – https://powerbi.microsoft.com/en-us/documentation/powerbi-developer-register-app/.Once you updated “UserDetails.CSV” file with essential information, save it to your preferred location.

When you open the template, you will see a pop message asking Full path of the UserDetails file, e.g. C:\Users\xxxx\OneDrive\xxx\PBI Portal\PBI\UserDetails.csv. Enter file location and click on the Load button. That’s all; it should just work. Sometimes, you may get a timeout error. Most of the times refreshing again will do the trick if not go to edit queries and choose “Refresh All queries”.

The first page has drill through functionality enabled where it shows the lil driller icon. Are you impressed with above screenshots, want to know how I designed this model? Find it here – https://wp.me/p5u6Pw-AQ 

Summary

Remember, you can see all data points only if you have enough rights and level of access is maintained through Azure App. Also, you can only retrieve the data from workspaces where the username you used to call API has the access. This Power BI model gives exposure to a lot of information but not all. Using this model, I can find all users who have access to a Workspace but not to Power BI Apps. If you want to know all users on your tenant, use Graph API as I showed here – http://prathy.com/2017/11/find-power-bi-users-organisation/. Ideally, I would love to see an OData compatible API like Microsoft Graph API so that I can schedule the refresh. Also, I don’t think we have an endpoint to get Power BI Apps or username of data source connections. Power BI hides Username in data source connections, so this would be something handy to understand which username we used to set up the data source. You may have noticed many placeholders, I would like to add much more information to it, and design reports a bit nicer.  If I make any changes to the template, I will keep this post updated or write another with changes. Hope this helps someone out there! Thanks for reading 🙂

Keep smiling,

Prathy 🙂

 

10 comments
2 FacebookTwitterPinterestEmail