Skip to content

Instantly share code, notes, and snippets.

@hmbouk
Last active March 4, 2024 12:54
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hmbouk/b5712f8d4639eb85328bf4828b402284 to your computer and use it in GitHub Desktop.
Save hmbouk/b5712f8d4639eb85328bf4828b402284 to your computer and use it in GitHub Desktop.
Monday-paginated-board-PBI-query
The following files are the three functions and a query that can be used to retrieve data from a Monday.com board
using the new 2023-10 API and Cursor-based pagination.
let
Source = () =>
let
Source = #table(
{"Column1.cursor","Column1.items.id","Column1.items.name", "Reported By", "Created Date", "AssignedTo", "State", "Priority", "Issue Category", "Due Date", "Description", "Resolution", "Resolved Date", "Project Name"},
{
{Text.From(null),"","", "", "", "", "", "", "", "", "", "", "", ""}
}
)
in
Source
in
Source
let
Source = (BoardID as number) =>
let
MondayQuery1 = "{""query"": ""{boards(ids: ",
MondayQuery2 = Number.ToText(BoardID),
MondayQuery3 = ") {items_page(limit: 100) {cursor items {id name column_values {text column {title}}}}}}""}",
MondayFullQuery = MondayQuery1 & MondayQuery2 & MondayQuery3,
Source = Web.Contents("https://api.monday.com/",
[
RelativePath="v2",
Headers=
[
#"Method"="POST",
#"Content-Type"="application/json",
#"Authorization"="eyJhbGciOiJIUzI1NiJ9.eyJ0aWQiOjI1NTUwMDU0NywiYWFpIjoxMSwidWlkIjo0MzAyNDk2MiwiaWFkIjoiMjAyMy0wNS0wOVQyMzowMzowMC42NTdaIiwicGVyIjoibWU6d3JpdGUiLCJhY3RpZCI6MTI0Mjc4NzUsInJnbiI6InVzZTEifQ.4DN9PXL0bhfbGNQ82I-Mcar-huskws2kQtuKqqpR4z0",
#"API-Version"="2023-10"
],
Content=Text.ToBinary(MondayFullQuery)
]
),
#"JSON" = Json.Document(Source, 65001),
data = JSON[data],
boards = data[boards],
#"Converted to Table" = Table.FromList(boards, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
#"Expanded Column1" = Table.ExpandRecordColumn(#"Converted to Table", "Column1", {"items_page"}, {"Column1.items_page"}),
#"Renamed Columns" = Table.RenameColumns(#"Expanded Column1",{{"Column1.items_page", "Column1"}}),
#"Expanded Column2" = Table.ExpandRecordColumn(#"Renamed Columns", "Column1", {"cursor", "items"}, {"Column1.cursor", "Column1.items"}),
#"Expanded Column1.items" = Table.ExpandListColumn(#"Expanded Column2", "Column1.items"),
#"Expanded Column1.items1" = Table.ExpandRecordColumn(#"Expanded Column1.items", "Column1.items", {"id", "name", "column_values"}, {"Column1.items.id", "Column1.items.name", "Column1.items.column_values"}),
#"Expanded Column1.items.column_values" = Table.ExpandListColumn(#"Expanded Column1.items1", "Column1.items.column_values"),
#"Expanded Column1.items.column_values1" = Table.ExpandRecordColumn(#"Expanded Column1.items.column_values", "Column1.items.column_values", {"text", "column"}, {"Column1.items.column_values.text", "Column1.items.column_values.column"}),
#"Expanded Column1.items.column_values.column" = Table.ExpandRecordColumn(#"Expanded Column1.items.column_values1", "Column1.items.column_values.column", {"title"}, {"Column1.items.column_values.column.title"}),
#"Changed Type" = Table.TransformColumnTypes(#"Expanded Column1.items.column_values.column",{{"Column1.items.column_values.column.title", type text}}),
#"Pivoted Column" = Table.Pivot(#"Changed Type", List.Distinct(#"Changed Type"[Column1.items.column_values.column.title]), "Column1.items.column_values.column.title", "Column1.items.column_values.text")
in
#"Pivoted Column"
in
Source
let
Source = (PageCursor as text) =>
let
Monday2ndQuery1 = "{""query"": ""{next_items_page(cursor: \""",
Monday2ndQuery2 = PageCursor,
Monday2ndQuery3 = "\"" limit: 100) {cursor items {id name column_values {column {title} text}}}}""}",
MondayFull2ndQuery = Monday2ndQuery1 & Monday2ndQuery2 & Monday2ndQuery3,
Source2 = Web.Contents("https://api.monday.com/",
[
RelativePath="v2",
Headers=
[
#"Method"="POST",
#"Content-Type"="application/json",
#"Authorization"="eyJhbGciOiJIUzI1NiJ9.eyJ0aWQiOjI1NTUwMDU0NywiYWFpIjoxMSwidWlkIjo0MzAyNDk2MiwiaWFkIjoiMjAyMy0wNS0wOVQyMzowMzowMC42NTdaIiwicGVyIjoibWU6d3JpdGUiLCJhY3RpZCI6MTI0Mjc4NzUsInJnbiI6InVzZTEifQ.4DN9PXL0bhfbGNQ82I-Mcar-huskws2kQtuKqqpR4z0",
#"API-Version"="2023-10"
],
Content=Text.ToBinary(MondayFull2ndQuery)
]
),
#"2ndJSON" = Json.Document(Source2, 65001),
data1 = #"2ndJSON"[data],
#"Converted to Table1" = Record.ToTable(data1),
#"Renamed Columns1" = Table.RenameColumns(#"Converted to Table1",{{"Value", "Column1"}}),
#"Removed Columns" = Table.RemoveColumns(#"Renamed Columns1",{"Name"}),
#"Expanded Column2" = Table.ExpandRecordColumn(#"Removed Columns", "Column1", {"cursor", "items"}, {"Column1.cursor", "Column1.items"}),
#"Expanded Column1.items" = Table.ExpandListColumn(#"Expanded Column2", "Column1.items"),
#"Expanded Column1.items1" = Table.ExpandRecordColumn(#"Expanded Column1.items", "Column1.items", {"id", "name", "column_values"}, {"Column1.items.id", "Column1.items.name", "Column1.items.column_values"}),
#"Expanded Column1.items.column_values" = Table.ExpandListColumn(#"Expanded Column1.items1", "Column1.items.column_values"),
#"Expanded Column1.items.column_values1" = Table.ExpandRecordColumn(#"Expanded Column1.items.column_values", "Column1.items.column_values", {"column", "text"}, {"Column1.items.column_values.column", "Column1.items.column_values.text"}),
#"Expanded Column1.items.column_values.column" = Table.ExpandRecordColumn(#"Expanded Column1.items.column_values1", "Column1.items.column_values.column", {"title"}, {"Column1.items.column_values.column.title"}),
#"Changed Type1" = Table.TransformColumnTypes(#"Expanded Column1.items.column_values.column",{{"Column1.items.column_values.column.title", type text}}),
#"Pivoted Column1" = Table.Pivot(#"Changed Type1", List.Distinct(#"Changed Type1"[Column1.items.column_values.column.title]), "Column1.items.column_values.column.title", "Column1.items.column_values.text")
in
#"Pivoted Column1"
in
Source
let
/* The Monday.com API dated 2023-10 changed the way rows of data are returned from boards. You now have to call for 1 page of data
at a time and also request the cursor which points to the next page of data. If the cursor is null there are no more pages of data.
The maximum number of rows per page is configurable with the maximum allow of 100 rows of data. The following query attempts to
request five pages of 100 rows of data. If you expect more than 500 rows of data then additional page requests need to be added below. */
/* Define the Monday.com board ID number then call the GetBoard_1stPage function to return the first 100 rows of data */
/* Update the Column1.cursor column if the value is null and replace with NoMorePages */
/* Assign the value found in the Column1.cursor column to the 2nd_page_cursor variable */
BoardID = 1234567890,
#"1st_Page_Table" = GetBoard_1stPage(BoardID),
#"1st_Page_Table_Updated" = Table.ReplaceValue(#"1st_Page_Table",null,"NoMorePages",Replacer.ReplaceValue,{"Column1.cursor"}),
#"2nd_page_cursor" = #"1st_Page_Table_Updated"{0}[Column1.cursor],
/* If the 2nd_page_cursor value is not equal to NoMorePages then call the GetBoard_RemainingPages function to get the next page of 100 rows of data */
/* If the cursor value is NoMorePages then call the EmptyIssuesTable to create an empty table with the correct column names */
/* Update the Column1.cursor column if the value is null and replace with NoMorePages */
/* Assign the value found in the Column1.cursor column to the 3rd_page_cursor variable */
#"2nd_Page_Table" = if #"2nd_page_cursor" <> "NoMorePages" then GetBoard_RemainingPages(#"2nd_page_cursor") else EmptyTable(),
#"2nd_Page_Table_Updated" = Table.ReplaceValue(#"2nd_Page_Table",null,"NoMorePages",Replacer.ReplaceValue,{"Column1.cursor"}),
#"3rd_page_cursor" = #"2nd_Page_Table_Updated"{0}[Column1.cursor],
/* If the 3rd_page_cursor value is not equal to NoMorePages then call the GetBoard_RemainingPages function to get the next page of 100 rows of data */
/* If the cursor value is NoMorePages then call the EmptyIssuesTable to create an empty table with the correct column names */
/* Update the Column1.cursor column if the value is null and replace with NoMorePages */
/* Assign the value found in the Column1.cursor column to the 4th_page_cursor variable */
#"3rd_Page_Table" = if #"3rd_page_cursor" <> "NoMorePages" then GetBoard_RemainingPages(#"3rd_page_cursor") else EmptyTable(),
#"3rd_Page_Table_Updated" = Table.ReplaceValue(#"3rd_Page_Table",null,"NoMorePages",Replacer.ReplaceValue,{"Column1.cursor"}),
#"4th_page_cursor" = #"3rd_Page_Table_Updated"{0}[Column1.cursor],
/* If the 4th_page_cursor value is not equal to NoMorePages then call the GetBoard_RemainingPages function to get the next page of 100 rows of data */
/* If the cursor value is NoMorePages then call the EmptyIssuesTable to create an empty table with the correct column names */
/* Update the Column1.cursor column if the value is null and replace with NoMorePages */
/* Assign the value found in the Column1.cursor column to the 5th_page_cursor variable */
#"4th_Page_Table" = if #"4th_page_cursor" <> "NoMorePages" then GetBoard_RemainingPages(#"4th_page_cursor") else EmptyTable(),
#"4th_Page_Table_Updated" = Table.ReplaceValue(#"4th_Page_Table",null,"NoMorePages",Replacer.ReplaceValue,{"Column1.cursor"}),
#"5th_page_cursor" = #"4th_Page_Table_Updated"{0}[Column1.cursor],
/* If the 5th_page_cursor value is not equal to NoMorePages then call the GetBoard_RemainingPages function to get the next page of 100 rows of data */
/* If the cursor value is NoMorePages then call the EmptyIssuesTable to create an empty table with the correct column names */
/* Update the Column1.cursor column if the value is null and replace with NoMorePages */
/* Assign the value found in the Column1.cursor column to the 6th_page_cursor variable */
#"5th_Page_Table" = if #"5th_page_cursor" <> "NoMorePages" then GetBoard_RemainingPages(#"5th_page_cursor") else EmptyTable(),
#"5th_Page_Table_Updated" = Table.ReplaceValue(#"5th_Page_Table",null,"NoMorePages",Replacer.ReplaceValue,{"Column1.cursor"}),
#"6th_page_cursor" = #"5th_Page_Table_Updated"{0}[Column1.cursor],
/* If you need to load more than 500 rows of data then duplicate the three lines above and modify the variable names. You will also need to add the new variable in the Table.Combine function below */
/* E.G.
#"6th_Page_Table" = if #"6th_page_cursor" <> "NoMorePages" then GetBoard_RemainingPages(#"6th_page_cursor") else EmptyTable(),
#"6th_Page_Table_Updated" = Table.ReplaceValue(#"6th_Page_Table",null,"NoMorePages",Replacer.ReplaceValue,{"Column1.cursor"}),
#"7th_page_cursor" = #"6th_Page_Table_Updated"{0}[Column1.cursor],
*/
/* Combine the five tables contained in #"1st_Page_Table_Updated", #"2nd_Page_Table_Updated", #"3rd_Page_Table_Updated", #"4th_Page_Table_Updated", #"5th_Page_Table_Updated" */
/* Keep all columns except the Column1.cursor and Column1.items.id columns. These two columns are not needed. */
/* Rename the Column1.items.name to Topic */
/* Remove the empty rows that were due to any empty tables. */
Combined_Table1 = Table.Combine({#"1st_Page_Table_Updated", #"2nd_Page_Table_Updated", #"3rd_Page_Table_Updated", #"4th_Page_Table_Updated", #"5th_Page_Table_Updated"}), // add additional table variables if more than 500 rows of data
#"Removed Other Columns" = Table.SelectColumns(Combined_Table1,{"Column1.items.name", "Reported By", "Created Date", "AssignedTo", "State", "Priority", "Issue Category", "Due Date", "Description", "Resolution", "Resolved Date", "Project Name"}),
#"Renamed Columns" = Table.RenameColumns(#"Removed Other Columns",{{"Column1.items.name", "Item"}}),
#"Filtered Rows" = Table.SelectRows(#"Renamed Columns", each [Item] <> null and [Item] <> "")
in
#"Filtered Rows"
@jscortesgh
Copy link

Hello, and thanks for this code it worked for me, but i would also need to bring/retrieve the group of each item, is it possible?

@sachecka
Copy link

sachecka commented Jan 18, 2024

I ran into an error when I was trying this method out.
The error I see in the Monday Board Query table is:
Formula.Firewall: Query 'Table' (step '2nd_Page_Table_Updated') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination.
Any suggestions as to how to resolve this error?

Resolved it by going into the File -> Options and Settings -> Options -> Current File -> Privacy -> Selected Ignore the Privacy Levels and potentially improve performance

@hmbouk
Copy link
Author

hmbouk commented Jan 19, 2024

Hello, and thanks for this code it worked for me, but i would also need to bring/retrieve the group of each item, is it possible?

I'm working on updated queries that will bring in the group for each item and also subitems.

@hmbouk
Copy link
Author

hmbouk commented Jan 19, 2024

I ran into an error when I was trying this method out. The error I see in the Monday Board Query table is: Formula.Firewall: Query 'Table' (step '2nd_Page_Table_Updated') references other queries or steps, so it may not directly access a data source. Please rebuild this data combination. Any suggestions as to how to resolve this error?

Resolved it by going into the File -> Options and Settings -> Options -> Current File -> Privacy -> Selected Ignore the Privacy Levels and potentially improve performance

Exactly. You have to ignore the privacy levels in your PBIX file.

@sachecka
Copy link

Thanks for this @hmbouk.
I was able to get the groups in the same query without the need for additional query.
https://github.com/sachecka/Monday-PBI-Connection

@hmbouk
Copy link
Author

hmbouk commented Jan 22, 2024 via email

@ariten
Copy link

ariten commented Feb 18, 2024

Thanks for this @hmbouk
I was able to adapt your queries and get the dynamic paging implemented for my use case.
I have published these on https://github.com/ariten/Monday-to-PowerBi
It can be quite slow if you have a lot of records but the dynamic paging does work for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment