How to paginate (get all) Appwrite using listDocuments? - appwrite

I can list documents from Appwrite database using: https://appwrite.io/docs/client/database#databaseListDocuments
const sdk = new Appwrite();
sdk
.setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint
.setProject('5df5acd0d48c2') // Your project ID
;
let promise = sdk.database.listDocuments('[COLLECTION_ID]');
promise.then(function (response) {
console.log(response); // Success
}, function (error) {
console.log(error); // Failure
});
This function supports limit, but it is capped at a maximum 100. What if I have 500 documents? How can I get all documents using this method?

Include an offset parameter with your request, too (keep reading for details).
Rather than return an entire database (which could bring all network traffic to a crawl), AppWrite, being paginated, returns only a single page of results with each request. The limit parameter indicates the maximum amount of data allowed on each page (rather than the maximum number of items available in all).
Including a limit of 100 tells the API server you want a single page of results with 100 documents, and by default, AppWrite will always return the first page -- unless you also specify the page number you want, too. Fortunately, the listDocuments() function allows you to do just that via its offset parameter, which you can think of as a "page number."
To retrieve more items, simply move to the next page:
For example, begin with a limit of 100 (or less) and an offset of 0 (the first page). If the server returns a full page of results (eg, 100 items in this case), then increase the offset by 1 and make another request (This second request will return the next 100 to 199 documents). Continue increasing the offset and making requests until the server returns fewer results than the limit allows. At this point, you'll know you've reached the end of the documents, without forcing the server to choke on all of the results at once.
This is a common procedure with any system returning paginated results, although the parameter names can vary (eg, "page" vs "offset").

As of Appwrite 0.12.0, Appwrite offers 2 forms of pagination: offset and cursor.
Offset pagination is only supported up to 5000 documents. This is because the higher the offset, the longer it takes to get the data. This is an example of offset based pagination:
import { Databases } from "appwrite";
const databases = new Databases(client, "[DATABASE_ID]"); // 'client' comes from setup
// Page 1
const page1 = await databases.listDocuments('movies', [], 25, 0);
// Page 2
const page2 = await databases.listDocuments('movies', [], 25, 25);
Cursor based pagination is much more efficient and, as such, does not have a limit. This is an example of cursor based pagination:
import { Databases } from "appwrite";
const databases = new Databases(client, "[DATABASE_ID]"); // 'client' comes from setup
// Page 1
const page1 = await databases.listDocuments('movies', [], 25, 0);
const lastId = results.documents[results.documents.length - 1].$id;
// Page 2
const page2 = await databases.listDocuments('movies', [], 25, 0, lastId);
For more information on offset vs cursor based pagination, refer to this article. For more information on pagination in Appwrite, refer to the Appwrite Pagination docs.

Related

Pagination is not working for calls resource in Twilio

Problem: I want API to serve all the calls that were received by any given twilio number. It works just fine initially when the call logs are in 50s but as the number increases the call logs API is becoming very slow as there are too many call logs to fetch and process on our end.
Expected Result: I want to paginate the call logs to fetch only 20 call logs at a time.
I tried using List all calls api
// Download the helper library from https://www.twilio.com/docs/node/install
// Find your Account SID and Auth Token at twilio.com/console
// and set the environment variables. See http://twil.io/secure
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const client = require('twilio')(accountSid, authToken);
client.calls.list({limit: 3})
.then(calls => calls.forEach(c => console.log(c.sid)));
The expected result contain the following:
"calls": [1.., 2.., 3..],
"end": 1,
"first_page_uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls.json?Status=completed&To=%2B123456789&From=%2B987654321&StartTime=2008-01-02&ParentCallSid=CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&EndTime=2009-01-02&PageSize=2&Page=0",
"next_page_uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls.json?Status=completed&To=%2B123456789&From=%2B987654321&StartTime=2008-01-02&ParentCallSid=CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&EndTime=2009-01-02&PageSize=2&Page=1&PageToken=PACAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0",
"page": 0,
"page_size": 2,
"previous_page_uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls.json?Status=completed&To=%2B123456789&From=%2B987654321&StartTime=2008-01-02&ParentCallSid=CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&EndTime=2009-01-02&PageSize=2&Page=0",
"start": 0,
"uri": "/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Calls.json?Status=completed&To=%2B123456789&From=%2B987654321&StartTime=2008-01-02&ParentCallSid=CAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&EndTime=2009-01-02&PageSize=2&Page=0"
But in my case its only returning, even though there are more call logs present
"calls": []
Now since I am not able to get next_page_uri, I am not able to paginate.
How can I get next_page_uri?
If you are using the latest version of the Twilio Node module then you can get all your calls in a couple of ways:
The library automatically handles paging for you. Collections, such as
calls, have list and each methods that page under the hood. With
both list and each, you can specify the number of records you want
to receive (limit) and the maximum size you want each page fetch to
be (pageSize). The library will then handle the task for you.
list eagerly fetches all records and returns them as a list, whereas
each streams records and lazily retrieves pages of records as you
iterate over the collection. You can also page manually using the
page method.
For more information about these methods, view the [auto-generated
library https://www.twilio.com/docs/libraries/reference/twilio-node/).
If you want to use paging anyway, it'd probably recommend the page option:
client.calls.page({ pageSize: 10 }, function pageReceived(page) {
page.instances.forEach(function(call) {
console.log(call);
});
if (page.nextPage) {
page.nextPage().then(pageReceived);
}
})

How can I retrieve all members when querying for appRoleAssignedTo in Microsoft Graph?

I am attempting to programmatically retrieve a list of users (principalType = "User") and their associated appRoleId values for an enterprise app using itsresourceId value from Azure AD. There is a total of ten Users with a combined total of twenty appRoleId values associated with the app. However, when I run my query I receive data for just two users and a combined total of four appRoleId values.
Here's my C# code:
GraphServiceClient myGraphClient = GetGraphServiceClient([scopes]);
// Retrieve the [Id] value for the app. Note [Id] is a pseudonym for the [resourceId] required to retrieve users and app roles assigned.
var servPrinPage = await myGraphClient.ServicePrincipals.Request()
.Select("id,appRoles")
.Filter($"startswith(displayName, 'Display Name')")
.GetAsync()
.ConfigureAwait(false);
// Using the first [Id] value from the [ServicePrincipals] page, retrieve the list of users and their assigned roles for the app.
var appRoleAssignedTo = await myGraphClient.ServicePrincipals[servPrinPage[0].Id].AppRoleAssignedTo.Request().GetAsync().ConfigureAwait(false);
The query returns a ServicePrincipalAppRoleAssignedToCollectionPage (as expected) but the collection only contains four pages (one per User/appRoleId combination).
As an aside, the following query in Microsoft Graph Explorer produces an equivalent result:
https://graph.microsoft.com/v1.0/servicePrincipals/[resourceId]/appRoleAssignedTo
What am I missing here? I need to be able to retrieve the complete list of users and assigned app roles. Any assistance is greatly appreciated.
The issue I was confronting has to do with the pagination feature employed by Azure AD and MS Graph. In a nutshell, I was forced to submit two queries in order to retrieve all twenty records I was expecting.
If you have a larger set of records to be retrieved you may be faced with submitting a much larger number of successive queries. The successive queries are managed using a "skiptoken" passed as a request header each time your query is resubmitted.
Here is my revised code with notation....
// Step #1: Create a class in order to strongly type the <List> which will hold your results.
// Not absolutely necessary but always a good idea when working with <Lists> in C#.
private class AppRoleByUser
{
public string AzureDisplayName;
public string PrincipalDisplayName;
}
// Step #2: Submit a query to acquire the [id] for the Service Principal (i.e. your app).
// Note the [ServicePrincipals].[id] property is synonymous with the [resourceId] needed to
// retrieve [AppRoleAssignedTo] values from Microsoft Graph in the next step.
// Initialize the Microsoft Graph Client.
GraphServiceClient myGraphClient = GetGraphServiceClient("Directory.Read.All");
// Retrieve the Service Principals page containing the app [Id].
var servPrinPage = await myGraphClient.ServicePrincipals.Request().Select("id,appRoles").Filter($"startswith(displayName, 'Your App Name')").GetAsync().ConfigureAwait(false);
// Store the app [Id] in a local variable (for readability).
string resourceId = servPrinPage[0].Id;
// Step #3: Using the [Id]/[ResourceId] value from the previous step, retrieve a list of AppRoleId/DisplayName pairs for your app.
// Results of the successive queries are typed against the class created earlier and are appended to the <List>.
List<AppRoleByUser> appRoleByUser = new List<AppRoleByUser>();
// Note, unlike "Filter" or "Search" parameters, it is not possible to
// add a "Skiptoken" parameter directly to your query in C#.
// Instead, it is necessary to insert the "skiptoken" as request header using the Graph QueryOption class.
// Note the QueryOption List is passed as an empty object on the first pass of the while loop.
var queryOptions = new List<QueryOption>();
// Initialize the variable to hold the anticipated query result.
ServicePrincipalAppRoleAssignedToCollectionPage appRoleAssignedTo = new ServicePrincipalAppRoleAssignedToCollectionPage();
// Note the number of user/role combinations associated with an app is not always known.
// Consequently, you may be faced with the need to acquire multiple pages
// (and submit multiple consecutive queries) in order to obtain a complete
// listing of user/role combinations.
// The "while" loop construct will be utilized to manage query iteration.
// Execution of the "while" loop will be stopped when the "bRepeat" variable is set to false.
bool bRepeat = true;
while (bRepeat == true)
{
appRoleAssignedTo = (ServicePrincipalAppRoleAssignedToCollectionPage) await myGraphClient.ServicePrincipals[resourceId].AppRoleAssignedTo.Request(queryOptions).GetAsync().ConfigureAwait(false);
foreach (AppRoleAssignment myPage in appRoleAssignedTo)
{
// I was not able to find a definitive answer in any of the documents I
// found but it appears the final record in the recordset carries a
// [PrincipalType] = "Group" (all others carry a [PrincipalType] = "User").
if (myPage.PrincipalType != "Group")
{
// Insert "User" data into the List<AppRoleByUser> collection.
appRoleByUser.Add(new AppRoleByUser{ AzureDisplayName = myPage.PrincipalDisplayName, AzureUserRole = myPage.AppRoleId.ToString() });
}
else
{
// The "bRepeat" variable is initially set to true and is set to
// false when the "Group" record is detected thus signaling
// task completion and closing execution of the "while" loop.
bRepeat = false;
}
}
// Acquire the "nextLink" string from the response header.
// The "nextLink" string contains the "skiptoken" string required for the next
// iteration of the query.
string nextLinkValue = appRoleAssignedTo.AdditionalData["#odata.nextLink"].ToString();
// Parse the "skiptoken" value from the response header.
string skipToken = nextLinkValue.Substring(nextLinkValue.IndexOf("=") + 1);
// Include the "skiptoken" as a request header in the next iteration of the query.
queryOptions = new List<QueryOption>()
{
new QueryOption("$skiptoken", skipToken)
};
}
That's a long answer to what should have been a simple question. I am relatively new to Microsoft Graph but it appears to me Microsoft has a long way to go in making this API developer-friendly. All I needed was to know the combination of AppRoles and Users associated with a single, given Azure AD app. One query and one response should have been more than sufficient.
At any rate, I hope my toil might help save time for someone else going forward.
Could you please remove "Filter" from the code and retry the operation. Let us know if that worked.

YouTube API "ChannelSections" results don't match with channel?

So for the YouTube Channel Mindless Self Indulgence it has 4 sections on the home tab first section is they're music videos playlist, the 2nd section is albums which is a group of different playlists, then another playlist section and the last section is there uploads.
But when I do a channelSections api call I get like 20 different items and it has me scratching my head why.
Here's the api response https://notepad.pw/raw/w27ot290s
https://www.googleapis.com/youtube/v3/channelSections?key={KEYHERE}&channelId=UChS8bULfMVx10SiyZyeTszw&part=snippet,contentDetails
So I figured this out finally, I neglected to read the documentation on the channelSections api 😅
here: https://developers.google.com/youtube/v3/docs/channelSections
I was getting channel sections for all the regions where channel like music may more often have region specific sections... To filter these you need to also include the targeting object in the part parameter. If the section is region free (or atleast i assume) it won't have the targeting object so something to take into condertation when handling your api response and filter sectoins based on regions.
Here's my code just trying to get the data filtered in react app, not the most practical maybe but I fumbled through it:
const data = response2.data.items;
console.log("response2 data", data);
const filtered = data.filter(item => {
if (item.targeting === undefined) return true;
let test = false;
item.targeting.countries.forEach(i => {
if (i === "US") test = true;
});
return test;
});

What is the maximum HTTP GET request length for a YouTube API?

I want to use youtube video:list api to get details of multiple videos in single request. As per the api documentation, I can send comma separated videoId list as id parameter. But what is the maximum length possible?
I know the GET request limit is dependent on both the server and the client. In my case I am making the request from server-side and not from browser. Hence the maximum length could be configured on my end. But what is the maximum length acceptable for youtube?
UPDATE: Though official documentation couldn't find, current limit is 50 ids from the tests performed as explained by Tempus. I am adding a code below with 51 different video ids (1 is commented) for those who want to check this in future.
var key = prompt("Please enter your key here");
if (!key) {
alert("No key entered");
} else {
var videoIds = ["RgKAFK5djSk",
"fRh_vgS2dFE",
"OPf0YbXqDm0",
"KYniUCGPGLs",
"e-ORhEE9VVg",
"nfWlot6h_JM",
"NUsoVlDFqZg",
"YqeW9_5kURI",
"YQHsXMglC9A",
"CevxZvSJLk8",
"09R8_2nJtjg",
"HP-MbfHFUqs",
"7PCkvCPvDXk",
"0KSOMA3QBU0",
"hT_nvWreIhg",
"kffacxfA7G4",
"DK_0jXPuIr0",
"2vjPBrBU-TM",
"lp-EO5I60KA",
"5GL9JoH4Sws",
"kOkQ4T5WO9E",
"AJtDXIazrMo",
"RBumgq5yVrA",
"pRpeEdMmmQ0",
"YBHQbu5rbdQ",
"PT2_F-1esPk",
"uelHwf8o7_U",
"KQ6zr6kCPj8",
"IcrbM1l_BoI",
"vjW8wmF5VWc",
"PIh2xe4jnpk",
"QFs3PIZb3js",
"TapXs54Ah3E",
"uxpDa-c-4Mc",
"oyEuk8j8imI",
"ebXbLfLACGM",
"kHSFpGBFGHY",
"CGyEd0aKWZE",
"rYEDA3JcQqw",
"fLexgOxsZu0",
"450p7goxZqg",
"ASO_zypdnsQ",
"t4H_Zoh7G5A",
"QK8mJJJvaes",
"QcIy9NiNbmo",
"yzTuBuRdAyA",
"L0MK7qz13bU",
"uO59tfQ2TbA",
"kkx-7fsiWgg",
"EgqUJOudrcM",
// "60ItHLz5WEA" // 51st VideoID. Uncomment it to see error
];
var url = "https://www.googleapis.com/youtube/v3/videos?part=statistics&key=" + key + "&id=" + videoIds.join(",");
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
(xmlHttp.readyState == 4) && alert("HTTP Status code: " + xmlHttp.status);
}
xmlHttp.open("GET", url, true);
xmlHttp.send(null);
}
The answer is 50. Reason being, is that is all you will get back.
As some calls can have quite a few results depending on search criteria and available results, they have capped the "maxResults" at 50.
Acception to this is the CommentThreads which are up to 100.
This is (as you can work out) to speed page loads and call times.
EDIT:
This can be tested out HERE in the "Try api" part.
You will need to put 50 videoID's into the "id" field separated by coma's.
Then ad one more ID to get 51 and test again. You should receive a "400" response.
P.S. they do not need to be unique ID's. So have a few and then copy and paste as many times as needed ;-)

How to create a method query that works for an Infinite Scroll loading behavior

I'm creating a page that outputs a list of 1000-3000 records. The current flow is:
User loads a page
jQuery hits the server for all the records and injects them into the page.
Problem here is that those records for some users can take 3+ seconds to return which is a horrible UX.
What I would like to do is the following:
1. User loads a page
2. jQuery hits the server and gets at most 100 records. Then keeps hitting the server in a loop until the records loaded equal the max records.
Idea here is the user gets to see records quickly and doesn't think something broke.
So it's not really an infinite scroll as I don't care about the scroll position but it seems like a similar flow.
How in jQuery can I the the server in a loop? And how in rails can I query taking into account a offset and limit?
Thank you
You can simply query the server for a batch of data over and over again.
There are numerous APIs you can implement. Like:
client: GET request /url/
server: {
data: [ ... ]
rest: resturl
}
client GET request resturl
repeat.
Or you can get the client to pass in parameters saying you want resource 1-100, then 101-200 and do this in a loop.
All the while you will render the data as it comes in.
Your server either needs to let you pass in parameters saying you want record i to i + n.
Or your server needs to get all the data. Store it somewhere then return a chunk of the data along with some kind unique id or url to request another chunk of data and repeat this.
// pseudo jquery code
function next(data) {
render(data.records);
$.when(getData(data.uniqueId)).then(next);
}
function getData(id) {
return $.ajax({
type: "GET",
url: ...
data {
// when id is undefined get server to load all data
// when id is defined get server to send subset of data stored # id.
id: id
},
...
});
}
$.when(getData()).then(next);

Resources