Forum Discussion
List.Generate - Qualys API - Issue fetching all records
Hi All,
I am trying to access the Qualys API data (Post request). I succesfully get the first 300 records (API limits is 300 records). The response has fields "hasMore" and "lastseenId" based on the which I can iterate the next 300 records.
I'm using List.Generate, whose initial call is getting the first 300 records. The conditional part (If hasMore = 1), should run the next part of the function which is supposed to call the api again with a lastSeenId to fetch the next 300.
Below is the call that I'm trying to make - Conditional check [Result][hasMore] = 1 works as it runs the next part of the query. It also returns data if [Result][lastSeenID] is replaced by a hardocded value inside the string - "&lastSeenId=111111".
Source = List.Generate(
() => [ Result = Json.Document(Web.Contents(data_url, [Headers = headers , Content=Text.ToBinary("")]))],
each [Result][hasMore] = 1,
each [Result = Json.Document(Web.Contents(data_url & "&lastSeenId=" [Result][lastSeenID], [Headers = headers , Content=Text.ToBinary("")]))],
each [Result])
in
Source
I've tried to convert lastSeenId to a text field(Number.ToText and a few others) as well and that didn't work either. I just get an error in form of a Record. If I click Error, it opens up the Record and returns the first result(from the initial call) and nothing else.
Any help would be greatly appreciated.
12 Replies
- lbendlin
Super User
There are a couple of things you may want to consider. Usually List.Generate is a rather inefficient way of collecting the resulting data as you would need to lug all of the results along.
List.Generate( () => [ Result = Json.Document(Web.Contents(data_url, [Headers = headers , Content=Text.ToBinary("")]))], each [Result][hasMore] = 1, each Result = Json.Document(Web.Contents(data_url & "&lastSeenId=" [Result][lastSeenID], [Headers = headers , Content=Text.ToBinary("")])), each [Result][Payload] & Json.Document(Web.Contents(data_url & "&lastSeenId=" [Result][lastSeenID], [Headers = headers , Content=Text.ToBinary("")]))[Payload] )Usually it is easier to use List.Generate only to create the list of required URLs, then to add a custom column with the URL contents, and finally to combine the data in that column. Usually the data is cached so it's not really a big issue if you fetch it twice.
- HR_BIFrequent Visitor
I tried the above code but it didn't work, I made some changes to the code :
Source = List.Generate(
() => [ Result = Json.Document(Web.Contents(data_url, [Headers = headers , Content=Text.ToBinary("")]))],
each [Result][hasMore] = 1,
each [Result = Json.Document(Web.Contents(data_lastSeenUrl & [Result][lastSeenId], [Headers = headers , Content=Text.ToBinary("")]))],
each [Result] & Json.Document(Web.Contents(data_lastSeenUrl & [Result][lastSeenId], [Headers = headers , Content=Text.ToBinary("")]))
),
Source1 = Source{0}
in
Source1but it throws me below mention error :
Expression.Error: We cannot apply operator & to types Text and Number.
Details:
Operator=&
Left=https://gateway.qg1.apps.qualys.com/list?pageSize=100&includeFields=tag,operatingSystem,hostId,agentId,assetName,address,createdDate&lastSeenId=
Right=1234566When I convert lastSeenId into string it throws authentication error.
How do I concatenate lastSeenId to remove these error? Any help is appreciated.
- mahoneypat
Microsoft Employee
Looks like you are missing an &, and I agree you need to convert it to text (if it isn't text already).
Source = List.Generate(
() => [ Result = Json.Document(Web.Contents(data_url, [Headers = headers , Content=Text.ToBinary("")]))],
each [Result][hasMore] = 1,
each [Result = Json.Document(Web.Contents(data_url & "&lastSeenId=" & Number.ToText([Result][lastSeenID]), [Headers = headers , Content=Text.ToBinary("")]))],
each [Result])
in
Source
Pat - DLEATEFrequent Visitor
I am also learning M for Qualys API integration and was wondering if you ever managed to get a working script.
I am struggling with pagination I have built a function with a manual loop with manual iterations just to make sure I had the code right but I am finding functional language fairly hard to wrap my head around!
I think i need to create a function and call it whilst the variable hasMoreRecords = true but this is day 4 and my brain is literally melting.. 🙂
Anyway whilst the code is crude and ugly I thought it may help someone that was struggling to create a power Query integration with Qualys.
Any advice on how should be done in M is very welcome 🙂
let
url = "https://qualysapi.qualys.eu/qps/rest/2.0/search/am/hostasset/",
authKey = "xxxxxxxxxxxxxxxxxxxxx",
Headers = [
#"Content-Type" = "text/xml",
#"Accept" = "application/xml",
#"Authorization" = "Basic " & authKey,
#"X-Requested-With" = "AHARP_BI"
//#"Cache-Control" = "no-cache",
//#"lastID" = lastId,
//#"lastID1" = lastId1,
//#"hasMoreRecords" = hasMoreRecords
],
//lastId1 = jsonResponse{0}[lastId],
//Recursive Loop Here while hasMoreRecords = true//Manual test Loop
postData = "<ServiceRequest><preferences><limitResults>10</limitResults></preferences><filters><Criteria field=""tagName"" operator=""EQUALS"">Cloud Agent</Criteria><Criteria field=""id"" operator=""GREATER"">0</Criteria></filters></ServiceRequest>",
jsonResponse = Xml.Tables(Web.Contents(url, [Headers = Headers, Content = Text.ToBinary(postData)])),
lastId1 = jsonResponse{0}[lastId],
data = jsonResponse{0}[data],
postData1 = "<ServiceRequest><preferences><limitResults>10</limitResults></preferences><filters><Criteria field=""tagName"" operator=""EQUALS"">Cloud Agent</Criteria><Criteria field=""id"" operator=""GREATER"">"&lastId1&"</Criteria></filters></ServiceRequest>",
jsonResponse1 = Xml.Tables(Web.Contents(url, [Headers = Headers, Content = Text.ToBinary(postData1)])),
lastId2 = jsonResponse1{0}[lastId],
data1 = jsonResponse1{0}[data],
postData2 = "<ServiceRequest><preferences><limitResults>10</limitResults></preferences><filters><Criteria field=""tagName"" operator=""EQUALS"">Cloud Agent</Criteria><Criteria field=""id"" operator=""GREATER"">"&lastId2&"</Criteria></filters></ServiceRequest>",
jsonResponse2 = Xml.Tables(Web.Contents(url, [Headers = Headers, Content = Text.ToBinary(postData1)])),
lastId3 = jsonResponse2{0}[lastId],
data2 = jsonResponse2{0}[data],postData3 = "<ServiceRequest><preferences><limitResults>10</limitResults></preferences><filters><Criteria field=""tagName"" operator=""EQUALS"">Cloud Agent</Criteria><Criteria field=""id"" operator=""GREATER"">"&lastId3&"</Criteria></filters></ServiceRequest>",
jsonResponse3 = Xml.Tables(Web.Contents(url, [Headers = Headers, Content = Text.ToBinary(postData1)])),
lastIda4 = jsonResponse3{0}[lastId],
data3 = jsonResponse3{0}[data],postData4 = "<ServiceRequest><preferences><limitResults>10</limitResults></preferences><filters><Criteria field=""tagName"" operator=""EQUALS"">Cloud Agent</Criteria><Criteria field=""id"" operator=""GREATER"">"&lastId4&"</Criteria></filters></ServiceRequest>",
jsonResponse4 = Xml.Tables(Web.Contents(url, [Headers = Headers, Content = Text.ToBinary(postData1)])),
lastId5 = jsonResponse4{0}[lastId],
data4 = jsonResponse4{0}[data],postData5 = "<ServiceRequest><preferences><limitResults>10</limitResults></preferences><filters><Criteria field=""tagName"" operator=""EQUALS"">Cloud Agent</Criteria><Criteria field=""id"" operator=""GREATER"">"&lastId5&"</Criteria></filters></ServiceRequest>",
jsonResponse5 = Xml.Tables(Web.Contents(url, [Headers = Headers, Content = Text.ToBinary(postData1)])),
lastId6 = jsonResponse5{0}[lastId],
data5 = jsonResponse5{0}[data],
HostAsset = data{0}[HostAsset] & data1{0}[HostAsset] & data2{0}[HostAsset] & data3{0}[HostAsset] & data4{0}[HostAsset] & data5{0}[HostAsset],
#"Changed Type" = Table.TransformColumnTypes(HostAsset,{{"id", Int64.Type}, {"name", type text}, {"created", type datetime}, {"modified", type datetime}, {"type", type text}, {"qwebHostId", Int64.Type}, {"lastVulnScan", type datetime}, {"lastSystemBoot", type datetime}, {"lastLoggedOnUser", type text}, {"domain", type text}, {"fqdn", type text}, {"os", type text}, {"dnsHostName", type text}, {"netbiosName", type text}, {"networkGuid", type text}, {"address", type text}, {"trackingMethod", type text}, {"manufacturer", type text}, {"model", type text}, {"totalMemory", Int64.Type}, {"timezone", type text}, {"biosDescription", type text}, {"isDockerHost", type logical}, {"cloudProvider", type text}})
in
#"Changed Type"