-
Notifications
You must be signed in to change notification settings - Fork 710
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wip on endless scrolling pagination #2221
Conversation
This PR is waiting to be rebased I assume? |
You're right. I'll merge upstream changes, resolve conflicts and update the PR description adding more information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (endless) PR adds endless scro
Haha - yes, it can feel that way sometimse!
query: string, | ||
page: number, | ||
size: number, | ||
nextPage: number, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for my own understanding, there's 3 changes here:
- repo -> repos - the query now enables charts from multiple repos
- addition of
query
- not yet sure why - Addition of pagination vars - why page and nextPage?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right,
i) repos is because we can select multiple repos.;
ii) query
is for searching charts using free text, when ""
it doesn't perform any search;
iii) page
and size
is just for the usual pagination. nextPage
is a workaround for attempting to solve the race condition when requesting
const charts = await Chart.fetchCharts(cluster, namespace, repo); | ||
if (charts) { | ||
dispatch(receiveCharts(charts)); | ||
if (page === nextPage && !(query === "" && size === 0)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment here would be helpful... it's not immediately clear what this is doing. It looks like: "we only request charts if there's either a query or the size is non-zero" (still not clear what nextpage is for, but I'll find out below).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, totally agree, it isn't clear at all. It has to do with the workaround for skipping useless requests. We should get rid of the nextPage
and solve the cancelation of requests in another place.
dashboard/src/actions/charts.ts
Outdated
cluster: string, | ||
namespace: string, | ||
repos: string, | ||
query: string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear why we need a separate function here if the previous one includes a query
option too? Only the actions look different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was just a reminiscence of old code. This duplicated function can be safely removed. Thanks for the observation!
dashboard/src/actions/charts.ts
Outdated
try { | ||
const categories = await Chart.fetchChartCategories(cluster, namespace, repos); | ||
if (categories) { | ||
dispatch(receiveChartCategories(categories)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I always find it odd that we don't dispatch a receive when we receive zero categories (ie []
), but I think it's what other actions do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I just tried to replicate the behavior of other actions in this case
dashboard/src/actions/repos.ts
Outdated
dispatch(receiveReposSecrets(repoSecrets)); | ||
} catch (e) { | ||
dispatch(errorRepos(e, "fetch")); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously unhandled error? Nice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I faced this unhandled rejection when developing and just solved it here since it is a very minor fix
dispatch(actions.charts.resetChartsSearch()); | ||
fetchChartsWithPagination(cluster, namespace, currentRepo, "", 1, size, nextPage); // get the first charts | ||
getCSVs(cluster, namespace); | ||
dispatch(actions.charts.resetPaginaton()); // start from page=1 again |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? Won't this mean the next page of results requested will be the first page again? (guessing not, but if you could expand the comment to explain why that'd be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now I'm not able to recall why the resetPaginaton()
was useful. I remember I was getting page=2 results even if I changed the filters, but I'm not able to reproduce it right now. Commenting this line, but not deleting it just in case.
(query: string) => { | ||
setCurrentSearchQuery(trimStart(query)); | ||
if (currentSearchQuery.length) { | ||
if (currentSearchQuery.length < 5 || currentSearchQuery.endsWith(" ")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If the user is still typing a query, throttle requests to a sensible frequency,
// otherwise debounce the requests so we only get a single request for the last event.
though, it's still not clear why we don't just use debounce regardless of whether the last character was a space? If debounce had 400ms and we used it exclusively, we'd just get a request every time the user paused (for more than 400ms). Why do we want the throttled one which will continually request when there are less that 5 letters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed this post: https://www.peterbe.com/plog/how-to-throttle-and-debounce-an-autocomplete-input-in-react
Previously, I did a PoC of autocompletion in the search box and that's why I followed this twofold approximation.
Perhaps just debouncing may work.
|
||
const filteredCharts = charts | ||
.filter( | ||
() => filters[filterNames.TYPE].length === 0 || filters[filterNames.TYPE].includes("Charts"), | ||
) | ||
.filter(() => filters[filterNames.OPERATOR_PROVIDER].length === 0) | ||
.filter(c => new RegExp(escapeRegExp(searchFilter), "i").test(c.id)) | ||
// .filter(c => new RegExp(escapeRegExp(searchFilter), "i").test(c.id)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guessing you meant to remove this.
() => filters[filterNames.TYPE].length === 0 || filters[filterNames.TYPE].includes("Charts"), | ||
) | ||
.filter(() => filters[filterNames.OPERATOR_PROVIDER].length === 0) | ||
// .filter(c => new RegExp(escapeRegExp(search.query), "i").test(c.id)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here (remove?)
csvs, | ||
cluster, | ||
namespace, | ||
hasFinished, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasFinished
isn't really descriptive. I assume this means hasFinishedFetching
?
Thank you for your comments despite it is still a draft PR. I wanted to polish up a couple of things (such as solving some issues when merging and adding more comments) and add some implementation details in the PR comment. |
As per the discussion with @andresmgot, this PR should be split into many smaller PRs, namely:
The main problem is when making multiple requests: we should track each one and determine if it should be accepted (add chunks), ignored (the data shouldn't be added) or retried (if it failed) |
This reverts commit 77b3b6e.
With a proper Note: no PR open yet since I want to address other PR's comments (and learn from them :) ) first. |
Closing this PR since it has been moved to #2264. |
Description of the change
This (endless) PR adds endless scrolling in the Catalog view as a result of the analysis and discussions of the design document.
Currently, in this PR we perform a server-side filtering for the most expensive calls: 1) filter by text; 2) filter by repository.
Moreover, the available categories are retrieved from the server-side (instead of getting unique names each time on client-side).
I decided to go this way because it results in faster response times from a UX perspective. I mean: to the best of my knowledge, there are no large repos adding meta information such as the categories. The typical use case would be people using bitnami catalog or other hand-crafted repo; in both cases, the amount of charts is still handleable (78 charts, 0.6 seconds)
Assuming this hypothesis, a server-side filtering here would increase the (perceived) response time: click filter, blank page with a spinner, render charts (instead of instantaneously filtering them)
Some notes about the PR
Current status
This PR is to be split into smaller ones. As I'm creating the new ones, I'm removing the corresponding changes here.
Implementation details
The main changes are in these files:
filters
for, each time the component is being loaded, applying some filters in client-side for chartIntersectionAPI
of the browser. This is a widely supported browser API, but could eventually fail in old browsers, but I don't think it''s a concerning issue.fetchCharts
function has been replaced withfetchChartsWithPagination
. Some attributes likequery
,page
andsize
have been introduced.reachEnd, receiveChartCategories, receiveChartsSearch, requestChartsCategories, requestChartsSearch, requestChartsVersions, resetChartsSearch, resetPaginaton
idleStatus, errorStatus, loadingStatus, finishedStatus
). It would be really useful to have a state diagram here, one can get lost very likely...The remaining changes are less important (adding server URL with query params in
url.ts
, modifying the search box inSearchFilter.tsx
to pass the current value and adding properties in types)Benefits
Large repositories (>2000) won't take more than 5 seconds in being rendered as each element will be created while the user scrolls down.
Possible drawbacks
Since the scroll event is captured regardless of the current filter, we are triggering the loading even if it is not required.
For instance, in this example, we are retrieving the whole set of charts but displaying only the operators. This also happens in the filtering by category.
This is not that easy, because when rendering the view the first time, it will update the state to be "fetching charts" instead of "finish". Client-side filters do not modify the state.
Applicable issues
Additional information
Design discussion at via.vmw.com/2020-kubeapps-ui-proposal.
Current status: