diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html index cc4b0c22a1..e10b9da247 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html @@ -42,6 +42,7 @@ [key]="'map'" [dsoRD$]="mappedItemsRD$" [paginationOptions]="(searchOptions$ | async)?.pagination" + [featureId]="FeatureIds.CanManageMappings" [confirmButton]="'collection.edit.item-mapper.confirm'" [cancelButton]="'collection.edit.item-mapper.cancel'" (confirm)="mapItems($event)" diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index 49b6a0d63c..5ae1445cef 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -40,6 +40,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createPaginatedList } from '../../shared/testing/utils.test'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; describe('CollectionItemMapperComponent', () => { let comp: CollectionItemMapperComponent; @@ -136,6 +137,10 @@ describe('CollectionItemMapperComponent', () => { } }; + const authorizationDataService = jasmine.createSpyObj('authorizationDataService', { + isAuthorized: observableOf(true) + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], @@ -152,6 +157,7 @@ describe('CollectionItemMapperComponent', () => { { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, { provide: RouteService, useValue: routeServiceStub }, + { provide: AuthorizationDataService, useValue: authorizationDataService } ] }).compileComponents(); })); diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts index 571b755897..ce5ea5a142 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts @@ -28,6 +28,7 @@ import { PaginatedSearchOptions } from '../../shared/search/paginated-search-opt import { SearchService } from '../../core/shared/search/search.service'; import { followLink } from '../../shared/utils/follow-link-config.model'; import { NoContent } from '../../core/shared/NoContent.model'; +import { FeatureID } from '../../core/data/feature-authorization/feature-id'; @Component({ selector: 'ds-collection-item-mapper', @@ -50,6 +51,8 @@ import { NoContent } from '../../core/shared/NoContent.model'; */ export class CollectionItemMapperComponent implements OnInit { + FeatureIds = FeatureID; + /** * A view on the tabset element * Used to switch tabs programmatically diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts index d3577c3637..c15c84a647 100644 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts @@ -40,6 +40,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { createPaginatedList } from '../../../shared/testing/utils.test'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; describe('ItemCollectionMapperComponent', () => { let comp: ItemCollectionMapperComponent; @@ -110,6 +111,10 @@ describe('ItemCollectionMapperComponent', () => { onDefaultLangChange: new EventEmitter() }; + const authorizationDataService = jasmine.createSpyObj('authorizationDataService', { + isAuthorized: observableOf(true) + }); + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule], @@ -124,7 +129,8 @@ describe('ItemCollectionMapperComponent', () => { { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, { provide: TranslateService, useValue: translateServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: CollectionDataService, useValue: collectionDataServiceStub } + { provide: CollectionDataService, useValue: collectionDataServiceStub }, + { provide: AuthorizationDataService, useValue: authorizationDataService } ] }).compileComponents(); })); diff --git a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts index 199bc56647..b8db9b9344 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts +++ b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts @@ -13,11 +13,10 @@ import { CollectionSelectComponent } from './collection-select.component'; import { Collection } from '../../../core/shared/collection.model'; import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; import { createPaginatedList } from '../../testing/utils.test'; -import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; -import { FindListOptions } from '../../../core/data/request.models'; -import { of as observableOf } from 'rxjs'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../testing/pagination-service.stub'; +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; describe('CollectionSelectComponent', () => { let comp: CollectionSelectComponent; @@ -41,6 +40,10 @@ describe('CollectionSelectComponent', () => { currentPage: 1 }); + const authorizationDataService = jasmine.createSpyObj('authorizationDataService', { + isAuthorized: observableOf(true) + }); + const paginationService = new PaginationServiceStub(); beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -49,7 +52,8 @@ describe('CollectionSelectComponent', () => { providers: [ { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockCollectionList[1].id]) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: PaginationService, useValue: paginationService } + { provide: PaginationService, useValue: paginationService }, + { provide: AuthorizationDataService, useValue: authorizationDataService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/shared/object-select/collection-select/collection-select.component.ts b/src/app/shared/object-select/collection-select/collection-select.component.ts index 3d40b469da..a02305f116 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.ts +++ b/src/app/shared/object-select/collection-select/collection-select.component.ts @@ -3,6 +3,7 @@ import { Collection } from '../../../core/shared/collection.model'; import { ObjectSelectComponent } from '../object-select/object-select.component'; import { isNotEmpty } from '../../empty.util'; import { ObjectSelectService } from '../object-select.service'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; @Component({ selector: 'ds-collection-select', @@ -14,8 +15,9 @@ import { ObjectSelectService } from '../object-select.service'; */ export class CollectionSelectComponent extends ObjectSelectComponent { - constructor(protected objectSelectService: ObjectSelectService) { - super(objectSelectService); + constructor(protected objectSelectService: ObjectSelectService, + protected authorizationService: AuthorizationDataService) { + super(objectSelectService, authorizationService); } ngOnInit(): void { diff --git a/src/app/shared/object-select/item-select/item-select.component.html b/src/app/shared/object-select/item-select/item-select.component.html index dab5b2f715..e53f2af99b 100644 --- a/src/app/shared/object-select/item-select/item-select.component.html +++ b/src/app/shared/object-select/item-select/item-select.component.html @@ -19,7 +19,7 @@ - + {{collection?.name}} diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index 224fb764b6..de52f1c3c2 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -11,13 +11,13 @@ import { HostWindowService } from '../../host-window.service'; import { HostWindowServiceStub } from '../../testing/host-window-service.stub'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { of as observableOf, of } from 'rxjs'; +import { of } from 'rxjs'; import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; import { createPaginatedList } from '../../testing/utils.test'; import { PaginationService } from '../../../core/pagination/pagination.service'; -import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; -import { FindListOptions } from '../../../core/data/request.models'; import { PaginationServiceStub } from '../../testing/pagination-service.stub'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; @@ -39,7 +39,8 @@ describe('ItemSelectComponent', () => { key: 'dc.type', language: null, value: 'Article' - }] + }], + _links: { self: { href: 'selfId1' } } }), Object.assign(new Item(), { id: 'id2', @@ -54,7 +55,8 @@ describe('ItemSelectComponent', () => { key: 'dc.type', language: null, value: 'Article' - }] + }], + _links: { self: { href: 'selfId2' } } }) ]; const mockItems = createSuccessfulRemoteDataObject$(createPaginatedList(mockItemList)); @@ -66,6 +68,7 @@ describe('ItemSelectComponent', () => { paginationService = new PaginationServiceStub(mockPaginationOptions); + const authorizationDataService = new AuthorizationDataService(null, null, null, null, null, null, null, null, null, null); beforeEach(waitForAsync(() => { @@ -75,7 +78,8 @@ describe('ItemSelectComponent', () => { providers: [ { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: PaginationService, useValue: paginationService } + { provide: PaginationService, useValue: paginationService }, + { provide: AuthorizationDataService, useValue: authorizationDataService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -146,4 +150,21 @@ describe('ItemSelectComponent', () => { expect(comp.cancel.emit).toHaveBeenCalled(); }); }); + + describe('when the authorize feature is not authorized', () => { + + beforeEach(() => { + comp.featureId = FeatureID.CanManageMappings; + spyOn(authorizationDataService, 'isAuthorized').and.returnValue(of(false)); + }); + + it('should disable the checkbox', waitForAsync(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + const checkbox = fixture.debugElement.query(By.css('input.item-checkbox')).nativeElement; + expect(authorizationDataService.isAuthorized).toHaveBeenCalled(); + expect(checkbox.disabled).toBeTrue(); + }); + })); + }); }); diff --git a/src/app/shared/object-select/item-select/item-select.component.ts b/src/app/shared/object-select/item-select/item-select.component.ts index b4918ada99..80808311a0 100644 --- a/src/app/shared/object-select/item-select/item-select.component.ts +++ b/src/app/shared/object-select/item-select/item-select.component.ts @@ -7,6 +7,7 @@ import { Observable } from 'rxjs'; import { getAllSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { map } from 'rxjs/operators'; import { getItemPageRoute } from '../../../+item-page/item-page-routing-paths'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; @Component({ selector: 'ds-item-select', @@ -33,8 +34,9 @@ export class ItemSelectComponent extends ObjectSelectComponent { [itemId: string]: string }>; - constructor(protected objectSelectService: ObjectSelectService) { - super(objectSelectService); + constructor(protected objectSelectService: ObjectSelectService, + protected authorizationService: AuthorizationDataService ) { + super(objectSelectService, authorizationService); } ngOnInit(): void { diff --git a/src/app/shared/object-select/object-select/object-select.component.ts b/src/app/shared/object-select/object-select/object-select.component.ts index f8be095719..165a8c0a4f 100644 --- a/src/app/shared/object-select/object-select/object-select.component.ts +++ b/src/app/shared/object-select/object-select/object-select.component.ts @@ -1,11 +1,15 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { take } from 'rxjs/operators'; +import { startWith, take } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; import { ObjectSelectService } from '../object-select.service'; import { SortOptions } from '../../../core/cache/models/sort-options.model'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { of } from 'rxjs/internal/observable/of'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; /** * An abstract component used to select DSpaceObjects from a specific list and returning the UUIDs of the selected DSpaceObjects @@ -47,6 +51,12 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro @Input() confirmButton: string; + /** + * Authorize check to enable the selection when present. + */ + @Input() + featureId: FeatureID; + /** * The message key used for the cancel button * @type {string} @@ -79,7 +89,8 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro */ selectedIds$: Observable; - constructor(protected objectSelectService: ObjectSelectService) { + constructor(protected objectSelectService: ObjectSelectService, + protected authorizationService: AuthorizationDataService) { } ngOnInit(): void { @@ -107,6 +118,16 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro return this.objectSelectService.getSelected(this.key, id); } + /** + * Return if the item can be selected or not due to authorization check. + */ + canSelect(item: DSpaceObject): Observable { + if (!this.featureId) { + return of(true); + } + return this.authorizationService.isAuthorized(this.featureId, item.self).pipe(startWith(false)); + } + /** * Called when the confirm button is pressed * Sends the selected UUIDs to the parent component