[Power BI] Створена Power BI версія Vertipaq Analyzer. Додано автоматичне форматування коду

На шляху повної 1:1 міграції Vertipaq Analyzer з Excel до Power BI я оновив раніше створену Power BI версію інструменту, де застосував автоматичне форматування DAX коду за допомогою DAX Formatter сервісу, який робить код гарним та читабельним. Деталі про DAX Formatter читайте тут.

Нагадаю, що Vertipaq Analyzer в Excel та Power BI версіях може підключатися до Power BI файлу чи Analysis Services Tabular моделі та аналізувати її структуру, даючи інформацію про потенційні місця для оптимізації.

Форматування в початковій версії (Excel) реалізоване за допомогою VBA, а в новій версії Vertipaq Analyzer (Power BI) це зроблене через PowerQuery.

Поки я реалізовував виклик DAX Formatter API через PowerQuery, то зіштовхнувся з деякими проблемами. Декілька слів про свій досвід далі.

Загалом, я досяг результату за допомогою створення користувацької функції PowerQuery. А проблема була в наступному. Якщо DAX формула, яку бажаєте форматувати має довжину менше ніж 2000 символів, тоді ви можете використовувати GET запит, інакше - POST. Ось код обох варіантів:

GET:
(DAXCode as text, Region as text) =>
let
    DAXCodeFull = "= " & DAXCode,
    DAXCodeURIEncoded = Text.Middle(Uri.BuildQueryString([DAXExpr = DAXCodeFull]),8),
    URIPart = "?r=US&embed=1&app=VertiPaqAnalyzer&version=1.91&fx=" & DAXCodeURIEncoded,
    Source = Web.Page(Web.Contents("http://www.daxformatter.com/" & URIPart)),
    Data = Source{0}[Data]{0}[Children]{2}[Children]{0}[Children]{0}[Children],
    #"Expanded Children" = Table.ExpandTableColumn(Data, "Children", {"Text"}, {"Children.Text"}),
    #"Added Custom" = Table.AddColumn(#"Expanded Children", "Custom", each 
                                                                            if [Name] = "BR" 
                                                                                then "#(lf)" 
                                                                                    else 
                                                                                        if [Children.Text] is null 
                                                                                            then [Text] 
                                                                                            else [Children.Text]
    ),
    #"Removed Columns" = Table.RemoveColumns(#"Added Custom",{"Name", "Children.Text", "Text"}),
    Output = Lines.ToText(#"Removed Columns"[Custom],"")
in
    if Text.Length(DAXCode) > 0 then Output else DAXCode
POST:
(DAXCode as text, Region as text) =>
let
    DAXCodeFull = "= " & DAXCode,
    DAXCodeURIEncoded = Text.Middle(Uri.BuildQueryString([DAXExpr = DAXCodeFull]),8),
    Body = "r=" & Region & "&embed=1&app=VertiPaqAnalyzer&version=1.91&fx=" & DAXCodeURIEncoded,
    Source = Web.Page(Text.FromBinary(Web.Contents("http://www.daxformatter.com/", [Headers=[#"Content-Type"="application/x-www-form-urlencoded"], Content=Text.ToBinary(Body)]))),
    Data = Source{0}[Data]{0}[Children]{1}[Children],
    #"Expanded Children" = Table.ExpandTableColumn(Data, "Children", {"Name", "Children", "Text"}, {"Children.Name", "Children.Children", "Children.Text"}),
    #"Children Children" = #"Expanded Children"{0}[Children.Children],
    #"Removed Columns" = Table.RemoveColumns(#"Children Children",{"Kind"}),
    #"Expanded Children1" = Table.ExpandTableColumn(#"Removed Columns", "Children", {"Text"}, {"Children.Text"}),
    AddColumn = Table.AddColumn(#"Expanded Children1", "Custom", each 
                                                                        if  [Name] = "BR" 
                                                                            then "#(lf)" 
                                                                            else
                                                                                if  [Children.Text] is null 
                                                                                    then [Text] 
                                                                                    else [Children.Text]
    ),
    RemoveColumns = Table.RemoveColumns(AddColumn,{"Name", "Children.Text", "Text"}),
    Output = Lines.ToText(RemoveColumns[Custom],"")
in
    if Text.Length(DAXCode) > 0 then Output else DAXCode
P.S. Використовуючи Content в Web.Contents ви конвертуєте GET запит в POST. Дивіться документацію від Microsoft - https://docs.microsoft.com/en-us/powerquery-m/web-contents.

Під час реалізації POST версії я знайшов обмеження, що результат функції Web.Contents не може бути параметром функції Web.Page, якщо ви використовуєте параметр Content в Headers. В результаті маємо помилку:


Наприклад, на основі цієї інформації, код нижче працює:

Source = Web.Page(Web.Contents("http://www.daxformatter.com/" & URIPart))

Але наступний код, ні:
Source = Web.Page(Web.Contents("http://www.daxformatter.com/", [Headers=[#"Content-Type"="application/x-www-form-urlencoded"], Content=Text.ToBinary(Body)]))

Я запитав у команди підтримки Microsoft про цю проблему. Та відповідь була наступною:

"Product team has confirmed that POST command cannot be used with Web.Page"
(Команда підтримки підтвердила, що POST команда не може бути використана в Web.Page)

Але виявилось, що це легко виправити. Тільки треба помістити Web.Contents в Text.FromBinary, а тільки потім до Web.Page:
Source = Web.Page(Text.FromBinary(Web.Contents("http://www.daxformatter.com/", [Headers=[#"Content-Type"="application/x-www-form-urlencoded"], Content=Text.ToBinary(Body)])))

І нарешті, давайте подивимость на різницю першої та другої Power BI версій Vertipaq Analyzer:
vs

Другий варіант має колонку Expression, яка має дані по DAX формулам з моделі/файлу та має гарний формат.

Нову версію можна знайти нижче чи завантажити тут.

Коментарі

Популярні дописи з цього блогу

[DAX] Динамічна відносна фільтрація в slicer за допомогою віртуальних зв'язків DAX

Power BI Reports Monitoring Tool in 5 minutes