Merge pull request #873 from atmire/w2p-fix-bitstreams-pagination-on-item-page

Fix bug concerning bistream pagination on the item page
This commit is contained in:
Tim Donohue
2020-09-29 11:51:02 -05:00
committed by GitHub
8 changed files with 161 additions and 109 deletions

View File

@@ -1,5 +1,6 @@
<ds-metadata-field-wrapper [label]="label | translate"> <ds-metadata-field-wrapper [label]="label | translate">
<div *ngVar="(originals$ | async)?.payload as originals"> <div *ngVar="(originals$ | async)?.payload as originals">
<div *ngIf="hasValuesInBundle(originals)">
<h5 class="simple-view-element-header">{{"item.page.filesection.original.bundle" | translate}}</h5> <h5 class="simple-view-element-header">{{"item.page.filesection.original.bundle" | translate}}</h5>
<ds-pagination *ngIf="originals?.page?.length > 0" <ds-pagination *ngIf="originals?.page?.length > 0"
[hideGear]="true" [hideGear]="true"
@@ -11,7 +12,6 @@
(pageChange)="switchOriginalPage($event)"> (pageChange)="switchOriginalPage($event)">
<div class="file-section row" *ngFor="let file of originals?.page;"> <div class="file-section row" *ngFor="let file of originals?.page;">
<div class="col-3"> <div class="col-3">
<ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail> <ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail>
@@ -22,7 +22,7 @@
<dd class="col-md-8">{{file.name}}</dd> <dd class="col-md-8">{{file.name}}</dd>
<dt class="col-md-4">{{"item.page.filesection.size" | translate}}</dt> <dt class="col-md-4">{{"item.page.filesection.size" | translate}}</dt>
<dd class="col-md-8">{{(file.sizeBytes) | dsFileSize }}</dd> <dd class="col-md-8">{{(file.sizeBytes) | dsFileSize }}</dd>
<dt class="col-md-4">{{"item.page.filesection.format" | translate}}</dt> <dt class="col-md-4">{{"item.page.filesection.format" | translate}}</dt>
@@ -41,8 +41,9 @@
</div> </div>
</ds-pagination> </ds-pagination>
</div> </div>
</div>
<div *ngVar="(licenses$ | async)?.payload as licenses"> <div *ngVar="(licenses$ | async)?.payload as licenses">
<div *ngIf="hasValuesInBundle(licenses)">
<h5 class="simple-view-element-header">{{"item.page.filesection.license.bundle" | translate}}</h5> <h5 class="simple-view-element-header">{{"item.page.filesection.license.bundle" | translate}}</h5>
<ds-pagination *ngIf="licenses?.page?.length > 0" <ds-pagination *ngIf="licenses?.page?.length > 0"
[hideGear]="true" [hideGear]="true"
@@ -54,7 +55,6 @@
(pageChange)="switchLicensePage($event)"> (pageChange)="switchLicensePage($event)">
<div class="file-section row" *ngFor="let file of licenses?.page;"> <div class="file-section row" *ngFor="let file of licenses?.page;">
<div class="col-3"> <div class="col-3">
<ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail> <ds-thumbnail [thumbnail]="(file.thumbnail | async)?.payload"></ds-thumbnail>
@@ -65,8 +65,7 @@
<dd class="col-md-8">{{file.name}}</dd> <dd class="col-md-8">{{file.name}}</dd>
<dt class="col-md-4">{{"item.page.filesection.size" | translate}}</dt> <dt class="col-md-4">{{"item.page.filesection.size" | translate}}</dt>
<dd class="col-md-8">{{(file.sizeBytes) | dsFileSize }}</dd> <dd class="col-md-8">{{(file.sizeBytes) | dsFileSize }}</dd>
<dt class="col-md-4">{{"item.page.filesection.format" | translate}}</dt> <dt class="col-md-4">{{"item.page.filesection.format" | translate}}</dt>
<dd class="col-md-8">{{(file.format | async)?.payload?.description}}</dd> <dd class="col-md-8">{{(file.format | async)?.payload?.description}}</dd>
@@ -84,4 +83,5 @@
</div> </div>
</ds-pagination> </ds-pagination>
</div> </div>
</div>
</ds-metadata-field-wrapper> </ds-metadata-field-wrapper>

View File

@@ -14,6 +14,8 @@ import {Bitstream} from '../../../../core/shared/bitstream.model';
import {of as observableOf} from 'rxjs'; import {of as observableOf} from 'rxjs';
import {MockBitstreamFormat1} from '../../../../shared/mocks/item.mock'; import {MockBitstreamFormat1} from '../../../../shared/mocks/item.mock';
import {By} from '@angular/platform-browser'; import {By} from '@angular/platform-browser';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
describe('FullFileSectionComponent', () => { describe('FullFileSectionComponent', () => {
let comp: FullFileSectionComponent; let comp: FullFileSectionComponent;
@@ -61,7 +63,8 @@ describe('FullFileSectionComponent', () => {
}), BrowserAnimationsModule], }), BrowserAnimationsModule],
declarations: [FullFileSectionComponent, VarDirective, FileSizePipe, MetadataFieldWrapperComponent], declarations: [FullFileSectionComponent, VarDirective, FileSizePipe, MetadataFieldWrapperComponent],
providers: [ providers: [
{provide: BitstreamDataService, useValue: bitstreamDataService} {provide: BitstreamDataService, useValue: bitstreamDataService},
{provide: NotificationsService, useValue: new NotificationsServiceStub()}
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -10,6 +10,10 @@ import { PaginationComponentOptions } from '../../../../shared/pagination/pagina
import { PaginatedList } from '../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../core/data/paginated-list';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { hasValue, isEmpty } from '../../../../shared/empty.util';
import { tap } from 'rxjs/internal/operators/tap';
/** /**
* This component renders the file section of the item * This component renders the file section of the item
@@ -31,14 +35,14 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
licenses$: Observable<RemoteData<PaginatedList<Bitstream>>>; licenses$: Observable<RemoteData<PaginatedList<Bitstream>>>;
pageSize = 5; pageSize = 5;
originalOptions = Object.assign(new PaginationComponentOptions(),{ originalOptions = Object.assign(new PaginationComponentOptions(), {
id: 'original-bitstreams-options', id: 'original-bitstreams-options',
currentPage: 1, currentPage: 1,
pageSize: this.pageSize pageSize: this.pageSize
}); });
originalCurrentPage$ = new BehaviorSubject<number>(1); originalCurrentPage$ = new BehaviorSubject<number>(1);
licenseOptions = Object.assign(new PaginationComponentOptions(),{ licenseOptions = Object.assign(new PaginationComponentOptions(), {
id: 'license-bitstreams-options', id: 'license-bitstreams-options',
currentPage: 1, currentPage: 1,
pageSize: this.pageSize pageSize: this.pageSize
@@ -46,9 +50,11 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
licenseCurrentPage$ = new BehaviorSubject<number>(1); licenseCurrentPage$ = new BehaviorSubject<number>(1);
constructor( constructor(
bitstreamDataService: BitstreamDataService bitstreamDataService: BitstreamDataService,
protected notificationsService: NotificationsService,
protected translateService: TranslateService
) { ) {
super(bitstreamDataService); super(bitstreamDataService, notificationsService, translateService);
} }
ngOnInit(): void { ngOnInit(): void {
@@ -60,18 +66,30 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
switchMap((pageNumber: number) => this.bitstreamDataService.findAllByItemAndBundleName( switchMap((pageNumber: number) => this.bitstreamDataService.findAllByItemAndBundleName(
this.item, this.item,
'ORIGINAL', 'ORIGINAL',
{ elementsPerPage: this.pageSize, currentPage: pageNumber }, {elementsPerPage: this.pageSize, currentPage: pageNumber},
followLink( 'format') followLink('format')
)) )),
tap((rd: RemoteData<PaginatedList<Bitstream>>) => {
if (hasValue(rd.error)) {
this.notificationsService.error(this.translateService.get('file-section.error.header'), `${rd.error.statusCode} ${rd.error.message}`);
}
}
)
); );
this.licenses$ = this.licenseCurrentPage$.pipe( this.licenses$ = this.licenseCurrentPage$.pipe(
switchMap((pageNumber: number) => this.bitstreamDataService.findAllByItemAndBundleName( switchMap((pageNumber: number) => this.bitstreamDataService.findAllByItemAndBundleName(
this.item, this.item,
'LICENSE', 'LICENSE',
{ elementsPerPage: this.pageSize, currentPage: pageNumber }, {elementsPerPage: this.pageSize, currentPage: pageNumber},
followLink( 'format') followLink('format')
)) )),
tap((rd: RemoteData<PaginatedList<Bitstream>>) => {
if (hasValue(rd.error)) {
this.notificationsService.error(this.translateService.get('file-section.error.header'), `${rd.error.statusCode} ${rd.error.message}`);
}
}
)
); );
} }
@@ -93,4 +111,8 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
this.licenseOptions.currentPage = page; this.licenseOptions.currentPage = page;
this.licenseCurrentPage$.next(page); this.licenseCurrentPage$.next(page);
} }
hasValuesInBundle(bundle: PaginatedList<Bitstream>) {
return hasValue(bundle) && !isEmpty(bundle.page);
}
} }

View File

@@ -15,6 +15,8 @@ import {FileSizePipe} from '../../../../shared/utils/file-size-pipe';
import {PageInfo} from '../../../../core/shared/page-info.model'; import {PageInfo} from '../../../../core/shared/page-info.model';
import {MetadataFieldWrapperComponent} from '../../../field-components/metadata-field-wrapper/metadata-field-wrapper.component'; import {MetadataFieldWrapperComponent} from '../../../field-components/metadata-field-wrapper/metadata-field-wrapper.component';
import {createPaginatedList} from '../../../../shared/testing/utils.test'; import {createPaginatedList} from '../../../../shared/testing/utils.test';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
describe('FileSectionComponent', () => { describe('FileSectionComponent', () => {
let comp: FileSectionComponent; let comp: FileSectionComponent;
@@ -62,7 +64,8 @@ describe('FileSectionComponent', () => {
}), BrowserAnimationsModule], }), BrowserAnimationsModule],
declarations: [FileSectionComponent, VarDirective, FileSizePipe, MetadataFieldWrapperComponent], declarations: [FileSectionComponent, VarDirective, FileSizePipe, MetadataFieldWrapperComponent],
providers: [ providers: [
{provide: BitstreamDataService, useValue: bitstreamDataService} {provide: BitstreamDataService, useValue: bitstreamDataService},
{provide: NotificationsService, useValue: new NotificationsServiceStub()}
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -4,10 +4,12 @@ import { BitstreamDataService } from '../../../../core/data/bitstream-data.servi
import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Bitstream } from '../../../../core/shared/bitstream.model';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { filter, takeWhile } from 'rxjs/operators'; import { filter, take } from 'rxjs/operators';
import { RemoteData } from '../../../../core/data/remote-data'; import { RemoteData } from '../../../../core/data/remote-data';
import { hasNoValue, hasValue } from '../../../../shared/empty.util'; import { hasValue } from '../../../../shared/empty.util';
import { PaginatedList } from '../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../core/data/paginated-list';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
/** /**
* This component renders the file section of the item * This component renders the file section of the item
@@ -36,7 +38,9 @@ export class FileSectionComponent implements OnInit {
pageSize = 5; pageSize = 5;
constructor( constructor(
protected bitstreamDataService: BitstreamDataService protected bitstreamDataService: BitstreamDataService,
protected notificationsService: NotificationsService,
protected translateService: TranslateService
) { ) {
} }
@@ -58,14 +62,21 @@ export class FileSectionComponent implements OnInit {
} else { } else {
this.currentPage++; this.currentPage++;
} }
this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'ORIGINAL', { currentPage: this.currentPage, elementsPerPage: this.pageSize }).pipe( this.bitstreamDataService.findAllByItemAndBundleName(this.item, 'ORIGINAL', {
filter((bitstreamsRD: RemoteData<PaginatedList<Bitstream>>) => hasValue(bitstreamsRD)), currentPage: this.currentPage,
takeWhile((bitstreamsRD: RemoteData<PaginatedList<Bitstream>>) => hasNoValue(bitstreamsRD.payload) && hasNoValue(bitstreamsRD.error), true) elementsPerPage: this.pageSize
}).pipe(
filter((bitstreamsRD: RemoteData<PaginatedList<Bitstream>>) => hasValue(bitstreamsRD) && (hasValue(bitstreamsRD.error) || hasValue(bitstreamsRD.payload))),
take(1),
).subscribe((bitstreamsRD: RemoteData<PaginatedList<Bitstream>>) => { ).subscribe((bitstreamsRD: RemoteData<PaginatedList<Bitstream>>) => {
if (bitstreamsRD.error) {
this.notificationsService.error(this.translateService.get('file-section.error.header'), `${bitstreamsRD.error.statusCode} ${bitstreamsRD.error.message}`);
} else if (hasValue(bitstreamsRD.payload)) {
const current: Bitstream[] = this.bitstreams$.getValue(); const current: Bitstream[] = this.bitstreams$.getValue();
this.bitstreams$.next([...current, ...bitstreamsRD.payload.page]); this.bitstreams$.next([...current, ...bitstreamsRD.payload.page]);
this.isLoading = false; this.isLoading = false;
this.isLastPage = this.currentPage === bitstreamsRD.payload.totalPages; this.isLastPage = this.currentPage === bitstreamsRD.payload.totalPages;
}
}); });
} }
} }

View File

@@ -30,6 +30,8 @@ import { RestResponse } from '../cache/response.models';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { configureRequest, getResponseFromEntry } from '../shared/operators'; import { configureRequest, getResponseFromEntry } from '../shared/operators';
import { combineLatest as observableCombineLatest } from 'rxjs'; import { combineLatest as observableCombineLatest } from 'rxjs';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { PageInfo } from '../shared/page-info.model';
/** /**
* A service to retrieve {@link Bitstream}s from the REST API * A service to retrieve {@link Bitstream}s from the REST API
@@ -165,8 +167,10 @@ export class BitstreamDataService extends DataService<Bitstream> {
public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Bitstream>>): Observable<RemoteData<PaginatedList<Bitstream>>> { public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Bitstream>>): Observable<RemoteData<PaginatedList<Bitstream>>> {
return this.bundleService.findByItemAndName(item, bundleName).pipe( return this.bundleService.findByItemAndName(item, bundleName).pipe(
switchMap((bundleRD: RemoteData<Bundle>) => { switchMap((bundleRD: RemoteData<Bundle>) => {
if (hasValue(bundleRD.payload)) { if (bundleRD.hasSucceeded && hasValue(bundleRD.payload)) {
return this.findAllByBundle(bundleRD.payload, options, ...linksToFollow); return this.findAllByBundle(bundleRD.payload, options, ...linksToFollow);
} else if (!bundleRD.hasSucceeded && bundleRD.error.statusCode === 404) {
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
} else { } else {
return [bundleRD as any]; return [bundleRD as any];
} }

View File

@@ -22,6 +22,7 @@ import { FindListOptions, GetRequest } from './request.models';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model'; import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
import { Bitstream } from '../shared/bitstream.model'; import { Bitstream } from '../shared/bitstream.model';
import { RemoteDataError } from './remote-data-error';
/** /**
* A service to retrieve {@link Bundle}s from the REST API * A service to retrieve {@link Bundle}s from the REST API
@@ -71,6 +72,7 @@ export class BundleDataService extends DataService<Bundle> {
if (hasValue(rd.payload) && hasValue(rd.payload.page)) { if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
const matchingBundle = rd.payload.page.find((bundle: Bundle) => const matchingBundle = rd.payload.page.find((bundle: Bundle) =>
bundle.name === bundleName); bundle.name === bundleName);
if (hasValue(matchingBundle)) {
return new RemoteData( return new RemoteData(
false, false,
false, false,
@@ -78,6 +80,9 @@ export class BundleDataService extends DataService<Bundle> {
undefined, undefined,
matchingBundle matchingBundle
); );
} else {
return new RemoteData(false, false, false, new RemoteDataError(404, 'Not found', `The bundle with name ${bundleName} was not found.` ))
}
} else { } else {
return rd as any; return rd as any;
} }

View File

@@ -1107,6 +1107,10 @@
"file-section.error.header": "Error obtaining files for this item",
"footer.copyright": "copyright © 2002-{{ year }}", "footer.copyright": "copyright © 2002-{{ year }}",
"footer.link.dspace": "DSpace software", "footer.link.dspace": "DSpace software",