71380: Fix loading/cache issue with dropping objects on page

This commit is contained in:
Kristof De Langhe
2020-06-25 11:09:37 +02:00
parent 28891211e4
commit 73c25998e3
5 changed files with 57 additions and 21 deletions

View File

@@ -196,9 +196,11 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
path: `/_links/bitstreams/${event.toIndex}/href` path: `/_links/bitstreams/${event.toIndex}/href`
}); });
this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RestResponse) => { this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RestResponse) => {
this.zone.run(() => {
this.displayNotifications('item.edit.bitstreams.notifications.move', [response]); this.displayNotifications('item.edit.bitstreams.notifications.move', [response]);
this.requestService.removeByHrefSubstring(bundle.self); this.requestService.removeByHrefSubstring(bundle.self);
this.zone.run(() => event.finish()); event.finish();
});
}); });
} }
}); });

View File

@@ -16,6 +16,7 @@ import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-siz
import { ResponsiveColumnSizes } from '../../../../../shared/responsive-table-sizes/responsive-column-sizes'; import { ResponsiveColumnSizes } from '../../../../../shared/responsive-table-sizes/responsive-column-sizes';
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
import { createPaginatedList } from '../../../../../shared/testing/utils.test'; import { createPaginatedList } from '../../../../../shared/testing/utils.test';
import { RequestService } from '../../../../../core/data/request.service';
describe('PaginatedDragAndDropBitstreamListComponent', () => { describe('PaginatedDragAndDropBitstreamListComponent', () => {
let comp: PaginatedDragAndDropBitstreamListComponent; let comp: PaginatedDragAndDropBitstreamListComponent;
@@ -23,6 +24,7 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => {
let objectUpdatesService: ObjectUpdatesService; let objectUpdatesService: ObjectUpdatesService;
let bundleService: BundleDataService; let bundleService: BundleDataService;
let objectValuesPipe: ObjectValuesPipe; let objectValuesPipe: ObjectValuesPipe;
let requestService: RequestService;
const columnSizes = new ResponsiveTableSizes([ const columnSizes = new ResponsiveTableSizes([
new ResponsiveColumnSizes(2, 2, 3, 4, 4), new ResponsiveColumnSizes(2, 2, 3, 4, 4),
@@ -98,18 +100,24 @@ describe('PaginatedDragAndDropBitstreamListComponent', () => {
); );
bundleService = jasmine.createSpyObj('bundleService', { bundleService = jasmine.createSpyObj('bundleService', {
getBitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2])) getBitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2])),
getBitstreamsEndpoint: observableOf('')
}); });
objectValuesPipe = new ObjectValuesPipe(); objectValuesPipe = new ObjectValuesPipe();
requestService = jasmine.createSpyObj('requestService', {
hasByHrefObservable: observableOf(true)
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()], imports: [TranslateModule.forRoot()],
declarations: [PaginatedDragAndDropBitstreamListComponent, VarDirective], declarations: [PaginatedDragAndDropBitstreamListComponent, VarDirective],
providers: [ providers: [
{ provide: ObjectUpdatesService, useValue: objectUpdatesService }, { provide: ObjectUpdatesService, useValue: objectUpdatesService },
{ provide: BundleDataService, useValue: bundleService }, { provide: BundleDataService, useValue: bundleService },
{ provide: ObjectValuesPipe, useValue: objectValuesPipe } { provide: ObjectValuesPipe, useValue: objectValuesPipe },
{ provide: RequestService, useValue: requestService }
], schemas: [ ], schemas: [
NO_ERRORS_SCHEMA NO_ERRORS_SCHEMA
] ]

View File

@@ -9,6 +9,7 @@ import { PaginatedSearchOptions } from '../../../../../shared/search/paginated-s
import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-sizes/responsive-table-sizes'; import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-sizes/responsive-table-sizes';
import { followLink } from '../../../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../../../shared/utils/follow-link-config.model';
import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe'; import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe';
import { RequestService } from '../../../../../core/data/request.service';
@Component({ @Component({
selector: 'ds-paginated-drag-and-drop-bitstream-list', selector: 'ds-paginated-drag-and-drop-bitstream-list',
@@ -35,7 +36,8 @@ export class PaginatedDragAndDropBitstreamListComponent extends AbstractPaginate
constructor(protected objectUpdatesService: ObjectUpdatesService, constructor(protected objectUpdatesService: ObjectUpdatesService,
protected elRef: ElementRef, protected elRef: ElementRef,
protected objectValuesPipe: ObjectValuesPipe, protected objectValuesPipe: ObjectValuesPipe,
protected bundleService: BundleDataService) { protected bundleService: BundleDataService,
protected requestService: RequestService) {
super(objectUpdatesService, elRef, objectValuesPipe); super(objectUpdatesService, elRef, objectValuesPipe);
} }
@@ -48,12 +50,18 @@ export class PaginatedDragAndDropBitstreamListComponent extends AbstractPaginate
*/ */
initializeObjectsRD(): void { initializeObjectsRD(): void {
this.objectsRD$ = this.currentPage$.pipe( this.objectsRD$ = this.currentPage$.pipe(
switchMap((page: number) => this.bundleService.getBitstreams( switchMap((page: number) => {
const paginatedOptions = new PaginatedSearchOptions({pagination: Object.assign({}, this.options, { currentPage: page })});
return this.bundleService.getBitstreamsEndpoint(this.bundle.id, paginatedOptions).pipe(
switchMap((href) => this.requestService.hasByHrefObservable(href)),
switchMap(() => this.bundleService.getBitstreams(
this.bundle.id, this.bundle.id,
new PaginatedSearchOptions({pagination: Object.assign({}, this.options, { currentPage: page })}), paginatedOptions,
followLink('format') followLink('format')
)) ))
); );
})
);
} }
/** /**

View File

@@ -88,10 +88,12 @@ export class BundleDataService extends DataService<Bundle> {
/** /**
* Get the bitstreams endpoint for a bundle * Get the bitstreams endpoint for a bundle
* @param bundleId * @param bundleId
* @param searchOptions
*/ */
getBitstreamsEndpoint(bundleId: string): Observable<string> { getBitstreamsEndpoint(bundleId: string, searchOptions?: PaginatedSearchOptions): Observable<string> {
return this.getBrowseEndpoint().pipe( return this.getBrowseEndpoint().pipe(
switchMap((href: string) => this.halService.getEndpoint(this.bitstreamsEndpoint, `${href}/${bundleId}`)) switchMap((href: string) => this.halService.getEndpoint(this.bitstreamsEndpoint, `${href}/${bundleId}`)),
map((href) => searchOptions ? searchOptions.toRestUrl(href) : href)
); );
} }
@@ -102,9 +104,8 @@ export class BundleDataService extends DataService<Bundle> {
* @param linksToFollow The {@link FollowLinkConfig}s for the request * @param linksToFollow The {@link FollowLinkConfig}s for the request
*/ */
getBitstreams(bundleId: string, searchOptions?: PaginatedSearchOptions, ...linksToFollow: Array<FollowLinkConfig<Bitstream>>): Observable<RemoteData<PaginatedList<Bitstream>>> { getBitstreams(bundleId: string, searchOptions?: PaginatedSearchOptions, ...linksToFollow: Array<FollowLinkConfig<Bitstream>>): Observable<RemoteData<PaginatedList<Bitstream>>> {
const hrefObs = this.getBitstreamsEndpoint(bundleId).pipe( const hrefObs = this.getBitstreamsEndpoint(bundleId, searchOptions);
map((href) => searchOptions ? searchOptions.toRestUrl(href) : href)
);
hrefObs.pipe( hrefObs.pipe(
take(1) take(1)
).subscribe((href) => { ).subscribe((href) => {

View File

@@ -1,4 +1,4 @@
import { FieldUpdate, FieldUpdates, Identifiable } from '../../core/data/object-updates/object-updates.reducer'; import { FieldUpdate, FieldUpdates } from '../../core/data/object-updates/object-updates.reducer';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list';
@@ -6,7 +6,7 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service'; import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service';
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators'; import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../empty.util'; import { hasValue, isNotEmpty } from '../empty.util';
import { paginatedListToArray } from '../../core/shared/operators'; import { paginatedListToArray } from '../../core/shared/operators';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
@@ -95,10 +95,19 @@ export abstract class AbstractPaginatedDragAndDropListComponent<T extends DSpace
/** /**
* Whether or not we should display a loading animation * Whether or not we should display a loading animation
* This is used to display a loading page when the user drops a bitstream onto a new page. The loading animation * This is used to display a loading page when the user drops a bitstream onto a new page. The loading animation
* should stop once the bitstream has moved to the new page and the new page's response has loaded * should stop once the bitstream has moved to the new page and the new page's response has loaded and contains the
* dropped object on top (see this.stopLoadingWhenFirstIs below)
*/ */
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
/**
* ID of the object the page's first element needs to match in order to stop the loading animation.
* This is to ensure the new page is fully loaded containing the latest data from the REST API whenever an object is
* dropped on a new page. This allows the component to expect the dropped object to be present on top of the new page,
* while displaying a loading animation until this is the case.
*/
stopLoadingWhenFirstIs: string;
/** /**
* List of subscriptions * List of subscriptions
*/ */
@@ -148,7 +157,15 @@ export abstract class AbstractPaginatedDragAndDropListComponent<T extends DSpace
distinctUntilChanged(compareArraysUsingFieldUuids()) distinctUntilChanged(compareArraysUsingFieldUuids())
).subscribe((updateValues) => { ).subscribe((updateValues) => {
this.customOrder = updateValues.map((fieldUpdate) => fieldUpdate.field.uuid); this.customOrder = updateValues.map((fieldUpdate) => fieldUpdate.field.uuid);
}) // Check if stopLoadingWhenFirstIs contains a value. If it does and it equals the first value in customOrder, stop the loading animation.
// This is to ensure the page is updated to contain the new values first, before displaying it.
if (hasValue(this.stopLoadingWhenFirstIs) && isNotEmpty(this.customOrder) && this.customOrder[0] === this.stopLoadingWhenFirstIs) {
this.stopLoadingWhenFirstIs = undefined;
this.loading$.next(false);
}
}),
// Disable the pagination when objects are loading
this.loading$.subscribe((loading) => this.options.disabled = loading)
); );
} }
@@ -197,6 +214,7 @@ export abstract class AbstractPaginatedDragAndDropListComponent<T extends DSpace
// Send out a drop event (and navigate to the new page) when the "from" and "to" indexes are different from each other // Send out a drop event (and navigate to the new page) when the "from" and "to" indexes are different from each other
if (fromIndex !== toIndex) { if (fromIndex !== toIndex) {
if (isNewPage) { if (isNewPage) {
this.stopLoadingWhenFirstIs = this.customOrder[dragIndex];
this.customOrder = []; this.customOrder = [];
this.paginationComponent.doPageChange(redirectPage); this.paginationComponent.doPageChange(redirectPage);
this.loading$.next(true); this.loading$.next(true);
@@ -207,7 +225,6 @@ export abstract class AbstractPaginatedDragAndDropListComponent<T extends DSpace
finish: () => { finish: () => {
if (isNewPage) { if (isNewPage) {
this.currentPage$.next(redirectPage); this.currentPage$.next(redirectPage);
this.loading$.next(false);
} }
} }
})); }));