How to Use Web.Contents to circumvent the dynamic data source issue in Power BI?

I’m connecting to the Ruddr API in Power Query for Power BI using the Web.Contents function in M. The Web.Contents function can cause issues in the Power BI Service as the data source might be dynamic and the Power BI Service won’t allow for a scheduled refresh.

From my research, I understood that I would need to properly make use of the Query and RelativePath options to circumnvent this problem. I used these sources: Source 1, [Source 2], and(https://blog.crossjoin.co.uk/2016/08/23/web-contents-m-functions-and-dataset-refresh-errors-in-power-bi/), Source 3. I rewrote my code to incorporate their recommendations but the Power BI Service still considers it to be a dynamic data source.

What am I doing wrong? I would think the baseUrl is static in my code. See below for my code:

let
    // Retrieve the API key from the parameter
    apiKey = #"ApiKey",
    baseUrl = "https://www.ruddr.io",
    
    // Define the initial query
    initialQuery = [
        limit = "100",
        dateAfter = "2024-01-01",
        startingAfter = ""
    ],

    // Define the relative path
    RelativePath = "/api/workspace/time-entries", 

    // Define the headers, including the API key
    headers = [
        #"Authorization" = "Bearer " & apiKey
    ],

    // Initialize the list to store URLs
    initialUrls = {},

    // Function to fetch a single page of results and record the URL
    fetchPage = (urlFetchPage as text, queryFetchPage as record, relPathFetchPage as text, urls as list) as record =>
        let
            response = Json.Document(Web.Contents(urlFetchPage, [
                Headers = headers,
                Query = queryFetchPage,
                RelativePath = relPathFetchPage
            ])),
            results = response[results],
            hasMore = response[hasMore],
            lastId = if List.Count(results) > 0 then results{List.Count(results) - 1}[id] else null,
            updatedUrls = List.Distinct(urls & {urlFetchPage})
        in
            [Results = results, HasMore = hasMore, LastId = lastId, Urls = updatedUrls],

    // Recursive function to fetch all pages using cursor-based pagination
    fetchAllPages = (urlFetchPages as text, queryFetchPages as record, relPathFetchPages as text, urls as list, results as list) as record =>
        let
            pageResult = fetchPage(urlFetchPages, queryFetchPages, relPathFetchPages, urls),
            pageResults = pageResult[Results],
            newResults = List.Combine({results, pageResults}),
            updatedQuery = if pageResult[HasMore] and pageResult[LastId] <> null then
                Record.TransformFields(queryFetchPages, {{"startingAfter", each pageResult[LastId]}})
            else
                Record.TransformFields(queryFetchPages, {{"startingAfter", each null}}),
            delay = Function.InvokeAfter(() => null, #duration(0, 0, 0, 0.5)), // Delay of 500 milliseconds
            updatedUrls = pageResult[Urls]
        in
            if Record.Field(updatedQuery, "startingAfter") <> null then
                @fetchAllPages(urlFetchPages, updatedQuery, relPathFetchPages, updatedUrls, newResults)
            else
                [Results = newResults, Urls = updatedUrls],

    // Fetch all pages starting from the initial URL
    resultAndUrls = fetchAllPages(baseUrl, initialQuery, RelativePath, initialUrls, {}),
    allResults = resultAndUrls[Results],
    allUrls = resultAndUrls[Urls],

    // Convert the list of results to a table
    resultTable = Table.FromList(allResults, Splitter.SplitByNothing(), null, null, ExtraValues.Error),

    // Function to ensure unique field names
    EnsureUniqueNames = (fieldNames as list) as list =>
        let
            AddSuffix = (name, existingNames) =>
                let
                    count = List.Count(List.Select(existingNames, each _ = name))
                in
                    if count = 0 then name else name & "_" & Text.From(count + 1),

            uniqueNames = List.Accumulate(fieldNames, {}, (state, current) =>
                state & {AddSuffix(current, state)})
        in
            uniqueNames,

    // Function to recursively expand record columns with unique field names
    ExpandRecordColumns = (table as table, optional parentColumn as text) as table =>
        let
            GetNonNullRecord = (column) =>
                let
                    nonNullValue = List.First(List.Select(Table.Column(table, column), each _ <> null))
                in
                    if Type.Is(Value.Type(nonNullValue), type record) then nonNullValue else null,

            RecordColumns = List.Select(Table.ColumnNames(table), each GetNonNullRecord(_) <> null),
            ExpandTable = List.Accumulate(RecordColumns, table, (state, current) => 
                let
                    record = GetNonNullRecord(current),
                    fieldNames = Record.FieldNames(record),
                    newFieldNames = EnsureUniqueNames(List.Transform(fieldNames, each if parentColumn = null then current & "_" & _ else parentColumn & "_" & current & "_" & _)),
                    expanded = Table.ExpandRecordColumn(state, current, fieldNames, newFieldNames)
                in
                    expanded)
        in
            if List.Count(RecordColumns) = 0 then table else @ExpandRecordColumns(ExpandTable),

    // Function to remove prefix and capitalize column names
    FormatColumnNames = (table as table) as table =>
        let
            removePrefix = (name as text) as text => Text.Replace(name, "Column1_", ""),
            capitalizeWords = (name as text) as text =>
                let
                    words = Text.Split(name, "_"),
                    capitalizedWords = List.Transform(words, each Text.Proper(_)),
                    newName = Text.Combine(capitalizedWords, " ")
                in
                    newName,
            newColumnNames = List.Transform(Table.ColumnNames(table), each capitalizeWords(removePrefix(_))),
            renamedTable = Table.RenameColumns(table, List.Zip({Table.ColumnNames(table), newColumnNames}))
        in
            renamedTable,

    // Expand all records in the table and format column names
    expandedTable = ExpandRecordColumns(resultTable),
    finalTable = FormatColumnNames(expandedTable),
    #"Removed Columns" = Table.RemoveColumns(finalTable,{"Timerstartedat", "Createdat", "Role", "Project Client Name", "Project Id", "Project Name", "Project Client Id", "Timeofftype Id", "Invoice Id", "Invoice Number"}),

    // Combine results and URLs for inspection
    DebugOutput = [
        Results = #"Removed Columns",
        Urls = allUrls
    ],
    // Combine results and URLs for inspection
    Urls = DebugOutput[Urls]
in
    #"Removed Columns"

I rewrote my code to make sure that in the API call everything before the question mark would be in RelativePath and the queries would in the Query parameter. In addition, I put all constants in parameters instead of defining them in the M code.

New contributor

Yannick De Ceulaer is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật