{"id":1575,"date":"2023-04-11T09:13:12","date_gmt":"2023-04-11T07:13:12","guid":{"rendered":"https:\/\/cwiok.pl\/?p=1575"},"modified":"2023-04-11T09:13:12","modified_gmt":"2023-04-11T07:13:12","slug":"detailed-report-usage-tracking-a-pixel-story","status":"publish","type":"post","link":"https:\/\/cwiok.pl\/index.php\/en\/2023\/04\/11\/detailed-report-usage-tracking-a-pixel-story\/","title":{"rendered":"Detailed report usage tracking &ndash; a pixel story"},"content":{"rendered":"<p align=\"justify\">I always felt that the report usage extracted from Power BI is just not enough. Data from \u201cusage report\u201d is nice, but is aggregated to daily level, while Activity logs need to be retained, because they will disappear. It is also not possible to extract e.g. slicer usage. We can do better!<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1577\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel.png\" alt=\"\" width=\"1200\" height=\"628\" srcset=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel.png 1200w, https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel-300x157.png 300w, https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel-1024x536.png 1024w, https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel-768x402.png 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/a><\/p>\n<p align=\"justify\">Let me introduce to you a tracking pixel \u2013 a solution that is known and hated throughout the world wide web. Essentially, what we do by adding a pixel image URL is forcing Power BI to make a GET request to a URL that we fully control. We gather all the parameters and return an image of a pixel.<\/p>\n<p align=\"justify\">Setting up a web server that will do exactly that might be costly, but here we can simply rely on a serverless logic app in Azure. It is cheap and quite scalable, giving us full control over what we do with the data.<\/p>\n<p align=\"justify\"><strong>Setting up storage and returning a pixel<\/strong><\/p>\n<p align=\"justify\">First, let\u2019s create a storage account on Azure. We will gather all the data there. It is really the simplest solution for storage, but please do explore SQL Server or other services.<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image.png\"><img loading=\"lazy\" decoding=\"async\" style=\"margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb.png\" alt=\"image\" width=\"670\" height=\"229\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">Second, let\u2019s create a simple logic app and start developing the workflow. The idea is simple, we will use an HTTP trigger that will give me a URL to use with some parameters inside. We will be passing those parameters using DAX.<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-1.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-1.png\" alt=\"image\" width=\"484\" height=\"373\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">Next, we use a compose module to build a png pixel:<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-2.png\"><img loading=\"lazy\" decoding=\"async\" style=\"margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-2.png\" alt=\"image\" width=\"486\" height=\"164\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=<\/p>\n<p align=\"justify\">is a base64 representation of a png pixel.<\/p>\n<p align=\"justify\">Second compose operation will convert base64 to binary:<a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-3.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-3.png\" alt=\"image\" width=\"477\" height=\"181\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">Copy and paste: base64ToBinary(outputs(&#8216;Compose&#8217;))<\/p>\n<p align=\"justify\">Now, everything is ready to return a pixel to the user. We use Response module. The Body is an output from Compose 2.<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-4.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-4.png\" alt=\"image\" width=\"490\" height=\"247\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">And this already works! If you go to the URL in the trigger and paste this into the browser you should see a pixel!<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-5.png\"><img loading=\"lazy\" decoding=\"async\" style=\"margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-5.png\" alt=\"image\" width=\"670\" height=\"173\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">In run history, you should also see test values that were included in the URL (in place of values in brackets {}).<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-6.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-6.png\" alt=\"image\" width=\"648\" height=\"297\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\"><strong>Saving the data<\/strong><\/p>\n<p align=\"justify\">Now, we need to somehow save those parameters, and perhaps UserAgent to the storage. Again there are multiple ways to do it, but creating a file on Blob is probably the easiest.<\/p>\n<p align=\"justify\">Third Compose module will extract UserAgent:<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-7.png\"><img loading=\"lazy\" decoding=\"async\" style=\"margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-7.png\" alt=\"image\" width=\"564\" height=\"188\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">Code to paste: triggerOutputs()[&#8216;headers&#8217;][&#8216;User-Agent&#8217;]<\/p>\n<p align=\"justify\">Next, a compose module will be used to create a simple row with all the data. Between elements there are pipes \u201c|\u201d added, that will be used as delimiters.<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-8.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-8.png\" alt=\"image\" width=\"562\" height=\"197\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">With this, we can now move on to creating a blob on Blob Storage. The file name is going to be random to ensure no data is lost. Content is taken from Compose 4.<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-9.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-9.png\" alt=\"image\" width=\"586\" height=\"320\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">When refreshing the URL in the browser, a new file is created:<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-10.png\"><img loading=\"lazy\" decoding=\"async\" style=\"margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-10.png\" alt=\"image\" width=\"670\" height=\"148\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\"><strong>Power BI<\/strong><\/p>\n<p align=\"justify\">Now that the back-end is ready, it is time to move to the report creation. The plan is rather simple. Two pages, both with the same what-if parameter in a slicer. The pixel will extract user email, report name, page name and slicer value. This might be changed to whatever the requirement is. The only limit is DAX, so checking if slicers are used, if users multiselect values \u2013 all that can be tracked!<\/p>\n<p align=\"justify\">First step is to create a new report. Let\u2019s use enter data and create a simple table. Additionally, a what-if parameter will be created and slicer added to the page:<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-11.png\"><img loading=\"lazy\" decoding=\"async\" style=\"margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-11.png\" alt=\"image\" width=\"670\" height=\"232\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">Next, using DAX we will create a URL from the Logic App, that was created previously. The trigger URL is located in the first block of the app:<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-12.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-12.png\" alt=\"image\" width=\"589\" height=\"157\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">Using built-in DAX functions we are able to pass both user principal name and parameter value to the URL:<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-13.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-13.png\" alt=\"image\" width=\"669\" height=\"53\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">In \u201cMeasure tools\u201d it is very important to change the Data Category to \u201cImage URL\u201d:<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-14.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-14.png\" alt=\"image\" width=\"373\" height=\"160\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">Now, when this measure is added to a table, Power BI will query the URL and receive a pixel. It will also provide all the parameters, that will be saved to the blob storage.<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-15.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-15.png\" alt=\"image\" width=\"670\" height=\"137\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">A view from Blob Storage:<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-16.png\"><img loading=\"lazy\" decoding=\"async\" style=\"border: 0px currentcolor; margin-right: auto; margin-left: auto; float: none; display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-16.png\" alt=\"image\" width=\"662\" height=\"218\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">The image will be refreshed every time a user interacts with the page, this naturally allows to track the usage of the report in a much higher level of details. What is also possible is calculating a web metric called \u201cBounce rate\u201d \u2013 so how many users closed the report right after opening it.<\/p>\n<p align=\"justify\">This will require a bit of maintenance though. There will need to be a separate measure per report page and each page will need to have a table with the measure added and hidden.<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-18.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: block; background-image: none;\" title=\"image\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image_thumb-17.png\" alt=\"image\" width=\"668\" height=\"156\" border=\"0\" \/><\/a><\/p>\n<p align=\"justify\">As visible on the table, it is possible to extract significantly more information about report usage. I would not recommend tracking measures\u2019 values, but using functions like ISFILTERED() or HASONEVALUE() can give you important insights into how reports are being used. This solution may be seen as quite invasive in some organisations, so please do confirm the approach with the business. Apart from that DAX is the limit!<\/p>\n<p align=\"justify\">Let me know what you think,<\/p>\n<p align=\"justify\">Micha\u0142<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/image-18.png\">\u00a0<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p align=\"justify\">I always felt that the report usage extracted from Power BI is just not enough. Data from \u201cusage report\u201d is nice, but is aggregated to daily level, while Activity logs need to be retained, because they will disappear. It is also not possible to extract e.g. slicer usage. We can do better!<\/p>\n<p align=\"justify\"><a href=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1577\" src=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel.png\" alt=\"\" width=\"1200\" height=\"628\" srcset=\"https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel.png 1200w, https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel-300x157.png 300w, https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel-1024x536.png 1024w, https:\/\/cwiok.pl\/wp-content\/uploads\/2023\/04\/pixel-768x402.png 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/a><\/p>\n<div class=\"tech_read_more\"><a href=\"https:\/\/cwiok.pl\/index.php\/en\/2023\/04\/11\/detailed-report-usage-tracking-a-pixel-story\/\">Read More<\/a><\/div>","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-1575","post","type-post","status-publish","format-standard","hentry","category-powerbi"],"_links":{"self":[{"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/posts\/1575","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/comments?post=1575"}],"version-history":[{"count":2,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/posts\/1575\/revisions"}],"predecessor-version":[{"id":1578,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/posts\/1575\/revisions\/1578"}],"wp:attachment":[{"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/media?parent=1575"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/categories?post=1575"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cwiok.pl\/index.php\/wp-json\/wp\/v2\/tags?post=1575"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}