Skip to content
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

Prevent request with page size of 9999 #3694

Merged
merged 23 commits into from
Jan 23, 2025

Conversation

FrancescoMolinaro
Copy link
Contributor

@FrancescoMolinaro FrancescoMolinaro commented Nov 26, 2024

References

Fixes #2513

Description

Correct page size parameter when provided as 9999.

Affected components:

  1. EditBitstreamPageComponent - find All bitstream formats

is part of dynamic form rendered as DynamicSelectModel (ng-dynamic-forms documentation ) which does not support the infinite scroll or any other pagination feature

Solution:

Retrieve the bitstream formats using recursive pagination, fetching chunks of data with 20 elements per page until all data is retrieved. All data will be available in the dropdown, allowing the bitstream type to be selected.

  1. item-bitstreams.component.ts

Solution:

Retrieved the first 10 bundles initially. If more pages are available, a "load more" button will appear. On button click, the bundles for the next page will be retrieved and appended.

  1. scripts-select.component.ts

Solution:

Replaced the basic HTML select element with an ngbDropdown to enable the use of the infiniteScroll directive (ngx-infinite-scroll), as other examples in the application. This necessitated refactoring the component to align with the new template.

Load of the scripts will be on demand when the scroll reaches the end of the dropdown items.

Configuration: taking 20 elements per page

  1. bundle-data.service.ts

Solution: Removed elementsPerPage: 9999

Elements are no longer statically defined in the final layer of the service; they are now passed as parameters from higher levels.

  1. dynamic-list.component.ts

Solution: Figured all the values should be available all at once , so retrieved the data using recursive pagination, fetching chunks of data with 20 elements per page until all data is retrieved.

  1. relationship-type-data.service.ts

Solution:

As instructed by the existing comment , values should be available all at once , so retrieved the data using recursive pagination, fetching chunks of data with 20 elements per page until all data is retrieved.

  1. dynamic-lookup-relation-search-tab.component.ts

Solution:

selectAll method using the 9999 elements per page is not used , so it is removed.

  1. submission-section-cc-licenses.component.ts

Solution:

Starting from the structure of the cc-license component, where the dropdown toggle is a separate component and options are rendered using content projection in another component, the solution would be using an RxJS operator to recursively retrieve all elements until the list is complete.

Additional point: other changes would require a more complex refactor of the component and the existing logic.

Configuration: taking 20 elements per page

Checklist

This checklist provides a reminder of what we are going to look for when reviewing your PR. You do not need to complete this checklist prior creating your PR (draft PRs are always welcome).
However, reviewers may request that you complete any actions in this list if you have not done so. If you are unsure about an item in the checklist, don't hesitate to ask. We're here to help!

  • My PR is created against the main branch of code (unless it is a backport or is fixing an issue specific to an older branch).
  • My PR is small in size (e.g. less than 1,000 lines of code, not including comments & specs/tests), or I have provided reasons as to why that's not possible.
  • My PR passes ESLint validation using npm run lint
  • My PR doesn't introduce circular dependencies (verified via npm run check-circ-deps)
  • My PR includes TypeDoc comments for all new (or modified) public methods and classes. It also includes TypeDoc for large or complex private methods.
  • My PR passes all specs/tests and includes new/updated specs or tests based on the Code Testing Guide.
  • My PR aligns with Accessibility guidelines if it makes changes to the user interface.
  • My PR uses i18n (internationalization) keys instead of hardcoded English text, to allow for translations.
  • My PR includes details on how to test it. I've provided clear instructions to reviewers on how to successfully test this fix or feature.
  • If my PR includes new libraries/dependencies (in package.json), I've made sure their licenses align with the DSpace BSD License based on the Licensing of Contributions documentation.
  • If my PR includes new features or configurations, I've provided basic technical documentation in the PR itself.
  • If my PR fixes an issue ticket, I've linked them together.

@tdonohue tdonohue added bug performance / caching Related to performance, caching or embedded objects code task claimed: 4Science 4Science team is working on this issue & will contribute back port to dspace-7_x This PR needs to be ported to `dspace-7_x` branch for next bug-fix release port to dspace-8_x This PR needs to be ported to `dspace-8_x` branch for next bug-fix release high priority labels Dec 2, 2024
@tdonohue tdonohue requested a review from artlowel December 12, 2024 15:42
Copy link

Hi @FrancescoMolinaro,
Conflicts have been detected against the base branch.
Please resolve these conflicts as soon as you can. Thanks!

Copy link
Member

@artlowel artlowel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree with the approach of using expand to recursively retrieve all pages and still combine them as a single list on the client side. That doesn't solve the issue behind #2513.

The reason behind #2513 is that we shouldn't ever have a component on the client that requires all data from the server to work. The point of enforcing pagination is to keep the amount of data needed to start using a UI component to a minimum, both because it loads faster, and to reduce strain on the REST server. Getting all pages one at a time before we render something is arguably also slower and less efficient than just requesting everything in a single huge page.

What would solve #2513 imo is to replace all UI components that don't support working with one page at a time with ones that do. E.g. instead of a simple dropdown that contains all values, replace it with an "infinite scroll" style dropdown

I understand that that may be a lot of work for some of these components, and in other cases the reason why they used a huge page size in the first place was often that there was no rest endpoint to retrieve the exact information we need, so rest endpoints would likely need to be added first.

If you can't fix every instance in this PR, it's okay to leave some out, and leave #2513 open to be solved in later PRs

I wouldn't, however, replace one way of retrieving all data from the server and doing the processing client side, with another.

Copy link

Hi @FrancescoMolinaro,
Conflicts have been detected against the base branch.
Please resolve these conflicts as soon as you can. Thanks!

@FrancescoMolinaro
Copy link
Contributor Author

Hi @artlowel , thanks for your feedback!

I totally agree with your point and, following your suggestion, I have tried to improve the logic where possible, either using infinite scrolling or a "load more" button.

The only case where I had no luck to improve the solution is for:

src/app/core/data/relationship-type-data.service.ts

here unfortunately the call is made inside an effect and is not easily repleaceable, I think here we would need to add/extend an endpoint on the rest side as suggested also by a todo that was left for the method:

getRelationshipTypeByLabelAndTypes.

I will look forward to your feedback and thanks again for the review.

@tdonohue tdonohue requested review from artlowel and tdonohue January 16, 2025 14:42
Copy link
Member

@artlowel artlowel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @FrancescoMolinaro!

Most of it looks and works good now. I just have a few more inline comments

@@ -39,6 +39,9 @@
[isFirstTable]="isFirst"
aria-describedby="reorder-description">
</ds-item-edit-bitstream-bundle>
<div class="d-flex justify-content-center" *ngIf="showLoadMoreLink$ | async">
<div class="btn btn-link py-3" (click)="loadBundles()"> {{'item.edit.bitstreams.load-more.link' | translate}}</div>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've just done a major overhaul of this page to improve the accessibility in #3464

Please use an actual button here, so it can be accessed using the keyboard

Comment on lines 84 to 85
findByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, options?: FindListOptions, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<Bundle>> {
return this.findAllByItem(item, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how this change will work. The method worked by retrieving all bundles and filtering on their name client side. If we no longer retrieve all bundles that filter won't work. The only way it can be fixed is with a rest endpoint I think

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @artlowel thanks for the catch!

I though we could have use the limitation for bitstreams in the config ( I believe is 5 per page ) but is actually not true, that changes case by case, my bad.

I have kept the implementation because I think the method should support anyway the pagination and instead of the previous loop with expand I set the pagination parameter of 9999, I know this goes in the opposite direction of the PR but I agree that, in terms of rendering time, one big call is preferable to n small calls.
The solution, as you mentioned, will have to be a new endpoint that returns all the bitstreams by bundle name.

In addition to this I have also adapted my changes to the dynamic-list.component.ts as I realized that a "load more" button was not the right approach.
The issue that I noticed was happening in case the user would select an option out of range, save the submission, then reload the page; in this case the user would have had to load all options before being able to see his own selection.

The new solution I adopted is to keep the paginated calls, render the first ones as soon as they are ready, then keep loading the next ones in the background, adding them to the view progressively.

I will look forward to your feedback and thanks again!

Copy link
Member

@artlowel artlowel Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @FrancescoMolinaro

I can't seem to find a submission config that actually ends up using dynamic-list.component.ts.

Before I tested it by simply replacing the tag component with the list component here, and checking the subject field. However that keeps loading infinitely for me now. That might be due to differences between tags and lists tough.

Could you please let me know the backend submission field config you're using to test it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @artlowel , to visualize a dynamic-list in the submission form I am using a configuration like the following:

<field> <dc-schema>dc</dc-schema> <dc-element>identifier</dc-element> <label>Identifiers</label> <input-type value-pairs-name="common_identifiers">list</input-type> <repeatable>true</repeatable> <required /> <hint>Test hint</hint> </field>

The needed part is an input type of value "list" and a vocabulary, then we have two possible list based on the "repeatable" parameter.
Let me know in case this shouldn't work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that config works.

I'd still remove the line break between the pages.

image
(In this screenshot I reduced the page size to 2, to get a better idea how pagination works)

After they've loaded it doesn't matter for the user where the page breaks were:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @artlowel , thanks again for the feedback, I have removed the br, I agree it would just weirdly separate the options.

Comment on lines 118 to 157
// findByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<Bundle>> {
// return this.findAllByItem(item, { elementsPerPage: 1, currentPage: 1 }, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
// expand((rd: RemoteData<PaginatedList<Bundle>>) => {
// if (rd.hasSucceeded && hasValue(rd.payload) && hasValue(rd.payload.page) && rd.payload.currentPage < rd.payload.totalPages) {
// const nextPageOptions = { elementsPerPage: 1, currentPage: rd.payload.currentPage + 1 };
// return this.findAllByItem(item, nextPageOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
// } else {
// return EMPTY;
// }
// }),
// map((rd: RemoteData<PaginatedList<Bundle>>) => {
// if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
// const matchingBundle = rd.payload.page.find((bundle: Bundle) => bundle.name === bundleName);
// if (hasValue(matchingBundle)) {
// return new RemoteData(
// rd.timeCompleted,
// rd.msToLive,
// rd.lastUpdated,
// RequestEntryState.Success,
// null,
// matchingBundle,
// 200
// );
// } else {
// return new RemoteData(
// rd.timeCompleted,
// rd.msToLive,
// rd.lastUpdated,
// RequestEntryState.Error,
// `The bundle with name ${bundleName} was not found.`,
// null,
// 404
// );
// }
// } else {
// return rd as any;
// }
// })
// );
// }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably be removed

@tdonohue tdonohue requested a review from artlowel January 21, 2025 21:14
Copy link
Member

@artlowel artlowel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@tdonohue tdonohue left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Thanks @FrancescoMolinaro ! I tested this today, including testing all the modified components, and I couldn't find any obvious bugs. I also noticed (in some cases) where scrolling the dropdown loaded the next page, etc.

This appears to be working well to me. If any later issues are found, we can solve them in follow-up PRs.

@tdonohue tdonohue merged commit 33a091d into DSpace:main Jan 23, 2025
15 checks passed
@dspace-bot
Copy link
Contributor

Backport failed for dspace-7_x, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin dspace-7_x
git worktree add -d .worktree/backport-3694-to-dspace-7_x origin/dspace-7_x
cd .worktree/backport-3694-to-dspace-7_x
git switch --create backport-3694-to-dspace-7_x
git cherry-pick -x 369cb3eedfa30809e0a3ed60109832f00f2ee655 017d110da0d99e51ee3e66f222d58cbd3e1d5434 a9d151801b65088817496c02e7f1a9c811bec909 850a95172d382ed77b0d23d5e5f688864baeb2ec 4cd5e0aee170f749f451bc24496d3f55e0cbcc61 4830e4611211e21ceedc26b05f74c696c94f5b0b ebb408e492f21f529d7a678734a7b5856b359f41 4642ed1791e6a196a49bf423336f98a2e5860dae ac7af44c1dfb5387beabbbea83ff3a6628d151ec b43e1f4a8714cc596352d007fce5605561f00400 335fe621a003650e628b7a723d542c4a62d72889 d90a988e33a8181e3cac4549d58cef44d5dcaa61 f5ec5f9674fa3e404e08dbfa7eae7f7002445fa7 9f8083e9c539a76bdf45a49121ff5de4f4d9be1e 134ecd6d48167d3111f512abf997dfc739cb1c62 903daadcbf422f10d8c6af76c17e99c6bd7ad8e4 25b2f681ef341f70b17ab1feb0286e964c88ee9a c79affdec6a0a3f6b44bbd18be9ed72b2b95029a 9570cd9721891fc3fb3bef613e2758a3494f3dbf 2847dc39d9c8e9e23b5d353a6fb9ed1cb4e55798

@dspace-bot
Copy link
Contributor

Backport failed for dspace-8_x, because it was unable to cherry-pick the commit(s).

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin dspace-8_x
git worktree add -d .worktree/backport-3694-to-dspace-8_x origin/dspace-8_x
cd .worktree/backport-3694-to-dspace-8_x
git switch --create backport-3694-to-dspace-8_x
git cherry-pick -x 369cb3eedfa30809e0a3ed60109832f00f2ee655 017d110da0d99e51ee3e66f222d58cbd3e1d5434 a9d151801b65088817496c02e7f1a9c811bec909 850a95172d382ed77b0d23d5e5f688864baeb2ec 4cd5e0aee170f749f451bc24496d3f55e0cbcc61 4830e4611211e21ceedc26b05f74c696c94f5b0b ebb408e492f21f529d7a678734a7b5856b359f41 4642ed1791e6a196a49bf423336f98a2e5860dae ac7af44c1dfb5387beabbbea83ff3a6628d151ec b43e1f4a8714cc596352d007fce5605561f00400 335fe621a003650e628b7a723d542c4a62d72889 d90a988e33a8181e3cac4549d58cef44d5dcaa61 f5ec5f9674fa3e404e08dbfa7eae7f7002445fa7 9f8083e9c539a76bdf45a49121ff5de4f4d9be1e 134ecd6d48167d3111f512abf997dfc739cb1c62 903daadcbf422f10d8c6af76c17e99c6bd7ad8e4 25b2f681ef341f70b17ab1feb0286e964c88ee9a c79affdec6a0a3f6b44bbd18be9ed72b2b95029a 9570cd9721891fc3fb3bef613e2758a3494f3dbf 2847dc39d9c8e9e23b5d353a6fb9ed1cb4e55798

tdonohue pushed a commit to tdonohue/dspace-angular that referenced this pull request Jan 23, 2025
* [DURACOM-304] Refactored item-bitstreams.component by removing page size of 9999

* [DURACOM-304] Refactored edit-bitstream-page.component by removing page size of 9999

* [DURACOM-304] Refactored scripts-select.component by using infinite scroll instead of page size 9999

* [DURACOM-304] Refactored dynamic-list.component.ts by removing page size of 9999

* [DURACOM-304] Refactored relationship-type-data.service.ts by removing page size of 9999

* [DURACOM-304] removed unneeded selectAll method (dynamic-lookup-relation-search-tab.component)

* [DURACOM-304] Refactored submission-section-cc-licenses.component.ts by removing page size of 9999

* [DURACOM-304] lint fix

* [DURACOM-304] test fix

* [DURACOM-304] fix accessibility issue on scripts-select

* [DURACOM-304] Refactor of bundle-data.service.ts by removing page size of 9999

* [DURACOM-304] other fix related to accessibility

* [DURACOM-304] lint fix

* [DURACOM-304] resolve conflicts

* [DURACOM-304] fix lint

* [DURACOM-304] add support for findAll method in dynamic-scrollable-dropdown.component.ts

* [DURACOM-304] refactor to use lazy data provider

* [DURACOM-304] improve loading logic for cc-licenses section and dynamic-list

* [DURACOM-304] refactor, fix dynamic-list.component loading

* [DURACOM-304] remove br

---------

Co-authored-by: Alisa Ismailati <[email protected]>
@tdonohue
Copy link
Member

@FrancescoMolinaro : I was able to easily port this to dspace-8_x in #3887. However, porting it to dspace-7_x appears to be much more complex (I'm seeing a large number of merge conflicts).

Would you be able to create a separate PR to port this to dspace-7_x? I'd gladly test it myself if you can create the port.

tdonohue added a commit that referenced this pull request Jan 23, 2025
[Port dspace-8_x] Prevent request with page size of 9999 (#3694)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug claimed: 4Science 4Science team is working on this issue & will contribute back code task high priority performance / caching Related to performance, caching or embedded objects port to dspace-7_x This PR needs to be ported to `dspace-7_x` branch for next bug-fix release port to dspace-8_x This PR needs to be ported to `dspace-8_x` branch for next bug-fix release
Projects
Status: ✅ Done
Development

Successfully merging this pull request may close these issues.

Components should never send requests with a page size of 9999 as this may bypass pagination
5 participants