Tired of drowning in documentation? So am I! In this post, I’ll share some nifty tricks using DAX Info Functions to help "lazy (um, I mean efficient)" developers automate their Power BI documentation process. Because let’s face it, we all have better things to do—like refilling our coffee.
Within the Power BI ecosystem, there are at least 2 good external tools to support this process (Document Modeler and Power BI Helper). Both tools work very well, but they have an important prerequisite, as they must be accepted by the IT Organization.
In today's blog post we will look at how to leverage the newly published DAX Info functions, that will help us document our model. Those functions are using the already existing TMSCHEMA DMVs. While there are over 50 new Info functions, we will be focusing on 5 functions.
NOTE
You should go first and read our previous blog post around the DAX Query View.
To get started working with these functions, we will head over to the DAX Query View window, as all functions already have IntelliSense support and cannot be used currently as an expression of a calculated table.
Compared to the DMVs, an advantage of using the Info functions is that we can merge multiple tables for a consolidated output. This will be done by leveraging the NATURALLEFTOUTERJOIN function.
For today's blog post we will use 5 different Info functions:
Let's start by looking at an example that gets the measure details.
All "Info" tables will have a large number of columns, so we use the SELECTCOLUMNS function on the base table that we will be working with to reduce the number of columns we will be working with.
Let's look now at the sample output of the base table:
To make it simpler to use, and to be able to leverage queries individually, we need to get the Info.Tables details as well for the table name, this then merged with the support of the NATURALLEFTOUTERJOIN.
This then gets us the final output:
NOTE
You will find that some of the columns a bit too technical. In the upcoming feature, the Microsoft documentation will also describe all those elements.
You can find below the codes for each category:
--------------------------------Get Tables & Definitions-----------------------------------
EVALUATE
var _GetTables =
SELECTCOLUMNS(
INFO.TABLES(),
"TableID",[ID],
"Name",[Name],
"IsHidden",[IsHidden],
"ExcludeFromModelRefresh",[ExcludeFromModelRefresh])
var _GetTableDefinitions =
SELECTCOLUMNS(
INFO.PARTITIONS(),
"TableID",[TableID],
"QueryDefinition",[QueryDefinition],
"State",[State],
"Type",[Type])
var _TableMetadataOutput = NATURALLEFTOUTERJOIN(_GetTables,_GetTableDefinitions)
RETURN
_TableMetadataOutput
--------------------------------Get Measures & Definitions-----------------------------------
EVALUATE
var _GetMeasures =
SELECTCOLUMNS(
INFO.MEASURES(),
"TableID",[TableID],
"Name",[Name],
"DisplayFolder",[DisplayFolder],
"Description",[Description],
"Expressions",[Expression],
"IsHidden",[IsHidden],
"IsSimpleMeasure",[IsSimpleMeasure])
var _GetTables =
SELECTCOLUMNS(
INFO.TABLES(),
"TableID",[ID],
"TableName",[Name])
var _MeasuresMetadataOutput = NATURALLEFTOUTERJOIN(_GetMeasures,_GetTables)
RETURN
_MeasuresMetadataOutput
--------------------------------Get Relationships-----------------------------------
EVALUATE
var _RelationshipsMain =
SELECTCOLUMNS(
INFO.RELATIONSHIPS(),
"FromTableID", [FromTableID],
"FromColumnID",[FromColumnID],
"ToTableID",[ToTableID],
"ToColumnID",[ToColumnID],
"IsActive",[IsActive],
"FromCardinality",[FromCardinality],
"ToCardinality",[ToCardinality],
"SecurityFilterFlow",[SecurityFilteringBehavior],
"CrossFilterBehavior",[CrossFilteringBehavior]
)
var _FromTable =
SELECTCOLUMNS(
INFO.TABLES(),
"FromTableID",[ID],
"FromTableName",[Name]
)
var _ToTable =
SELECTCOLUMNS(
INFO.TABLES(),
"ToTableID",[ID],
"ToTableName",[Name]
)
var _FromTableJoin =
NATURALLEFTOUTERJOIN(_RelationshipsMain,_FromTable)
var _ToTableJoin =
NATURALLEFTOUTERJOIN(_FromTableJoin,_ToTable)
var _FromColumn =
SELECTCOLUMNS(
INFO.COLUMNS(),
"FromColumnID",[ID],
"FromColumnName",[ExplicitName]
)
var _ToColumn =
SELECTCOLUMNS(
INFO.COLUMNS(),
"ToColumnID",[ID],
"ToColumnName",[ExplicitName]
)
var _FromColumnJoin =
NATURALLEFTOUTERJOIN(_ToTableJoin,_FromColumn)
var _ToColumnJoin =
NATURALLEFTOUTERJOIN(_FromColumnJoin,_ToColumn)
RETURN
_ToColumnJoin
--------------------------------Get Columns-----------------------------------
EVALUATE
var _GetColumns=
SELECTCOLUMNS(
INFO.COLUMNS(),
"TableID",[TableID],
"Name",[ExplicitName],
"Description",[Description],
"ExplicitDataType",[ExplicitDataType],
"DataCategory",[DataCategory],
"IsHidden",[IsHidden],
"Unique",[IsUnique],
"IsKey",[IsKey],
"Isnullable",[IsNullable],
"SummarizeBy",[SummarizeBy],
"Type",[Type],
"AttributeHierarchyID",[AttributeHierarchyID],
"Expression",[Expression]
)
var _GetTables =
SELECTCOLUMNS(
INFO.TABLES(),
"TableID",[ID],
"Name",[Name],
"IsHidden",[IsHidden],
"ExcludeFromModelRefresh",[ExcludeFromModelRefresh]
)
var _TableMetadataOutput =
NATURALLEFTOUTERJOIN(_GetColumns,_GetTables)
RETURN
_TableMetadataOutput
NOTE
You could also have a second SELECTCOLUMNS function at the end of the query, if you would like to reorder the columns after the joins.
If you are just testing things out, you can also run all codes in the same DAX Query pane, and we will get 4 different outputs that we can look at. Any of those outputs can also be copied directly from the output.
You can also save those queries in the Power BI Desktop file, to make it easier to make changes in the future.
The easiest way to achieve this is with the support of Power Automate and running the queries against the dataset. You can read more of this in one of our older blog posts.
We will discuss using a predefined template, though there is another option to have everything centralized in a single file, but this requires access to the Graph API. If individual file can be an option, this becomes more easier, as it only requires generating a CSV file (we have a predefined action for this in Power Automate) and then save the file.
This solution starts from a template excel file, containing all outputs. In this case we will use a single template file that will contain all the outputs.
This follows a similar approach in terms of actions, but instead of creating a new file for each section, we will copy the template and fill in all the required details in the respective table.
Now let's also understand everything:
NOTE
In this example we have a manual input with a manual trigger of the Power Automate flow. However, the input fields can also be provided in a Power Platform solution through variables like you will see below when we get the template file content. You can check this blog post talking about the data extraction.
NOTE
You can read more about this action in this blog post.
Function used:
first(body('FilterRelationshipsRow'))?['id']
This is how a sample output would look like:
In conclusion, let’s be honest—no one wants to do documentation, but we’ve got to look out for ourselves (and our fellow developers). Using DAX Info Functions is like giving yourself a shortcut when also combined with an automatic flow.