diff --git a/src/app/license-contract-page/license-contract-page.component.html b/src/app/license-contract-page/license-contract-page.component.html index d0704bf2910..4b362a6a7c1 100644 --- a/src/app/license-contract-page/license-contract-page.component.html +++ b/src/app/license-contract-page/license-contract-page.component.html @@ -1,13 +1,48 @@
-
+
{{'contract.message.distribution-license-agreement' | translate}}
-
-
-

{{collection?.name}}

+ + + + +
+
+

{{collectionRD?.payload?.name}}

-
-
+
+
+ +
+ + + + +
+
+

{{collection?.name}}

+ +
+
+
+ + + + + +
+
diff --git a/src/app/license-contract-page/license-contract-page.component.spec.ts b/src/app/license-contract-page/license-contract-page.component.spec.ts index 7b13615c868..3ef0097471d 100644 --- a/src/app/license-contract-page/license-contract-page.component.spec.ts +++ b/src/app/license-contract-page/license-contract-page.component.spec.ts @@ -5,21 +5,34 @@ import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; import { ActivatedRoute, Params } from '@angular/router'; -import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; +import { Directive, Input, NO_ERRORS_SCHEMA } from '@angular/core'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { CollectionDataService } from '../core/data/collection-data.service'; import { Collection } from '../core/shared/collection.model'; -import { mockLicenseRD$ } from '../shared/testing/clarin-license-mock'; -import { take } from 'rxjs/operators'; -import { getFirstCompletedRemoteData } from '../core/shared/operators'; +import { License } from '../core/shared/license.model'; +import { PaginationService } from '../core/pagination/pagination.service'; +import { buildPaginatedList } from '../core/data/paginated-list.model'; +import { PageInfo } from '../core/shared/page-info.model'; +import { of as observableOf } from 'rxjs'; +import { FindListOptions } from '../core/data/find-list-options.model'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; + +/* eslint-disable @angular-eslint/directive-selector */ +@Directive({ + selector: '[ngVar]' +}) +class MockNgVarDirective { + @Input() ngVar: unknown; +} describe('LicenseContractPageComponent', () => { let component: LicenseContractPageComponent; let fixture: ComponentFixture; - let collection: Collection; - let routeStub: any; - let collectionService: CollectionDataService; + let collectionService: jasmine.SpyObj; + let paginationService: jasmine.SpyObj; const paramCollectionId = 'collectionId'; const paramCollectionIdValue = '1'; @@ -27,23 +40,72 @@ describe('LicenseContractPageComponent', () => { const paramObject: Params = {}; paramObject[paramCollectionId] = paramCollectionIdValue; - collection = Object.assign(new Collection(), { + const singleCollectionLicense = Object.assign(new License(), { + text: 'Single collection license text' + }); + + const collection = Object.assign(new Collection(), { uuid: 'fake-collection-id', + name: 'Single collection', _links: { self: {href: 'collection-selflink'}, license: {href: 'license-link'} }, - license: mockLicenseRD$ + license: createSuccessfulRemoteDataObject$(singleCollectionLicense) }); + const secondCollectionLicense = Object.assign(new License(), { + text: 'Second collection license text' + }); + + const authorizedCollections = [ + collection, + Object.assign(new Collection(), { + uuid: 'second-collection-id', + name: 'Second collection', + _links: { + self: { href: 'second-collection-selflink' }, + license: { href: 'second-license-link' } + }, + license: createSuccessfulRemoteDataObject$(secondCollectionLicense) + }) + ]; + + const authorizedCollectionsRD$ = createSuccessfulRemoteDataObject$( + buildPaginatedList( + new PageInfo({ + currentPage: 1, + elementsPerPage: 10, + totalElements: authorizedCollections.length, + totalPages: 1 + }), + authorizedCollections + ) + ); + routeStub = { snapshot: { - queryParams: paramObject, + queryParams: { ...paramObject }, } }; - collectionService = jasmine.createSpyObj('collectionService', { - findById: createSuccessfulRemoteDataObject$(collection) + collectionService = jasmine.createSpyObj('collectionService', { + findById: createSuccessfulRemoteDataObject$(collection), + getAuthorizedCollection: authorizedCollectionsRD$ + }); + + paginationService = jasmine.createSpyObj('paginationService', { + getFindListOptions: observableOf(Object.assign(new FindListOptions(), { + currentPage: 1, + elementsPerPage: 10 + })), + getCurrentPagination: observableOf(Object.assign(new PaginationComponentOptions(), { + currentPage: 1, + pageSize: 10, + pageSizeOptions: [1, 5, 10, 20, 40, 60, 80, 100], + })), + getCurrentSort: observableOf(new SortOptions('name', SortDirection.ASC)), + clearPagination: undefined }); beforeEach(async () => { @@ -53,20 +115,29 @@ describe('LicenseContractPageComponent', () => { CommonModule, FormsModule, ReactiveFormsModule, - TranslateModule.forRoot() + TranslateModule.forRoot(), ], declarations: [ - LicenseContractPageComponent + LicenseContractPageComponent, + MockNgVarDirective ], providers: [ { provide: ActivatedRoute, useValue: routeStub }, { provide: CollectionDataService, useValue: collectionService }, - ] + { provide: PaginationService, useValue: paginationService }, + ], + schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); }); beforeEach(() => { + routeStub.snapshot.queryParams = { ...paramObject }; + collectionService.findById.and.returnValue(createSuccessfulRemoteDataObject$(collection)); + collectionService.findById.calls.reset(); + collectionService.getAuthorizedCollection.calls.reset(); + paginationService.getFindListOptions.calls.reset(); + paginationService.clearPagination.calls.reset(); fixture = TestBed.createComponent(LicenseContractPageComponent); component = fixture.componentInstance; fixture.detectChanges(); @@ -77,19 +148,52 @@ describe('LicenseContractPageComponent', () => { }); it('should load collectionRD$', () => { - collectionService.findById(collection.uuid) - .pipe(getFirstCompletedRemoteData()) - .subscribe(collectionRD$ => { - expect(component.collectionRD$.value).toEqual(collectionRD$); - }); + expect(component.collectionRD$.value.payload).toEqual(collection); }); it('should load licenseRD$', () => { - collection.license - .pipe(take(1)) - .subscribe(licenseRD$ => { - expect(component.licenseRD$.value).toEqual(licenseRD$); - }); + expect(component.licenseRD$.value.payload).toEqual(singleCollectionLicense); + }); + + it('should set hasFailed on collectionRD$ when collectionId is bogus', () => { + collectionService.findById.and.returnValue(createFailedRemoteDataObject$('Not Found', 404)); + collectionService.findById.calls.reset(); + + const failFixture = TestBed.createComponent(LicenseContractPageComponent); + const failComponent = failFixture.componentInstance; + failFixture.detectChanges(); + + expect(collectionService.findById).toHaveBeenCalled(); + expect(failComponent.collectionRD$.value.hasFailed).toBeTrue(); + }); + + it('should load authorized collections when collectionId is missing', () => { + routeStub.snapshot.queryParams = {}; + collectionService.findById.calls.reset(); + collectionService.getAuthorizedCollection.calls.reset(); + + const listFixture = TestBed.createComponent(LicenseContractPageComponent); + const listComponent = listFixture.componentInstance; + listFixture.detectChanges(); + + expect(collectionService.findById).not.toHaveBeenCalled(); + expect(collectionService.getAuthorizedCollection).toHaveBeenCalled(); + + listComponent.collectionsRD$.subscribe((collectionsRD) => { + expect(collectionsRD.payload.page).toEqual(authorizedCollections); + }); + }); + + it('should clear pagination state on destroy in list mode', () => { + routeStub.snapshot.queryParams = {}; + paginationService.clearPagination.calls.reset(); + + const listFixture = TestBed.createComponent(LicenseContractPageComponent); + const listComponent = listFixture.componentInstance; + listFixture.detectChanges(); + listComponent.ngOnDestroy(); + + expect(paginationService.clearPagination).toHaveBeenCalledWith(listComponent.paginationId); }); }); diff --git a/src/app/license-contract-page/license-contract-page.component.ts b/src/app/license-contract-page/license-contract-page.component.ts index cdba8d33fc5..4849888f792 100644 --- a/src/app/license-contract-page/license-contract-page.component.ts +++ b/src/app/license-contract-page/license-contract-page.component.ts @@ -1,13 +1,17 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, EMPTY, Observable, Subject } from 'rxjs'; +import { filter, switchMap, takeUntil, tap } from 'rxjs/operators'; import { RemoteData } from '../core/data/remote-data'; import { Collection } from '../core/shared/collection.model'; import { CollectionDataService } from '../core/data/collection-data.service'; import { License } from '../core/shared/license.model'; import { followLink } from '../shared/utils/follow-link-config.model'; -import { filter } from 'rxjs/operators'; -import { isNotUndefined } from '../shared/empty.util'; +import { isNotEmpty } from '../shared/empty.util'; +import { PaginatedList } from '../core/data/paginated-list.model'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { FindListOptions } from '../core/data/find-list-options.model'; +import { PaginationService } from '../core/pagination/pagination.service'; /** * The component load and show distribution license based on the collection. @@ -17,10 +21,14 @@ import { isNotUndefined } from '../shared/empty.util'; templateUrl: './license-contract-page.component.html', styleUrls: ['./license-contract-page.component.scss'] }) -export class LicenseContractPageComponent implements OnInit { +export class LicenseContractPageComponent implements OnInit, OnDestroy { + + readonly paginationId = 'contract-collections'; + private readonly destroy$ = new Subject(); constructor(private route: ActivatedRoute, - protected collectionDataService: CollectionDataService,) { + protected collectionDataService: CollectionDataService, + protected paginationService: PaginationService,) { } /** @@ -38,18 +46,58 @@ export class LicenseContractPageComponent implements OnInit { */ licenseRD$: BehaviorSubject> = new BehaviorSubject>(null); + /** + * Collection list RemoteData object loaded from the API. + */ + collectionsRD$: Observable>>; + + /** + * The current pagination configuration for the page used by the authorized collection request. + */ + config: FindListOptions = Object.assign(new FindListOptions(), { + elementsPerPage: 10 + }); + + /** + * The current pagination configuration for the page. + */ + pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { + id: this.paginationId, + pageSize: 10 + }); + ngOnInit(): void { this.collectionId = this.route.snapshot.queryParams.collectionId; - this.collectionDataService.findById(this.collectionId, false, true, followLink('license')) - .pipe( - filter((collectionData: RemoteData) => isNotUndefined((collectionData.payload)))) - .subscribe(res => { - // load collection - this.collectionRD$.next(res); - res.payload.license.subscribe(licenseRD$ => { - // load license of the collection - this.licenseRD$.next(licenseRD$); - }); - }); + if (isNotEmpty(this.collectionId)) { + this.collectionDataService.findById(this.collectionId, false, true, followLink('license')) + .pipe( + tap((collectionData: RemoteData) => this.collectionRD$.next(collectionData)), + filter((collectionData: RemoteData) => isNotEmpty(collectionData.payload)), + switchMap((collectionData: RemoteData) => collectionData.payload.license ?? EMPTY), + tap((licenseRD: RemoteData) => this.licenseRD$.next(licenseRD)), + takeUntil(this.destroy$) + ) + .subscribe(); + } else { + this.loadAuthorizedCollections(); + } + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + if (this.isListMode()) { + this.paginationService.clearPagination(this.paginationId); + } + } + + isListMode(): boolean { + return !isNotEmpty(this.collectionId); + } + + private loadAuthorizedCollections(): void { + this.collectionsRD$ = this.paginationService.getFindListOptions(this.paginationId, this.config).pipe( + switchMap((config: FindListOptions) => this.collectionDataService.getAuthorizedCollection('', config, true, true, followLink('license'))) + ); } }