mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge pull request #645 from 4Science/#601-resource-policies
#601 resource policies
This commit is contained in:
@@ -6,7 +6,7 @@ import { GroupsRegistryComponent } from './group-registry/groups-registry.compon
|
|||||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||||
import { getAccessControlModulePath } from '../admin-routing.module';
|
import { getAccessControlModulePath } from '../admin-routing.module';
|
||||||
|
|
||||||
const GROUP_EDIT_PATH = 'groups';
|
export const GROUP_EDIT_PATH = 'groups';
|
||||||
|
|
||||||
export function getGroupEditPath(id: string) {
|
export function getGroupEditPath(id: string) {
|
||||||
return new URLCombiner(getAccessControlModulePath(), GROUP_EDIT_PATH, id).toString();
|
return new URLCombiner(getAccessControlModulePath(), GROUP_EDIT_PATH, id).toString();
|
||||||
|
@@ -8,7 +8,7 @@ import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.ser
|
|||||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
|
|
||||||
const REGISTRIES_MODULE_PATH = 'registries';
|
const REGISTRIES_MODULE_PATH = 'registries';
|
||||||
const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
export const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
||||||
|
|
||||||
export function getRegistriesModulePath() {
|
export function getRegistriesModulePath() {
|
||||||
return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
|
return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
|
||||||
|
@@ -29,6 +29,9 @@ import { ItemEditBitstreamDragHandleComponent } from './item-bitstreams/item-edi
|
|||||||
import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component';
|
import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component';
|
||||||
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
|
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
|
||||||
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
||||||
|
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
|
||||||
|
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
||||||
|
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module that contains all components related to the Edit Item page administrator functionality
|
* Module that contains all components related to the Edit Item page administrator functionality
|
||||||
@@ -67,6 +70,9 @@ import { ItemVersionHistoryComponent } from './item-version-history/item-version
|
|||||||
ItemMoveComponent,
|
ItemMoveComponent,
|
||||||
ItemEditBitstreamDragHandleComponent,
|
ItemEditBitstreamDragHandleComponent,
|
||||||
VirtualMetadataComponent,
|
VirtualMetadataComponent,
|
||||||
|
ItemAuthorizationsComponent,
|
||||||
|
ResourcePolicyEditComponent,
|
||||||
|
ResourcePolicyCreateComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
BundleDataService
|
BundleDataService
|
||||||
|
@@ -14,6 +14,12 @@ import { ItemMoveComponent } from './item-move/item-move.component';
|
|||||||
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
|
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
|
||||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
||||||
|
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
|
||||||
|
import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/resolvers/resource-policy-target.resolver';
|
||||||
|
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
|
||||||
|
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
|
||||||
|
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
|
||||||
|
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
|
|
||||||
export const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
|
export const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
|
||||||
export const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
|
export const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
|
||||||
@@ -21,6 +27,7 @@ export const ITEM_EDIT_PRIVATE_PATH = 'private';
|
|||||||
export const ITEM_EDIT_PUBLIC_PATH = 'public';
|
export const ITEM_EDIT_PUBLIC_PATH = 'public';
|
||||||
export const ITEM_EDIT_DELETE_PATH = 'delete';
|
export const ITEM_EDIT_DELETE_PATH = 'delete';
|
||||||
export const ITEM_EDIT_MOVE_PATH = 'move';
|
export const ITEM_EDIT_MOVE_PATH = 'move';
|
||||||
|
export const ITEM_EDIT_AUTHORIZATIONS_PATH = 'authorizations';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routing module that handles the routing for the Edit Item page administrator functionality
|
* Routing module that handles the routing for the Edit Item page administrator functionality
|
||||||
@@ -111,12 +118,43 @@ export const ITEM_EDIT_MOVE_PATH = 'move';
|
|||||||
path: ITEM_EDIT_MOVE_PATH,
|
path: ITEM_EDIT_MOVE_PATH,
|
||||||
component: ItemMoveComponent,
|
component: ItemMoveComponent,
|
||||||
data: { title: 'item.edit.move.title' },
|
data: { title: 'item.edit.move.title' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ITEM_EDIT_AUTHORIZATIONS_PATH,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'create',
|
||||||
|
resolve: {
|
||||||
|
resourcePolicyTarget: ResourcePolicyTargetResolver
|
||||||
|
},
|
||||||
|
component: ResourcePolicyCreateComponent,
|
||||||
|
data: { title: 'resource-policies.create.page.title' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'edit',
|
||||||
|
resolve: {
|
||||||
|
resourcePolicy: ResourcePolicyResolver
|
||||||
|
},
|
||||||
|
component: ResourcePolicyEditComponent,
|
||||||
|
data: { title: 'resource-policies.edit.page.title' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: ItemAuthorizationsComponent,
|
||||||
|
data: { title: 'item.edit.authorizations.title' }
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
providers: []
|
providers: [
|
||||||
|
I18nBreadcrumbResolver,
|
||||||
|
I18nBreadcrumbsService,
|
||||||
|
ResourcePolicyResolver,
|
||||||
|
ResourcePolicyTargetResolver
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class EditItemPageRoutingModule {
|
export class EditItemPageRoutingModule {
|
||||||
|
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
<div class="container">
|
||||||
|
<ds-alert [type]="'alert-info'" [content]="'item.edit.authorizations.heading'"></ds-alert>
|
||||||
|
<ds-resource-policies [resourceType]="'item'" [resourceUUID]="(getItemUUID() | async)"></ds-resource-policies>
|
||||||
|
<ng-container *ngFor="let bundle of (getItemBundles() | async); trackById">
|
||||||
|
<ds-resource-policies [resourceType]="'bundle'"
|
||||||
|
[resourceUUID]="bundle.id"></ds-resource-policies>
|
||||||
|
<ng-container *ngFor="let bitstream of (bundleBitstreamsMap.get(bundle.id) | async)?.page; trackById">
|
||||||
|
<ds-resource-policies [resourceType]="'bitstream'"
|
||||||
|
[resourceUUID]="bitstream.id"></ds-resource-policies>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
|
@@ -0,0 +1,183 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
|
import { ItemAuthorizationsComponent } from './item-authorizations.component';
|
||||||
|
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||||
|
import { Bundle } from '../../../core/shared/bundle.model';
|
||||||
|
import { createMockRDPaginatedObs } from '../item-bitstreams/item-bitstreams.component.spec';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { getMockLinkService } from '../../../shared/mocks/link-service.mock';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../../shared/testing/utils.test';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
|
|
||||||
|
describe('ItemAuthorizationsComponent test suite', () => {
|
||||||
|
let comp: ItemAuthorizationsComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ItemAuthorizationsComponent>;
|
||||||
|
let de;
|
||||||
|
|
||||||
|
const linkService: any = getMockLinkService();
|
||||||
|
|
||||||
|
const bitstream1 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream1',
|
||||||
|
uuid: 'bitstream1'
|
||||||
|
});
|
||||||
|
const bitstream2 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream2',
|
||||||
|
uuid: 'bitstream2'
|
||||||
|
});
|
||||||
|
const bitstream3 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream3',
|
||||||
|
uuid: 'bitstream3'
|
||||||
|
});
|
||||||
|
const bitstream4 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream4',
|
||||||
|
uuid: 'bitstream4'
|
||||||
|
});
|
||||||
|
const bundle1 = Object.assign(new Bundle(), {
|
||||||
|
id: 'bundle1',
|
||||||
|
uuid: 'bundle1',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'bundle1-selflink' }
|
||||||
|
},
|
||||||
|
bitstreams: createMockRDPaginatedObs([bitstream1, bitstream2])
|
||||||
|
});
|
||||||
|
const bundle2 = Object.assign(new Bundle(), {
|
||||||
|
id: 'bundle2',
|
||||||
|
uuid: 'bundle2',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'bundle2-selflink' }
|
||||||
|
},
|
||||||
|
bitstreams: createMockRDPaginatedObs([bitstream3, bitstream4])
|
||||||
|
});
|
||||||
|
const bundles = [bundle1, bundle2];
|
||||||
|
const bitstreamList1: PaginatedList<Bitstream> = new PaginatedList(new PageInfo(), [bitstream1, bitstream2]);
|
||||||
|
const bitstreamList2: PaginatedList<Bitstream> = new PaginatedList(new PageInfo(), [bitstream3, bitstream4]);
|
||||||
|
|
||||||
|
const item = Object.assign(new Item(), {
|
||||||
|
uuid: 'item',
|
||||||
|
id: 'item',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'item-selflink' }
|
||||||
|
},
|
||||||
|
bundles: createMockRDPaginatedObs([bundle1, bundle2])
|
||||||
|
});
|
||||||
|
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: createSuccessfulRemoteDataObject(item)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
NoopAnimationsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ItemAuthorizationsComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
ItemAuthorizationsComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-item-authorizations></ds-item-authorizations>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ItemAuthorizationsComponent', inject([ItemAuthorizationsComponent], (app: ItemAuthorizationsComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemAuthorizationsComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
linkService.resolveLink.and.callFake((object, link) => object);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init bundles and bitstreams map properly', () => {
|
||||||
|
expect(compAsAny.subs.length).toBe(2);
|
||||||
|
expect(compAsAny.bundles$.value).toEqual(bundles);
|
||||||
|
expect(compAsAny.bundleBitstreamsMap.has('bundle1')).toBeTruthy();
|
||||||
|
expect(compAsAny.bundleBitstreamsMap.has('bundle2')).toBeTruthy();
|
||||||
|
let bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle1');
|
||||||
|
expect(bitstreamList).toBeObservable(cold('(a|)', {
|
||||||
|
a: bitstreamList1
|
||||||
|
}));
|
||||||
|
|
||||||
|
bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle2');
|
||||||
|
expect(bitstreamList).toBeObservable(cold('(a|)', {
|
||||||
|
a: bitstreamList2
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the item UUID', () => {
|
||||||
|
|
||||||
|
expect(comp.getItemUUID()).toBeObservable(cold('(a|)', {
|
||||||
|
a: item.id
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the item\'s bundle', () => {
|
||||||
|
|
||||||
|
expect(comp.getItemBundles()).toBeObservable(cold('a', {
|
||||||
|
a: bundles
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,155 @@
|
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
|
import { catchError, filter, first, flatMap, map, take } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import {
|
||||||
|
getFirstSucceededRemoteDataPayload,
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload
|
||||||
|
} from '../../../core/shared/operators';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { Bundle } from '../../../core/shared/bundle.model';
|
||||||
|
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||||
|
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||||
|
import { FindListOptions } from '../../../core/data/request.models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for a bundle's bitstream map entry
|
||||||
|
*/
|
||||||
|
interface BundleBitstreamsMapEntry {
|
||||||
|
id: string;
|
||||||
|
bitstreams: Observable<PaginatedList<Bitstream>>
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-authorizations',
|
||||||
|
templateUrl: './item-authorizations.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that handles the item Authorizations
|
||||||
|
*/
|
||||||
|
export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map that contains all bitstream of the item's bundles
|
||||||
|
* @type {Observable<Map<string, Observable<PaginatedList<Bitstream>>>>}
|
||||||
|
*/
|
||||||
|
public bundleBitstreamsMap: Map<string, Observable<PaginatedList<Bitstream>>> = new Map<string, Observable<PaginatedList<Bitstream>>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of bundle for the item
|
||||||
|
* @type {Observable<PaginatedList<Bundle>>}
|
||||||
|
*/
|
||||||
|
private bundles$: BehaviorSubject<Bundle[]> = new BehaviorSubject<Bundle[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The target editing item
|
||||||
|
* @type {Observable<Item>}
|
||||||
|
*/
|
||||||
|
private item$: Observable<Item>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {LinkService} linkService
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private linkService: LinkService,
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component, setting up the bundle and bitstream within the item
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.item$ = this.route.data.pipe(
|
||||||
|
map((data) => data.item),
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||||
|
map((item: Item) => this.linkService.resolveLink(
|
||||||
|
item,
|
||||||
|
followLink('bundles', new FindListOptions(), true, followLink('bitstreams'))
|
||||||
|
))
|
||||||
|
) as Observable<Item>;
|
||||||
|
|
||||||
|
const bundles$: Observable<PaginatedList<Bundle>> = this.item$.pipe(
|
||||||
|
filter((item: Item) => isNotEmpty(item.bundles)),
|
||||||
|
flatMap((item: Item) => item.bundles),
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||||
|
catchError((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return observableOf(new PaginatedList(null, []))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.subs.push(
|
||||||
|
bundles$.pipe(
|
||||||
|
take(1),
|
||||||
|
map((list: PaginatedList<Bundle>) => list.page)
|
||||||
|
).subscribe((bundles: Bundle[]) => {
|
||||||
|
this.bundles$.next(bundles);
|
||||||
|
}),
|
||||||
|
bundles$.pipe(
|
||||||
|
take(1),
|
||||||
|
flatMap((list: PaginatedList<Bundle>) => list.page),
|
||||||
|
map((bundle: Bundle) => ({ id: bundle.id, bitstreams: this.getBundleBitstreams(bundle) }))
|
||||||
|
).subscribe((entry: BundleBitstreamsMapEntry) => {
|
||||||
|
this.bundleBitstreamsMap.set(entry.id, entry.bitstreams)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the item's UUID
|
||||||
|
*/
|
||||||
|
getItemUUID(): Observable<string> {
|
||||||
|
return this.item$.pipe(
|
||||||
|
map((item: Item) => item.id),
|
||||||
|
first((UUID: string) => isNotEmpty(UUID))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all item's bundles
|
||||||
|
*
|
||||||
|
* @return an observable that emits all item's bundles
|
||||||
|
*/
|
||||||
|
getItemBundles(): Observable<Bundle[]> {
|
||||||
|
return this.bundles$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all bundle's bitstreams
|
||||||
|
*
|
||||||
|
* @return an observable that emits all item's bundles
|
||||||
|
*/
|
||||||
|
private getBundleBitstreams(bundle: Bundle): Observable<PaginatedList<Bitstream>> {
|
||||||
|
return bundle.bitstreams.pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
catchError((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return observableOf(new PaginatedList(null, []))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe())
|
||||||
|
}
|
||||||
|
}
|
@@ -68,6 +68,7 @@ export class ItemStatusComponent implements OnInit {
|
|||||||
The value is supposed to be a href for the button
|
The value is supposed to be a href for the button
|
||||||
*/
|
*/
|
||||||
this.operations = [];
|
this.operations = [];
|
||||||
|
this.operations.push(new ItemOperation('authorizations', this.getCurrentUrl(item) + '/authorizations'));
|
||||||
this.operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl(item) + '/mapper'));
|
this.operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl(item) + '/mapper'));
|
||||||
if (item.isWithdrawn) {
|
if (item.isWithdrawn) {
|
||||||
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl(item) + '/reinstate'));
|
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl(item) + '/reinstate'));
|
||||||
|
@@ -7,6 +7,7 @@ import { Item } from '../core/shared/item.model';
|
|||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { find } from 'rxjs/operators';
|
import { find } from 'rxjs/operators';
|
||||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||||
|
import { FindListOptions } from '../core/data/request.models';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific item before the route is activated
|
* This class represents a resolver that requests a specific item before the route is activated
|
||||||
@@ -26,7 +27,7 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
|||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||||
return this.itemService.findById(route.params.id,
|
return this.itemService.findById(route.params.id,
|
||||||
followLink('owningCollection'),
|
followLink('owningCollection'),
|
||||||
followLink('bundles'),
|
followLink('bundles', new FindListOptions(), true, followLink('bitstreams')),
|
||||||
followLink('relationships'),
|
followLink('relationships'),
|
||||||
followLink('version', undefined, true, followLink('versionhistory')),
|
followLink('version', undefined, true, followLink('versionhistory')),
|
||||||
).pipe(
|
).pipe(
|
||||||
|
@@ -33,7 +33,7 @@ export function getBitstreamModulePath() {
|
|||||||
return `/${BITSTREAM_MODULE_PATH}`;
|
return `/${BITSTREAM_MODULE_PATH}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ADMIN_MODULE_PATH = 'admin';
|
export const ADMIN_MODULE_PATH = 'admin';
|
||||||
|
|
||||||
export function getAdminModulePath() {
|
export function getAdminModulePath() {
|
||||||
return `/${ADMIN_MODULE_PATH}`;
|
return `/${ADMIN_MODULE_PATH}`;
|
||||||
|
@@ -28,7 +28,8 @@ export class DSONameService {
|
|||||||
return dso.firstMetadataValue('organization.legalName');
|
return dso.firstMetadataValue('organization.legalName');
|
||||||
},
|
},
|
||||||
Default: (dso: DSpaceObject): string => {
|
Default: (dso: DSpaceObject): string => {
|
||||||
return dso.firstMetadataValue('dc.title');
|
// If object doesn't have dc.title metadata use name property
|
||||||
|
return dso.firstMetadataValue('dc.title') || dso.name;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
/**
|
/**
|
||||||
* Class representing a query parameter (query?fieldName=fieldValue) used in FindListOptions object
|
* Class representing a query parameter (query?fieldName=fieldValue) used in FindListOptions object
|
||||||
*/
|
*/
|
||||||
export class SearchParam {
|
export class RequestParam {
|
||||||
constructor(public fieldName: string, public fieldValue: any) {
|
constructor(public fieldName: string, public fieldValue: any) {
|
||||||
|
|
||||||
}
|
}
|
@@ -78,7 +78,7 @@ import { RegistryMetadatafieldsResponseParsingService } from './data/registry-me
|
|||||||
import { RegistryMetadataschemasResponseParsingService } from './data/registry-metadataschemas-response-parsing.service';
|
import { RegistryMetadataschemasResponseParsingService } from './data/registry-metadataschemas-response-parsing.service';
|
||||||
import { RelationshipTypeService } from './data/relationship-type.service';
|
import { RelationshipTypeService } from './data/relationship-type.service';
|
||||||
import { RelationshipService } from './data/relationship.service';
|
import { RelationshipService } from './data/relationship.service';
|
||||||
import { ResourcePolicyService } from './data/resource-policy.service';
|
import { ResourcePolicyService } from './resource-policy/resource-policy.service';
|
||||||
import { SearchResponseParsingService } from './data/search-response-parsing.service';
|
import { SearchResponseParsingService } from './data/search-response-parsing.service';
|
||||||
import { SiteDataService } from './data/site-data.service';
|
import { SiteDataService } from './data/site-data.service';
|
||||||
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
||||||
@@ -116,7 +116,7 @@ import { RelationshipType } from './shared/item-relationships/relationship-type.
|
|||||||
import { Relationship } from './shared/item-relationships/relationship.model';
|
import { Relationship } from './shared/item-relationships/relationship.model';
|
||||||
import { Item } from './shared/item.model';
|
import { Item } from './shared/item.model';
|
||||||
import { License } from './shared/license.model';
|
import { License } from './shared/license.model';
|
||||||
import { ResourcePolicy } from './shared/resource-policy.model';
|
import { ResourcePolicy } from './resource-policy/models/resource-policy.model';
|
||||||
import { SearchConfigurationService } from './shared/search/search-configuration.service';
|
import { SearchConfigurationService } from './shared/search/search-configuration.service';
|
||||||
import { SearchFilterService } from './shared/search/search-filter.service';
|
import { SearchFilterService } from './shared/search/search-filter.service';
|
||||||
import { SearchService } from './shared/search/search.service';
|
import { SearchService } from './shared/search/search.service';
|
||||||
|
@@ -12,7 +12,7 @@ import { PaginatedSearchOptions } from '../../shared/search/paginated-search-opt
|
|||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { ContentSourceSuccessResponse, RestResponse } from '../cache/response.models';
|
import { ContentSourceSuccessResponse, RestResponse } from '../cache/response.models';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
@@ -94,7 +94,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
|||||||
getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||||
const searchHref = 'findAuthorizedByCommunity';
|
const searchHref = 'findAuthorizedByCommunity';
|
||||||
options = Object.assign({}, options, {
|
options = Object.assign({}, options, {
|
||||||
searchParams: [new SearchParam('uuid', communityId)]
|
searchParams: [new RequestParam('uuid', communityId)]
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.searchBy(searchHref, options).pipe(
|
return this.searchBy(searchHref, options).pipe(
|
||||||
|
@@ -20,7 +20,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { getClassForType } from '../cache/builders/build-decorators';
|
import { getClassForType } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { ErrorResponse, RestResponse } from '../cache/response.models';
|
import { ErrorResponse, RestResponse } from '../cache/response.models';
|
||||||
@@ -111,7 +111,7 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
result$ = this.getSearchEndpoint(searchMethod);
|
result$ = this.getSearchEndpoint(searchMethod);
|
||||||
|
|
||||||
if (hasValue(options.searchParams)) {
|
if (hasValue(options.searchParams)) {
|
||||||
options.searchParams.forEach((param: SearchParam) => {
|
options.searchParams.forEach((param: RequestParam) => {
|
||||||
args.push(`${param.fieldName}=${param.fieldValue}`);
|
args.push(`${param.fieldName}=${param.fieldValue}`);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -153,6 +153,33 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn an array of RequestParam into a query string and combine it with the given HREF
|
||||||
|
*
|
||||||
|
* @param href The HREF to which the query string should be appended
|
||||||
|
* @param params Array with additional params to combine with query string
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*
|
||||||
|
* @return {Observable<string>}
|
||||||
|
* Return an observable that emits created HREF
|
||||||
|
*/
|
||||||
|
protected buildHrefWithParams(href: string, params: RequestParam[], ...linksToFollow: Array<FollowLinkConfig<T>>): string {
|
||||||
|
|
||||||
|
let args = [];
|
||||||
|
if (hasValue(params)) {
|
||||||
|
params.forEach((param: RequestParam) => {
|
||||||
|
args.push(`${param.fieldName}=${param.fieldValue}`);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
args = this.addEmbedParams(args, ...linksToFollow);
|
||||||
|
|
||||||
|
if (isNotEmpty(args)) {
|
||||||
|
return new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||||
|
} else {
|
||||||
|
return href;
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Adds the embed options to the link for the request
|
* Adds the embed options to the link for the request
|
||||||
* @param args params for the query string
|
* @param args params for the query string
|
||||||
@@ -293,9 +320,9 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
* @param searchMethod The search method for the object
|
* @param searchMethod The search method for the object
|
||||||
*/
|
*/
|
||||||
protected getSearchEndpoint(searchMethod: string): Observable<string> {
|
protected getSearchEndpoint(searchMethod: string): Observable<string> {
|
||||||
return this.halService.getEndpoint(`${this.linkPath}/search`).pipe(
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
filter((href: string) => isNotEmpty(href)),
|
filter((href: string) => isNotEmpty(href)),
|
||||||
map((href: string) => `${href}/${searchMethod}`));
|
map((href: string) => `${href}/search/${searchMethod}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -380,15 +407,15 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
*
|
*
|
||||||
* @param {DSpaceObject} dso
|
* @param {DSpaceObject} dso
|
||||||
* The object to create
|
* The object to create
|
||||||
* @param {string} parentUUID
|
* @param {RequestParam[]} params
|
||||||
* The UUID of the parent to create the new object under
|
* Array with additional params to combine with query string
|
||||||
*/
|
*/
|
||||||
create(dso: T, parentUUID: string): Observable<RemoteData<T>> {
|
create(dso: T, ...params: RequestParam[]): Observable<RemoteData<T>> {
|
||||||
const requestId = this.requestService.generateRequestId();
|
const requestId = this.requestService.generateRequestId();
|
||||||
const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe(
|
const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
isNotEmptyOperator(),
|
isNotEmptyOperator(),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
map((endpoint: string) => parentUUID ? `${endpoint}?parent=${parentUUID}` : endpoint)
|
map((endpoint: string) => this.buildHrefWithParams(endpoint, params))
|
||||||
);
|
);
|
||||||
|
|
||||||
const serializedDso = new DSpaceSerializer(getClassForType((dso as any).type)).serialize(dso);
|
const serializedDso = new DSpaceSerializer(getClassForType((dso as any).type)).serialize(dso);
|
||||||
@@ -479,7 +506,7 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
const requestId = this.deleteAndReturnRequestId(dsoID, copyVirtualMetadata);
|
const requestId = this.deleteAndReturnRequestId(dsoID, copyVirtualMetadata);
|
||||||
|
|
||||||
return this.requestService.getByUUID(requestId).pipe(
|
return this.requestService.getByUUID(requestId).pipe(
|
||||||
find((request: RequestEntry) => request.completed),
|
find((request: RequestEntry) => isNotEmpty(request) && request.completed),
|
||||||
map((request: RequestEntry) => request.response.isSuccessful)
|
map((request: RequestEntry) => request.response.isSuccessful)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -21,7 +21,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
@@ -257,7 +257,7 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
if (options) {
|
if (options) {
|
||||||
findListOptions = Object.assign(new FindListOptions(), options);
|
findListOptions = Object.assign(new FindListOptions(), options);
|
||||||
}
|
}
|
||||||
const searchParams = [new SearchParam('label', label), new SearchParam('dso', item.id)];
|
const searchParams = [new RequestParam('label', label), new RequestParam('dso', item.id)];
|
||||||
if (findListOptions.searchParams) {
|
if (findListOptions.searchParams) {
|
||||||
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
|
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
|
||||||
} else {
|
} else {
|
||||||
|
@@ -11,7 +11,7 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
|||||||
import { SubmissionResponseParsingService } from '../submission/submission-response-parsing.service';
|
import { SubmissionResponseParsingService } from '../submission/submission-response-parsing.service';
|
||||||
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
|
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
|
||||||
import { RestRequestMethod } from './rest-request-method';
|
import { RestRequestMethod } from './rest-request-method';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { EpersonResponseParsingService } from '../eperson/eperson-response-parsing.service';
|
import { EpersonResponseParsingService } from '../eperson/eperson-response-parsing.service';
|
||||||
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
|
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
|
||||||
import { MetadataschemaParsingService } from './metadataschema-parsing.service';
|
import { MetadataschemaParsingService } from './metadataschema-parsing.service';
|
||||||
@@ -146,7 +146,7 @@ export class FindListOptions {
|
|||||||
elementsPerPage?: number;
|
elementsPerPage?: number;
|
||||||
currentPage?: number;
|
currentPage?: number;
|
||||||
sort?: SortOptions;
|
sort?: SortOptions;
|
||||||
searchParams?: SearchParam[];
|
searchParams?: RequestParam[];
|
||||||
startsWith?: string;
|
startsWith?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,75 +0,0 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|
||||||
import { ResourcePolicy } from '../shared/resource-policy.model';
|
|
||||||
import { RequestService } from './request.service';
|
|
||||||
import { ResourcePolicyService } from './resource-policy.service';
|
|
||||||
|
|
||||||
describe('ResourcePolicyService', () => {
|
|
||||||
let scheduler: TestScheduler;
|
|
||||||
let service: ResourcePolicyService;
|
|
||||||
let requestService: RequestService;
|
|
||||||
let rdbService: RemoteDataBuildService;
|
|
||||||
let objectCache: ObjectCacheService;
|
|
||||||
const testObject = {
|
|
||||||
uuid: '664184ee-b254-45e8-970d-220e5ccc060b'
|
|
||||||
} as ResourcePolicy;
|
|
||||||
const requestURL = `https://rest.api/rest/api/resourcepolicies/${testObject.uuid}`;
|
|
||||||
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
scheduler = getTestScheduler();
|
|
||||||
|
|
||||||
requestService = jasmine.createSpyObj('requestService', {
|
|
||||||
generateRequestId: requestUUID,
|
|
||||||
configure: true
|
|
||||||
});
|
|
||||||
rdbService = jasmine.createSpyObj('rdbService', {
|
|
||||||
buildSingle: cold('a', {
|
|
||||||
a: {
|
|
||||||
payload: testObject
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
objectCache = {} as ObjectCacheService;
|
|
||||||
const halService = {} as HALEndpointService;
|
|
||||||
const notificationsService = {} as NotificationsService;
|
|
||||||
const http = {} as HttpClient;
|
|
||||||
const comparator = {} as any;
|
|
||||||
|
|
||||||
service = new ResourcePolicyService(
|
|
||||||
requestService,
|
|
||||||
rdbService,
|
|
||||||
objectCache,
|
|
||||||
halService,
|
|
||||||
notificationsService,
|
|
||||||
http,
|
|
||||||
comparator
|
|
||||||
);
|
|
||||||
|
|
||||||
spyOn((service as any).dataService, 'findByHref').and.callThrough();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findByHref', () => {
|
|
||||||
it('should proxy the call to dataservice.findByHref', () => {
|
|
||||||
scheduler.schedule(() => service.findByHref(requestURL));
|
|
||||||
scheduler.flush();
|
|
||||||
|
|
||||||
expect((service as any).dataService.findByHref).toHaveBeenCalledWith(requestURL);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a RemoteData<ResourcePolicy> for the object with the given URL', () => {
|
|
||||||
const result = service.findByHref(requestURL);
|
|
||||||
const expected = cold('a', {
|
|
||||||
a: {
|
|
||||||
payload: testObject
|
|
||||||
}
|
|
||||||
});
|
|
||||||
expect(result).toBeObservable(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,95 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
|
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
|
||||||
|
|
||||||
import { DataService } from '../data/data.service';
|
|
||||||
import { RequestService } from '../data/request.service';
|
|
||||||
import { FindListOptions } from '../data/request.models';
|
|
||||||
import { Collection } from '../shared/collection.model';
|
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|
||||||
import { ResourcePolicy } from '../shared/resource-policy.model';
|
|
||||||
import { RemoteData } from '../data/remote-data';
|
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
|
||||||
import { RESOURCE_POLICY } from '../shared/resource-policy.resource-type';
|
|
||||||
import { ChangeAnalyzer } from './change-analyzer';
|
|
||||||
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
|
|
||||||
import { PaginatedList } from './paginated-list';
|
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A private DataService implementation to delegate specific methods to.
|
|
||||||
*/
|
|
||||||
class DataServiceImpl extends DataService<ResourcePolicy> {
|
|
||||||
protected linkPath = 'resourcepolicies';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected requestService: RequestService,
|
|
||||||
protected rdbService: RemoteDataBuildService,
|
|
||||||
protected store: Store<CoreState>,
|
|
||||||
protected objectCache: ObjectCacheService,
|
|
||||||
protected halService: HALEndpointService,
|
|
||||||
protected notificationsService: NotificationsService,
|
|
||||||
protected http: HttpClient,
|
|
||||||
protected comparator: ChangeAnalyzer<ResourcePolicy>) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A service responsible for fetching/sending data from/to the REST API on the resourcepolicies endpoint
|
|
||||||
*/
|
|
||||||
@Injectable()
|
|
||||||
@dataService(RESOURCE_POLICY)
|
|
||||||
export class ResourcePolicyService {
|
|
||||||
private dataService: DataServiceImpl;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected requestService: RequestService,
|
|
||||||
protected rdbService: RemoteDataBuildService,
|
|
||||||
protected objectCache: ObjectCacheService,
|
|
||||||
protected halService: HALEndpointService,
|
|
||||||
protected notificationsService: NotificationsService,
|
|
||||||
protected http: HttpClient,
|
|
||||||
protected comparator: DefaultChangeAnalyzer<ResourcePolicy>) {
|
|
||||||
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an observable of {@link RemoteData} of a {@link ResourcePolicy}, based on an href, with a list of {@link FollowLinkConfig},
|
|
||||||
* to automatically resolve {@link HALLink}s of the {@link ResourcePolicy}
|
|
||||||
* @param href The url of {@link ResourcePolicy} we want to retrieve
|
|
||||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
|
||||||
*/
|
|
||||||
findByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<ResourcePolicy>> {
|
|
||||||
return this.dataService.findByHref(href, ...linksToFollow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of observables of {@link RemoteData} of {@link ResourcePolicy}s, based on an href, with a list of {@link FollowLinkConfig},
|
|
||||||
* to automatically resolve {@link HALLink}s of the {@link ResourcePolicy}
|
|
||||||
* @param href The url of the {@link ResourcePolicy} we want to retrieve
|
|
||||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
|
||||||
*/
|
|
||||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
|
||||||
return this.dataService.findAllByHref(href, findListOptions, ...linksToFollow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the defaultAccessConditions {@link ResourcePolicy} list for a given {@link Collection}
|
|
||||||
*
|
|
||||||
* @param collection the {@link Collection} to retrieve the defaultAccessConditions for
|
|
||||||
* @param findListOptions the {@link FindListOptions} for the request
|
|
||||||
*/
|
|
||||||
getDefaultAccessConditionsFor(collection: Collection, findListOptions?: FindListOptions): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
|
||||||
return this.dataService.findAllByHref(collection._links.defaultAccessConditions.href, findListOptions);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -9,7 +9,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
|||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { EPeopleRegistryCancelEPersonAction, EPeopleRegistryEditEPersonAction } from '../../+admin/admin-access-control/epeople-registry/epeople-registry.actions';
|
import { EPeopleRegistryCancelEPersonAction, EPeopleRegistryEditEPersonAction } from '../../+admin/admin-access-control/epeople-registry/epeople-registry.actions';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { ChangeAnalyzer } from '../data/change-analyzer';
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
@@ -105,7 +105,7 @@ describe('EPersonDataService', () => {
|
|||||||
it('search by default scope (byMetadata) and no query', () => {
|
it('search by default scope (byMetadata) and no query', () => {
|
||||||
service.searchByScope(null, '');
|
service.searchByScope(null, '');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', ''))]
|
searchParams: [Object.assign(new RequestParam('query', ''))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
@@ -113,7 +113,7 @@ describe('EPersonDataService', () => {
|
|||||||
it('search metadata scope and no query', () => {
|
it('search metadata scope and no query', () => {
|
||||||
service.searchByScope('metadata', '');
|
service.searchByScope('metadata', '');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', ''))]
|
searchParams: [Object.assign(new RequestParam('query', ''))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
@@ -121,7 +121,7 @@ describe('EPersonDataService', () => {
|
|||||||
it('search metadata scope and with query', () => {
|
it('search metadata scope and with query', () => {
|
||||||
service.searchByScope('metadata', 'test');
|
service.searchByScope('metadata', 'test');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', 'test'))]
|
searchParams: [Object.assign(new RequestParam('query', 'test'))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
@@ -129,7 +129,7 @@ describe('EPersonDataService', () => {
|
|||||||
it('search email scope and no query', () => {
|
it('search email scope and no query', () => {
|
||||||
service.searchByScope('email', '');
|
service.searchByScope('email', '');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('email', ''))]
|
searchParams: [Object.assign(new RequestParam('email', ''))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byEmail', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byEmail', options);
|
||||||
});
|
});
|
||||||
|
@@ -15,7 +15,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
@@ -97,7 +97,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
* @param linksToFollow
|
* @param linksToFollow
|
||||||
*/
|
*/
|
||||||
private getEpeopleByEmail(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
private getEpeopleByEmail(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
const searchParams = [new SearchParam('email', query)];
|
const searchParams = [new RequestParam('email', query)];
|
||||||
return this.getEPeopleBy(searchParams, this.searchByEmailPath, options, ...linksToFollow);
|
return this.getEPeopleBy(searchParams, this.searchByEmailPath, options, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
* @param linksToFollow
|
* @param linksToFollow
|
||||||
*/
|
*/
|
||||||
private getEpeopleByMetadata(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
private getEpeopleByMetadata(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
const searchParams = [new SearchParam('query', query)];
|
const searchParams = [new RequestParam('query', query)];
|
||||||
return this.getEPeopleBy(searchParams, this.searchByMetadataPath, options, ...linksToFollow);
|
return this.getEPeopleBy(searchParams, this.searchByMetadataPath, options, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
|||||||
* @param options
|
* @param options
|
||||||
* @param linksToFollow
|
* @param linksToFollow
|
||||||
*/
|
*/
|
||||||
private getEPeopleBy(searchParams: SearchParam[], searchMethod: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
private getEPeopleBy(searchParams: RequestParam[], searchMethod: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<EPerson>>): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
let findListOptions = new FindListOptions();
|
let findListOptions = new FindListOptions();
|
||||||
if (options) {
|
if (options) {
|
||||||
findListOptions = Object.assign(new FindListOptions(), options);
|
findListOptions = Object.assign(new FindListOptions(), options);
|
||||||
|
@@ -11,7 +11,7 @@ import {
|
|||||||
GroupRegistryEditGroupAction
|
GroupRegistryEditGroupAction
|
||||||
} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
|
} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
|
||||||
import { GroupMock, GroupMock2 } from '../../shared/testing/group-mock';
|
import { GroupMock, GroupMock2 } from '../../shared/testing/group-mock';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { ChangeAnalyzer } from '../data/change-analyzer';
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
import { PaginatedList } from '../data/paginated-list';
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
@@ -103,7 +103,7 @@ describe('GroupDataService', () => {
|
|||||||
it('search with empty query', () => {
|
it('search with empty query', () => {
|
||||||
service.searchGroups('');
|
service.searchGroups('');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', ''))]
|
searchParams: [Object.assign(new RequestParam('query', ''))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
@@ -111,7 +111,7 @@ describe('GroupDataService', () => {
|
|||||||
it('search with query', () => {
|
it('search with query', () => {
|
||||||
service.searchGroups('test');
|
service.searchGroups('test');
|
||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
searchParams: [Object.assign(new SearchParam('query', 'test'))]
|
searchParams: [Object.assign(new RequestParam('query', 'test'))]
|
||||||
});
|
});
|
||||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||||
});
|
});
|
||||||
|
@@ -14,7 +14,7 @@ import { hasValue } from '../../shared/empty.util';
|
|||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { RestResponse } from '../cache/response.models';
|
import { RestResponse } from '../cache/response.models';
|
||||||
import { DataService } from '../data/data.service';
|
import { DataService } from '../data/data.service';
|
||||||
@@ -97,7 +97,7 @@ export class GroupDataService extends DataService<Group> {
|
|||||||
* @param linksToFollow
|
* @param linksToFollow
|
||||||
*/
|
*/
|
||||||
public searchGroups(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Group>>): Observable<RemoteData<PaginatedList<Group>>> {
|
public searchGroups(query: string, options?: FindListOptions, ...linksToFollow: Array<FollowLinkConfig<Group>>): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
const searchParams = [new SearchParam('query', query)];
|
const searchParams = [new RequestParam('query', query)];
|
||||||
let findListOptions = new FindListOptions();
|
let findListOptions = new FindListOptions();
|
||||||
if (options) {
|
if (options) {
|
||||||
findListOptions = Object.assign(new FindListOptions(), options);
|
findListOptions = Object.assign(new FindListOptions(), options);
|
||||||
@@ -121,7 +121,7 @@ export class GroupDataService extends DataService<Group> {
|
|||||||
isMemberOf(groupName: string): Observable<boolean> {
|
isMemberOf(groupName: string): Observable<boolean> {
|
||||||
const searchHref = 'isMemberOf';
|
const searchHref = 'isMemberOf';
|
||||||
const options = new FindListOptions();
|
const options = new FindListOptions();
|
||||||
options.searchParams = [new SearchParam('groupName', groupName)];
|
options.searchParams = [new RequestParam('groupName', groupName)];
|
||||||
|
|
||||||
return this.searchBy(searchHref, options).pipe(
|
return this.searchBy(searchHref, options).pipe(
|
||||||
filter((groups: RemoteData<PaginatedList<Group>>) => !groups.isResponsePending),
|
filter((groups: RemoteData<PaginatedList<Group>>) => !groups.isResponsePending),
|
||||||
|
@@ -5,27 +5,27 @@ export enum ActionType {
|
|||||||
/**
|
/**
|
||||||
* Action of reading, viewing or downloading something
|
* Action of reading, viewing or downloading something
|
||||||
*/
|
*/
|
||||||
READ = 0,
|
READ = 'READ',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of modifying something
|
* Action of modifying something
|
||||||
*/
|
*/
|
||||||
WRITE = 1,
|
WRITE = 'WRITE',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of deleting something
|
* Action of deleting something
|
||||||
*/
|
*/
|
||||||
DELETE = 2,
|
DELETE = 'DELETE',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of adding something to a container
|
* Action of adding something to a container
|
||||||
*/
|
*/
|
||||||
ADD = 3,
|
ADD = 'ADD',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of removing something from a container
|
* Action of removing something from a container
|
||||||
*/
|
*/
|
||||||
REMOVE = 4,
|
REMOVE = 'REMOVE',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action of performing workflow step 1
|
* Action of performing workflow step 1
|
||||||
@@ -50,15 +50,20 @@ export enum ActionType {
|
|||||||
/**
|
/**
|
||||||
* Default Read policies for Bitstreams submitted to container
|
* Default Read policies for Bitstreams submitted to container
|
||||||
*/
|
*/
|
||||||
DEFAULT_BITSTREAM_READ = 9,
|
DEFAULT_BITSTREAM_READ = 'DEFAULT_BITSTREAM_READ',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default Read policies for Items submitted to container
|
* Default Read policies for Items submitted to container
|
||||||
*/
|
*/
|
||||||
DEFAULT_ITEM_READ = 10,
|
DEFAULT_ITEM_READ = 'DEFAULT_ITEM_READ',
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Administrative actions
|
* Administrative actions
|
||||||
*/
|
*/
|
||||||
ADMIN = 11,
|
ADMIN = 'ADMIN',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action of withdrawn reading
|
||||||
|
*/
|
||||||
|
WITHDRAWN_READ = 'WITHDRAWN_READ'
|
||||||
}
|
}
|
25
src/app/core/resource-policy/models/policy-type.model.ts
Normal file
25
src/app/core/resource-policy/models/policy-type.model.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Enum representing the Policy Type of a Resource Policy
|
||||||
|
*/
|
||||||
|
export enum PolicyType {
|
||||||
|
/**
|
||||||
|
* A policy in place during the submission
|
||||||
|
*/
|
||||||
|
TYPE_SUBMISSION = 'TYPE_SUBMISSION',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A policy in place during the approval workflow
|
||||||
|
*/
|
||||||
|
TYPE_WORKFLOW = 'TYPE_WORKFLOW',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A policy that has been inherited from a container (the collection)
|
||||||
|
*/
|
||||||
|
TYPE_INHERITED = 'TYPE_INHERITED',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A policy defined by the user during the submission or workflow phase
|
||||||
|
*/
|
||||||
|
TYPE_CUSTOM = 'TYPE_CUSTOM',
|
||||||
|
|
||||||
|
}
|
105
src/app/core/resource-policy/models/resource-policy.model.ts
Normal file
105
src/app/core/resource-policy/models/resource-policy.model.ts
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { autoserialize, deserialize, deserializeAs } from 'cerialize';
|
||||||
|
import { link, typedObject } from '../../cache/builders/build-decorators';
|
||||||
|
import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer';
|
||||||
|
import { ActionType } from './action-type.model';
|
||||||
|
import { CacheableObject } from '../../cache/object-cache.reducer';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { RESOURCE_POLICY } from './resource-policy.resource-type';
|
||||||
|
import { excludeFromEquals } from '../../utilities/equals.decorators';
|
||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
import { PolicyType } from './policy-type.model';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RemoteData } from '../../data/remote-data';
|
||||||
|
import { GROUP } from '../../eperson/models/group.resource-type';
|
||||||
|
import { Group } from '../../eperson/models/group.model';
|
||||||
|
import { EPERSON } from '../../eperson/models/eperson.resource-type';
|
||||||
|
import { EPerson } from '../../eperson/models/eperson.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model class for a Resource Policy
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
export class ResourcePolicy implements CacheableObject {
|
||||||
|
static type = RESOURCE_POLICY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The identifier for this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name for this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The description for this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The classification or this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
policyType: PolicyType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The action that is allowed by this Resource Policy
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
action: ActionType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first day of validity of the policy (format YYYY-MM-DD)
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
startDate: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last day of validity of the policy (format YYYY-MM-DD)
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
endDate: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object type
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
@autoserialize
|
||||||
|
type: ResourceType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The universally unique identifier for this Resource Policy
|
||||||
|
* This UUID is generated client-side and isn't used by the backend.
|
||||||
|
* It is based on the ID, so it will be the same for each refresh.
|
||||||
|
*/
|
||||||
|
@deserializeAs(new IDToUUIDSerializer('resource-policy'), 'id')
|
||||||
|
uuid: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HALLink}s for this ResourcePolicy
|
||||||
|
*/
|
||||||
|
@deserialize
|
||||||
|
_links: {
|
||||||
|
eperson: HALLink,
|
||||||
|
group: HALLink,
|
||||||
|
self: HALLink,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The eperson linked by this resource policy
|
||||||
|
* Will be undefined unless the version {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(EPERSON)
|
||||||
|
eperson?: Observable<RemoteData<EPerson>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The group linked by this resource policy
|
||||||
|
* Will be undefined unless the version {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(GROUP)
|
||||||
|
group?: Observable<RemoteData<Group>>;
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
import { ResourceType } from './resource-type';
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The resource type for ResourcePolicy
|
* The resource type for ResourcePolicy
|
||||||
@@ -6,4 +6,4 @@ import { ResourceType } from './resource-type';
|
|||||||
* Needs to be in a separate file to prevent circular
|
* Needs to be in a separate file to prevent circular
|
||||||
* dependencies in webpack.
|
* dependencies in webpack.
|
||||||
*/
|
*/
|
||||||
export const RESOURCE_POLICY = new ResourceType('resourcePolicy');
|
export const RESOURCE_POLICY = new ResourceType('resourcepolicy');
|
319
src/app/core/resource-policy/resource-policy.service.spec.ts
Normal file
319
src/app/core/resource-policy/resource-policy.service.spec.ts
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { ResourcePolicyService } from './resource-policy.service';
|
||||||
|
import { PolicyType } from './models/policy-type.model';
|
||||||
|
import { ActionType } from './models/action-type.model';
|
||||||
|
import { FindListOptions } from '../data/request.models';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
|
import { RequestEntry } from '../data/request.reducer';
|
||||||
|
import { RestResponse } from '../cache/response.models';
|
||||||
|
|
||||||
|
describe('ResourcePolicyService', () => {
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let service: ResourcePolicyService;
|
||||||
|
let requestService: RequestService;
|
||||||
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let objectCache: ObjectCacheService;
|
||||||
|
let halService: HALEndpointService;
|
||||||
|
let responseCacheEntry: RequestEntry;
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const anotherResourcePolicy: any = {
|
||||||
|
id: '2',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.WRITE,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-2',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const endpointURL = `https://rest.api/rest/api/resourcepolicies`;
|
||||||
|
const requestURL = `https://rest.api/rest/api/resourcepolicies/${resourcePolicy.id}`;
|
||||||
|
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||||
|
const resourcePolicyId = '1';
|
||||||
|
const epersonUUID = '8b39g7ya-5a4b-438b-9686-be1d5b4a1c5a';
|
||||||
|
const groupUUID = '8b39g7ya-5a4b-36987-9686-be1d5b4a1c5a';
|
||||||
|
const resourceUUID = '8b39g7ya-5a4b-438b-851f-be1d5b4a1c5a';
|
||||||
|
|
||||||
|
const pageInfo = new PageInfo();
|
||||||
|
const array = [resourcePolicy, anotherResourcePolicy];
|
||||||
|
const paginatedList = new PaginatedList(pageInfo, array);
|
||||||
|
const resourcePolicyRD = createSuccessfulRemoteDataObject(resourcePolicy);
|
||||||
|
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
|
||||||
|
halService = jasmine.createSpyObj('halService', {
|
||||||
|
getEndpoint: cold('a', { a: endpointURL })
|
||||||
|
});
|
||||||
|
|
||||||
|
responseCacheEntry = new RequestEntry();
|
||||||
|
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||||
|
|
||||||
|
requestService = jasmine.createSpyObj('requestService', {
|
||||||
|
generateRequestId: requestUUID,
|
||||||
|
configure: true,
|
||||||
|
removeByHrefSubstring: {},
|
||||||
|
getByHref: observableOf(responseCacheEntry),
|
||||||
|
getByUUID: observableOf(responseCacheEntry),
|
||||||
|
});
|
||||||
|
rdbService = jasmine.createSpyObj('rdbService', {
|
||||||
|
buildSingle: hot('a|', {
|
||||||
|
a: resourcePolicyRD
|
||||||
|
}),
|
||||||
|
buildList: hot('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
objectCache = {} as ObjectCacheService;
|
||||||
|
const notificationsService = {} as NotificationsService;
|
||||||
|
const http = {} as HttpClient;
|
||||||
|
const comparator = {} as any;
|
||||||
|
|
||||||
|
service = new ResourcePolicyService(
|
||||||
|
requestService,
|
||||||
|
rdbService,
|
||||||
|
objectCache,
|
||||||
|
halService,
|
||||||
|
notificationsService,
|
||||||
|
http,
|
||||||
|
comparator
|
||||||
|
);
|
||||||
|
|
||||||
|
spyOn((service as any).dataService, 'create').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'delete').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'update').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'findById').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'findByHref').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'searchBy').and.callThrough();
|
||||||
|
spyOn((service as any).dataService, 'getSearchByHref').and.returnValue(observableOf(requestURL));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should proxy the call to dataservice.create with eperson UUID', () => {
|
||||||
|
scheduler.schedule(() => service.create(resourcePolicy, resourceUUID, epersonUUID));
|
||||||
|
const params = [
|
||||||
|
new RequestParam('resource', resourceUUID),
|
||||||
|
new RequestParam('eperson', epersonUUID)
|
||||||
|
];
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.create).toHaveBeenCalledWith(resourcePolicy, ...params);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should proxy the call to dataservice.create with group UUID', () => {
|
||||||
|
scheduler.schedule(() => service.create(resourcePolicy, resourceUUID, null, groupUUID));
|
||||||
|
const params = [
|
||||||
|
new RequestParam('resource', resourceUUID),
|
||||||
|
new RequestParam('group', groupUUID)
|
||||||
|
];
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.create).toHaveBeenCalledWith(resourcePolicy, ...params);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<ResourcePolicy> for the object with the given id', () => {
|
||||||
|
const result = service.create(resourcePolicy, resourceUUID, epersonUUID);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: resourcePolicyRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
it('should proxy the call to dataservice.create', () => {
|
||||||
|
scheduler.schedule(() => service.delete(resourcePolicyId));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.delete).toHaveBeenCalledWith(resourcePolicyId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should proxy the call to dataservice.update', () => {
|
||||||
|
scheduler.schedule(() => service.update(resourcePolicy));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.update).toHaveBeenCalledWith(resourcePolicy);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findById', () => {
|
||||||
|
it('should proxy the call to dataservice.findById', () => {
|
||||||
|
scheduler.schedule(() => service.findById(resourcePolicyId));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.findById).toHaveBeenCalledWith(resourcePolicyId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<ResourcePolicy> for the object with the given id', () => {
|
||||||
|
const result = service.findById(resourcePolicyId);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: resourcePolicyRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findByHref', () => {
|
||||||
|
it('should proxy the call to dataservice.findByHref', () => {
|
||||||
|
scheduler.schedule(() => service.findByHref(requestURL));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.findByHref).toHaveBeenCalledWith(requestURL);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<ResourcePolicy> for the object with the given URL', () => {
|
||||||
|
const result = service.findByHref(requestURL);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: resourcePolicyRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchByEPerson', () => {
|
||||||
|
it('should proxy the call to dataservice.searchBy', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', epersonUUID)];
|
||||||
|
scheduler.schedule(() => service.searchByEPerson(epersonUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByEPersonMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should proxy the call to dataservice.searchBy with additional search param', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [
|
||||||
|
new RequestParam('uuid', epersonUUID),
|
||||||
|
new RequestParam('resource', resourceUUID),
|
||||||
|
];
|
||||||
|
scheduler.schedule(() => service.searchByEPerson(epersonUUID, resourceUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByEPersonMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<PaginatedList<ResourcePolicy>) for the search', () => {
|
||||||
|
const result = service.searchByEPerson(epersonUUID, resourceUUID);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchByGroup', () => {
|
||||||
|
it('should proxy the call to dataservice.searchBy', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', groupUUID)];
|
||||||
|
scheduler.schedule(() => service.searchByGroup(groupUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByGroupMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should proxy the call to dataservice.searchBy with additional search param', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [
|
||||||
|
new RequestParam('uuid', groupUUID),
|
||||||
|
new RequestParam('resource', resourceUUID),
|
||||||
|
];
|
||||||
|
scheduler.schedule(() => service.searchByGroup(groupUUID, resourceUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByGroupMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<PaginatedList<ResourcePolicy>) for the search', () => {
|
||||||
|
const result = service.searchByGroup(groupUUID);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('searchByResource', () => {
|
||||||
|
it('should proxy the call to dataservice.searchBy', () => {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', resourceUUID)];
|
||||||
|
scheduler.schedule(() => service.searchByResource(resourceUUID));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByResourceMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should proxy the call to dataservice.searchBy with additional search param', () => {
|
||||||
|
const action = ActionType.READ;
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [
|
||||||
|
new RequestParam('uuid', resourceUUID),
|
||||||
|
new RequestParam('action', action),
|
||||||
|
];
|
||||||
|
scheduler.schedule(() => service.searchByResource(resourceUUID, action));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect((service as any).dataService.searchBy).toHaveBeenCalledWith((service as any).searchByResourceMethod, options);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return a RemoteData<PaginatedList<ResourcePolicy>) for the search', () => {
|
||||||
|
const result = service.searchByResource(resourceUUID);
|
||||||
|
const expected = cold('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
});
|
||||||
|
expect(result).toBeObservable(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
193
src/app/core/resource-policy/resource-policy.service.ts
Normal file
193
src/app/core/resource-policy/resource-policy.service.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
|
|
||||||
|
import { DataService } from '../data/data.service';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { FindListOptions } from '../data/request.models';
|
||||||
|
import { Collection } from '../shared/collection.model';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { ResourcePolicy } from './models/resource-policy.model';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { RESOURCE_POLICY } from './models/resource-policy.resource-type';
|
||||||
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
|
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { ActionType } from './models/action-type.model';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A private DataService implementation to delegate specific methods to.
|
||||||
|
*/
|
||||||
|
class DataServiceImpl extends DataService<ResourcePolicy> {
|
||||||
|
protected linkPath = 'resourcepolicies';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected store: Store<CoreState>,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: ChangeAnalyzer<ResourcePolicy>) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service responsible for fetching/sending data from/to the REST API on the resourcepolicies endpoint
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
@dataService(RESOURCE_POLICY)
|
||||||
|
export class ResourcePolicyService {
|
||||||
|
private dataService: DataServiceImpl;
|
||||||
|
protected searchByEPersonMethod = 'eperson';
|
||||||
|
protected searchByGroupMethod = 'group';
|
||||||
|
protected searchByResourceMethod = 'resource';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected comparator: DefaultChangeAnalyzer<ResourcePolicy>) {
|
||||||
|
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new ResourcePolicy on the server, and store the response
|
||||||
|
* in the object cache
|
||||||
|
*
|
||||||
|
* @param {ResourcePolicy} resourcePolicy
|
||||||
|
* The resource policy to create
|
||||||
|
* @param {string} resourceUUID
|
||||||
|
* The uuid of the resource target of the policy
|
||||||
|
* @param {string} epersonUUID
|
||||||
|
* The uuid of the eperson that will be grant of the permission. Exactly one of eperson or group is required
|
||||||
|
* @param {string} groupUUID
|
||||||
|
* The uuid of the group that will be grant of the permission. Exactly one of eperson or group is required
|
||||||
|
*/
|
||||||
|
create(resourcePolicy: ResourcePolicy, resourceUUID: string, epersonUUID?: string, groupUUID?: string): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
const params = [];
|
||||||
|
params.push(new RequestParam('resource', resourceUUID));
|
||||||
|
if (isNotEmpty(epersonUUID)) {
|
||||||
|
params.push(new RequestParam('eperson', epersonUUID));
|
||||||
|
} else if (isNotEmpty(groupUUID)) {
|
||||||
|
params.push(new RequestParam('group', groupUUID));
|
||||||
|
}
|
||||||
|
return this.dataService.create(resourcePolicy, ...params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an existing ResourcePolicy on the server
|
||||||
|
*
|
||||||
|
* @param resourcePolicyID The resource policy's id to be removed
|
||||||
|
* @return an observable that emits true when the deletion was successful, false when it failed
|
||||||
|
*/
|
||||||
|
delete(resourcePolicyID: string): Observable<boolean> {
|
||||||
|
return this.dataService.delete(resourcePolicyID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new patch to the object cache
|
||||||
|
* The patch is derived from the differences between the given object and its version in the object cache
|
||||||
|
* @param {ResourcePolicy} object The given object
|
||||||
|
*/
|
||||||
|
update(object: ResourcePolicy): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
return this.dataService.update(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable of {@link RemoteData} of a {@link ResourcePolicy}, based on an href, with a list of {@link FollowLinkConfig},
|
||||||
|
* to automatically resolve {@link HALLink}s of the {@link ResourcePolicy}
|
||||||
|
* @param href The url of {@link ResourcePolicy} we want to retrieve
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
findByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
return this.dataService.findByHref(href, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an observable of {@link RemoteData} of a {@link ResourcePolicy}, based on its ID, with a list of {@link FollowLinkConfig},
|
||||||
|
* to automatically resolve {@link HALLink}s of the object
|
||||||
|
* @param id ID of {@link ResourcePolicy} we want to retrieve
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
findById(id: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
return this.dataService.findById(id, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the defaultAccessConditions {@link ResourcePolicy} list for a given {@link Collection}
|
||||||
|
*
|
||||||
|
* @param collection the {@link Collection} to retrieve the defaultAccessConditions for
|
||||||
|
* @param findListOptions the {@link FindListOptions} for the request
|
||||||
|
*/
|
||||||
|
getDefaultAccessConditionsFor(collection: Collection, findListOptions?: FindListOptions): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
||||||
|
return this.dataService.findAllByHref(collection._links.defaultAccessConditions.href, findListOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link ResourcePolicy} list for a {@link EPerson}
|
||||||
|
*
|
||||||
|
* @param UUID UUID of a given {@link EPerson}
|
||||||
|
* @param resourceUUID Limit the returned policies to the specified DSO
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
searchByEPerson(UUID: string, resourceUUID?: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', UUID)];
|
||||||
|
if (isNotEmpty(resourceUUID)) {
|
||||||
|
options.searchParams.push(new RequestParam('resource', resourceUUID))
|
||||||
|
}
|
||||||
|
return this.dataService.searchBy(this.searchByEPersonMethod, options, ...linksToFollow)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link ResourcePolicy} list for a {@link Group}
|
||||||
|
*
|
||||||
|
* @param UUID UUID of a given {@link Group}
|
||||||
|
* @param resourceUUID Limit the returned policies to the specified DSO
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
searchByGroup(UUID: string, resourceUUID?: string, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', UUID)];
|
||||||
|
if (isNotEmpty(resourceUUID)) {
|
||||||
|
options.searchParams.push(new RequestParam('resource', resourceUUID))
|
||||||
|
}
|
||||||
|
return this.dataService.searchBy(this.searchByGroupMethod, options, ...linksToFollow)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the {@link ResourcePolicy} list for a given DSO
|
||||||
|
*
|
||||||
|
* @param UUID UUID of a given DSO
|
||||||
|
* @param action Limit the returned policies to the specified {@link ActionType}
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||||
|
*/
|
||||||
|
searchByResource(UUID: string, action?: ActionType, ...linksToFollow: Array<FollowLinkConfig<ResourcePolicy>>): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
|
||||||
|
const options = new FindListOptions();
|
||||||
|
options.searchParams = [new RequestParam('uuid', UUID)];
|
||||||
|
if (isNotEmpty(action)) {
|
||||||
|
options.searchParams.push(new RequestParam('action', action))
|
||||||
|
}
|
||||||
|
return this.dataService.searchBy(this.searchByResourceMethod, options, ...linksToFollow)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,8 +1,15 @@
|
|||||||
import { deserialize, inheritSerialization } from 'cerialize';
|
import { deserialize, inheritSerialization } from 'cerialize';
|
||||||
import { typedObject } from '../cache/builders/build-decorators';
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { link, typedObject } from '../cache/builders/build-decorators';
|
||||||
import { BUNDLE } from './bundle.resource-type';
|
import { BUNDLE } from './bundle.resource-type';
|
||||||
import { DSpaceObject } from './dspace-object.model';
|
import { DSpaceObject } from './dspace-object.model';
|
||||||
import { HALLink } from './hal-link.model';
|
import { HALLink } from './hal-link.model';
|
||||||
|
import { RemoteData } from '../data/remote-data';
|
||||||
|
import { PaginatedList } from '../data/paginated-list';
|
||||||
|
import { BITSTREAM } from './bitstream.resource-type';
|
||||||
|
import { Bitstream } from './bitstream.model';
|
||||||
|
|
||||||
@typedObject
|
@typedObject
|
||||||
@inheritSerialization(DSpaceObject)
|
@inheritSerialization(DSpaceObject)
|
||||||
@@ -17,5 +24,19 @@ export class Bundle extends DSpaceObject {
|
|||||||
self: HALLink;
|
self: HALLink;
|
||||||
primaryBitstream: HALLink;
|
primaryBitstream: HALLink;
|
||||||
bitstreams: HALLink;
|
bitstreams: HALLink;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The primary Bitstream of this Bundle
|
||||||
|
* Will be undefined unless the primaryBitstream {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(BITSTREAM)
|
||||||
|
primaryBitstream?: Observable<RemoteData<Bitstream>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of Bitstreams that are direct children of this Bundle
|
||||||
|
* Will be undefined unless the bitstreams {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(BITSTREAM, true)
|
||||||
|
bitstreams?: Observable<RemoteData<PaginatedList<Bitstream>>>;
|
||||||
}
|
}
|
||||||
|
@@ -10,8 +10,8 @@ import { DSpaceObject } from './dspace-object.model';
|
|||||||
import { HALLink } from './hal-link.model';
|
import { HALLink } from './hal-link.model';
|
||||||
import { License } from './license.model';
|
import { License } from './license.model';
|
||||||
import { LICENSE } from './license.resource-type';
|
import { LICENSE } from './license.resource-type';
|
||||||
import { ResourcePolicy } from './resource-policy.model';
|
import { ResourcePolicy } from '../resource-policy/models/resource-policy.model';
|
||||||
import { RESOURCE_POLICY } from './resource-policy.resource-type';
|
import { RESOURCE_POLICY } from '../resource-policy/models/resource-policy.resource-type';
|
||||||
import { COMMUNITY } from './community.resource-type';
|
import { COMMUNITY } from './community.resource-type';
|
||||||
import { Community } from './community.model';
|
import { Community } from './community.model';
|
||||||
import { ChildHALResource } from './child-hal-resource.model';
|
import { ChildHALResource } from './child-hal-resource.model';
|
||||||
|
@@ -67,6 +67,10 @@ export const getSucceededRemoteData = () =>
|
|||||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded));
|
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded));
|
||||||
|
|
||||||
|
export const getSucceededRemoteWithNotEmptyData = () =>
|
||||||
|
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||||
|
source.pipe(find((rd: RemoteData<T>) => rd.hasSucceeded && isNotEmpty(rd.payload)));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the first successful remotely retrieved object
|
* Get the first successful remotely retrieved object
|
||||||
*
|
*
|
||||||
@@ -84,6 +88,23 @@ export const getFirstSucceededRemoteDataPayload = () =>
|
|||||||
getRemoteDataPayload()
|
getRemoteDataPayload()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the first successful remotely retrieved object with not empty payload
|
||||||
|
*
|
||||||
|
* You usually don't want to use this, it is a code smell.
|
||||||
|
* Work with the RemoteData object instead, that way you can
|
||||||
|
* handle loading and errors correctly.
|
||||||
|
*
|
||||||
|
* These operators were created as a first step in refactoring
|
||||||
|
* out all the instances where this is used incorrectly.
|
||||||
|
*/
|
||||||
|
export const getFirstSucceededRemoteDataWithNotEmptyPayload = () =>
|
||||||
|
<T>(source: Observable<RemoteData<T>>): Observable<T> =>
|
||||||
|
source.pipe(
|
||||||
|
getSucceededRemoteWithNotEmptyData(),
|
||||||
|
getRemoteDataPayload()
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the all successful remotely retrieved objects
|
* Get the all successful remotely retrieved objects
|
||||||
*
|
*
|
||||||
|
@@ -1,58 +0,0 @@
|
|||||||
import { autoserialize, deserialize, deserializeAs } from 'cerialize';
|
|
||||||
import { typedObject } from '../cache/builders/build-decorators';
|
|
||||||
import { IDToUUIDSerializer } from '../cache/id-to-uuid-serializer';
|
|
||||||
import { ActionType } from '../cache/models/action-type.model';
|
|
||||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
|
||||||
import { excludeFromEquals } from '../utilities/equals.decorators';
|
|
||||||
import { HALLink } from './hal-link.model';
|
|
||||||
import { RESOURCE_POLICY } from './resource-policy.resource-type';
|
|
||||||
import { ResourceType } from './resource-type';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model class for a Resource Policy
|
|
||||||
*/
|
|
||||||
@typedObject
|
|
||||||
export class ResourcePolicy implements CacheableObject {
|
|
||||||
static type = RESOURCE_POLICY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The object type
|
|
||||||
*/
|
|
||||||
@excludeFromEquals
|
|
||||||
@autoserialize
|
|
||||||
type: ResourceType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The action that is allowed by this Resource Policy
|
|
||||||
*/
|
|
||||||
@autoserialize
|
|
||||||
action: ActionType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name for this Resource Policy
|
|
||||||
*/
|
|
||||||
@autoserialize
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The uuid of the Group this Resource Policy applies to
|
|
||||||
*/
|
|
||||||
@autoserialize
|
|
||||||
groupUUID: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The universally unique identifier for this Resource Policy
|
|
||||||
* This UUID is generated client-side and isn't used by the backend.
|
|
||||||
* It is based on the ID, so it will be the same for each refresh.
|
|
||||||
*/
|
|
||||||
@deserializeAs(new IDToUUIDSerializer('resource-policy'), 'id')
|
|
||||||
uuid: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link HALLink}s for this ResourcePolicy
|
|
||||||
*/
|
|
||||||
@deserialize
|
|
||||||
_links: {
|
|
||||||
self: HALLink,
|
|
||||||
}
|
|
||||||
}
|
|
@@ -13,6 +13,7 @@ import { getSucceededRemoteData } from '../../../core/shared/operators';
|
|||||||
import { ResourceType } from '../../../core/shared/resource-type';
|
import { ResourceType } from '../../../core/shared/resource-type';
|
||||||
import { hasValue, isNotEmpty, isNotUndefined } from '../../empty.util';
|
import { hasValue, isNotEmpty, isNotUndefined } from '../../empty.util';
|
||||||
import { NotificationsService } from '../../notifications/notifications.service';
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { RequestParam } from '../../../core/cache/models/request-param.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the create page for communities and collections
|
* Component representing the create page for communities and collections
|
||||||
@@ -76,7 +77,7 @@ export class CreateComColPageComponent<TDomain extends DSpaceObject> implements
|
|||||||
const uploader = event.uploader;
|
const uploader = event.uploader;
|
||||||
|
|
||||||
this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => {
|
this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => {
|
||||||
this.dsoDataService.create(dso, uuid)
|
this.dsoDataService.create(dso, new RequestParam('parent', uuid))
|
||||||
.pipe(getSucceededRemoteData())
|
.pipe(getSucceededRemoteData())
|
||||||
.subscribe((dsoRD: RemoteData<TDomain>) => {
|
.subscribe((dsoRD: RemoteData<TDomain>) => {
|
||||||
if (isNotUndefined(dsoRD)) {
|
if (isNotUndefined(dsoRD)) {
|
||||||
|
@@ -3,6 +3,8 @@ import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { isObject } from 'lodash';
|
import { isObject } from 'lodash';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
|
|
||||||
|
import { isNull } from './empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the passed value is a NgbDateStruct.
|
* Returns true if the passed value is a NgbDateStruct.
|
||||||
*
|
*
|
||||||
@@ -13,7 +15,7 @@ import * as moment from 'moment';
|
|||||||
*/
|
*/
|
||||||
export function isNgbDateStruct(value: object): boolean {
|
export function isNgbDateStruct(value: object): boolean {
|
||||||
return isObject(value) && value.hasOwnProperty('day')
|
return isObject(value) && value.hasOwnProperty('day')
|
||||||
&& value.hasOwnProperty('month') && value.hasOwnProperty('year');
|
&& value.hasOwnProperty('month') && value.hasOwnProperty('year');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,3 +58,57 @@ export function dateToISOFormat(date: Date | NgbDateStruct): string {
|
|||||||
export function ngbDateStructToDate(date: NgbDateStruct): Date {
|
export function ngbDateStructToDate(date: NgbDateStruct): Date {
|
||||||
return new Date(date.year, (date.month - 1), date.day);
|
return new Date(date.year, (date.month - 1), date.day);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a NgbDateStruct object started from a string representing a date
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* The Date to convert
|
||||||
|
* @return NgbDateStruct
|
||||||
|
* the NgbDateStruct object
|
||||||
|
*/
|
||||||
|
export function stringToNgbDateStruct(date: string): NgbDateStruct {
|
||||||
|
return dateToNgbDateStruct(new Date(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a NgbDateStruct object started from a Date object
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* The Date to convert
|
||||||
|
* @return NgbDateStruct
|
||||||
|
* the NgbDateStruct object
|
||||||
|
*/
|
||||||
|
export function dateToNgbDateStruct(date?: Date): NgbDateStruct {
|
||||||
|
if (isNull(date)) {
|
||||||
|
date = new Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
year: date.getFullYear(),
|
||||||
|
month: date.getMonth() + 1,
|
||||||
|
day: date.getDate()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a date in simplified format (YYYY-MM-DD).
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* The date to format
|
||||||
|
* @return string
|
||||||
|
* the formatted date
|
||||||
|
*/
|
||||||
|
export function dateToString(date: Date | NgbDateStruct): string {
|
||||||
|
const dateObj: Date = (date instanceof Date) ? date : ngbDateStructToDate(date);
|
||||||
|
|
||||||
|
let year = dateObj.getFullYear().toString();
|
||||||
|
let month = (dateObj.getMonth() + 1).toString();
|
||||||
|
let day = dateObj.getDate().toString();
|
||||||
|
|
||||||
|
year = (year.length === 1) ? '0' + year : year;
|
||||||
|
month = (month.length === 1) ? '0' + month : month;
|
||||||
|
day = (day.length === 1) ? '0' + day : day;
|
||||||
|
const dateStr = `${year}-${month}-${day}`;
|
||||||
|
return moment.utc(dateStr, 'YYYYMMDD').format('YYYY-MM-DD');
|
||||||
|
}
|
||||||
|
10
src/app/shared/mocks/mock-resource-policy-service.ts
Normal file
10
src/app/shared/mocks/mock-resource-policy-service.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
|
||||||
|
|
||||||
|
export function getMockResourcePolicyService(): ResourcePolicyService {
|
||||||
|
return jasmine.createSpyObj('resourcePolicyService', {
|
||||||
|
searchByResource: jasmine.createSpy('searchByResource'),
|
||||||
|
create: jasmine.createSpy('create'),
|
||||||
|
delete: jasmine.createSpy('delete'),
|
||||||
|
update: jasmine.createSpy('update')
|
||||||
|
});
|
||||||
|
}
|
16
src/app/shared/ng-for-track-by-id.directive.ts
Normal file
16
src/app/shared/ng-for-track-by-id.directive.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Directive, Host } from '@angular/core';
|
||||||
|
import { NgForOf } from '@angular/common';
|
||||||
|
|
||||||
|
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
// tslint:disable-next-line:directive-selector
|
||||||
|
selector: '[ngForTrackById]',
|
||||||
|
})
|
||||||
|
export class NgForTrackByIdDirective<T extends DSpaceObject> {
|
||||||
|
|
||||||
|
constructor(@Host() private ngFor: NgForOf<T>) {
|
||||||
|
this.ngFor.ngForTrackBy = (index: number, dso: T) => (dso) ? dso.id : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h4 class="mb-3">{{'resource-policies.create.page.heading' | translate}} {{targetResourceName}}</h4>
|
||||||
|
|
||||||
|
<ds-resource-policy-form [isProcessing]="isProcessing()"
|
||||||
|
(reset)="redirectToAuthorizationsPage()"
|
||||||
|
(submit)="createResourcePolicy($event)"></ds-resource-policy-form>
|
||||||
|
</div>
|
@@ -0,0 +1,265 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createFailedRemoteDataObject,
|
||||||
|
createSuccessfulRemoteDataObject
|
||||||
|
} from '../../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../testing/utils.test';
|
||||||
|
import { ResourcePolicyCreateComponent } from './resource-policy-create.component';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { getMockResourcePolicyService } from '../../mocks/mock-resource-policy-service';
|
||||||
|
import { getMockLinkService } from '../../mocks/link-service.mock';
|
||||||
|
import { RouterStub } from '../../testing/router.stub';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { createMockRDPaginatedObs } from '../../../+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec';
|
||||||
|
import { ResourcePolicyEvent } from '../form/resource-policy-form.component';
|
||||||
|
import { GroupMock } from '../../testing/group-mock';
|
||||||
|
import { submittedResourcePolicy } from '../form/resource-policy-form.component.spec';
|
||||||
|
import { PolicyType } from '../../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../../core/resource-policy/models/action-type.model';
|
||||||
|
import { EPersonMock } from '../../testing/eperson.mock';
|
||||||
|
|
||||||
|
describe('ResourcePolicyCreateComponent test suite', () => {
|
||||||
|
let comp: ResourcePolicyCreateComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ResourcePolicyCreateComponent>;
|
||||||
|
let de;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let eventPayload: ResourcePolicyEvent;
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject({})),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject(GroupMock))
|
||||||
|
};
|
||||||
|
|
||||||
|
const item = Object.assign(new Item(), {
|
||||||
|
uuid: 'itemUUID',
|
||||||
|
id: 'itemUUID',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [{
|
||||||
|
value: 'test item'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
_links: {
|
||||||
|
self: { href: 'item-selflink' }
|
||||||
|
},
|
||||||
|
bundles: createMockRDPaginatedObs([])
|
||||||
|
});
|
||||||
|
|
||||||
|
const resourcePolicyService: any = getMockResourcePolicyService();
|
||||||
|
const linkService: any = getMockLinkService();
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
resourcePolicyTarget: createSuccessfulRemoteDataObject(item)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `url/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ResourcePolicyCreateComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: ResourcePolicyService, useValue: resourcePolicyService },
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
ResourcePolicyCreateComponent,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Injector
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-resource-policy-create></ds-resource-policy-create>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ResourcePolicyCreateComponent', inject([ResourcePolicyCreateComponent], (app: ResourcePolicyCreateComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyCreateComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init component properly', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(compAsAny.targetResourceUUID).toBe('itemUUID');
|
||||||
|
expect(compAsAny.targetResourceName).toBe('test item');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to authorizations page', () => {
|
||||||
|
comp.redirectToAuthorizationsPage();
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when is Processing', () => {
|
||||||
|
compAsAny.processing$.next(true);
|
||||||
|
expect(comp.isProcessing()).toBeObservable(cold('a', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when is not Processing', () => {
|
||||||
|
compAsAny.processing$.next(false);
|
||||||
|
expect(comp.isProcessing()).toBeObservable(cold('a', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when target type is group', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(comp, 'redirectToAuthorizationsPage').and.callThrough();
|
||||||
|
|
||||||
|
compAsAny.targetResourceUUID = 'itemUUID';
|
||||||
|
|
||||||
|
eventPayload = Object.create({});
|
||||||
|
eventPayload.object = submittedResourcePolicy;
|
||||||
|
eventPayload.target = {
|
||||||
|
type: 'group',
|
||||||
|
uuid: GroupMock.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify success when creation is successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.create.and.returnValue(observableOf(createSuccessfulRemoteDataObject(resourcePolicy)));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.createResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.create).toHaveBeenCalledWith(eventPayload.object, 'itemUUID', null, eventPayload.target.uuid);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify error when creation is not successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.create.and.returnValue(observableOf(createFailedRemoteDataObject({})));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.createResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.create).toHaveBeenCalledWith(eventPayload.object, 'itemUUID', null, eventPayload.target.uuid);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when target type of created policy is eperson', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(comp, 'redirectToAuthorizationsPage').and.callThrough();
|
||||||
|
|
||||||
|
compAsAny.targetResourceUUID = 'itemUUID';
|
||||||
|
|
||||||
|
eventPayload = Object.create({});
|
||||||
|
eventPayload.object = submittedResourcePolicy;
|
||||||
|
eventPayload.target = {
|
||||||
|
type: 'eperson',
|
||||||
|
uuid: EPersonMock.id
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify success when creation is successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.create.and.returnValue(observableOf(createSuccessfulRemoteDataObject(resourcePolicy)));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.createResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.create).toHaveBeenCalledWith(eventPayload.object, 'itemUUID', eventPayload.target.uuid);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify error when creation is not successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.create.and.returnValue(observableOf(createFailedRemoteDataObject({})));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.createResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.create).toHaveBeenCalledWith(eventPayload.object, 'itemUUID', eventPayload.target.uuid);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,112 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { first, map, take } from 'rxjs/operators';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { ResourcePolicyEvent } from '../form/resource-policy-form.component';
|
||||||
|
import { ITEM_EDIT_AUTHORIZATIONS_PATH } from '../../../+item-page/edit-item-page/edit-item-page.routing.module';
|
||||||
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-resource-policy-create',
|
||||||
|
templateUrl: './resource-policy-create.component.html'
|
||||||
|
})
|
||||||
|
export class ResourcePolicyCreateComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the resource target of the policy
|
||||||
|
*/
|
||||||
|
public targetResourceName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if a submission creation operation is pending
|
||||||
|
* @type {BehaviorSubject<boolean>}
|
||||||
|
*/
|
||||||
|
private processing$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The uuid of the resource target of the policy
|
||||||
|
*/
|
||||||
|
private targetResourceUUID: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {DSONameService} dsoNameService
|
||||||
|
* @param {NotificationsService} notificationsService
|
||||||
|
* @param {ResourcePolicyService} resourcePolicyService
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
* @param {Router} router
|
||||||
|
* @param {TranslateService} translate
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private resourcePolicyService: ResourcePolicyService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private translate: TranslateService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.route.data.pipe(
|
||||||
|
map((data) => data),
|
||||||
|
take(1)
|
||||||
|
).subscribe((data: any) => {
|
||||||
|
this.targetResourceUUID = (data.resourcePolicyTarget as RemoteData<DSpaceObject>).payload.id;
|
||||||
|
this.targetResourceName = this.dsoNameService.getName((data.resourcePolicyTarget as RemoteData<DSpaceObject>).payload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing if an operation is pending
|
||||||
|
*
|
||||||
|
* @return {Observable<boolean>}
|
||||||
|
*/
|
||||||
|
isProcessing(): Observable<boolean> {
|
||||||
|
return this.processing$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the authorizations page
|
||||||
|
*/
|
||||||
|
redirectToAuthorizationsPage(): void {
|
||||||
|
this.router.navigate([`../../${ITEM_EDIT_AUTHORIZATIONS_PATH}`], { relativeTo: this.route });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new resource policy
|
||||||
|
*
|
||||||
|
* @param event The {{ResourcePolicyEvent}} emitted
|
||||||
|
*/
|
||||||
|
createResourcePolicy(event: ResourcePolicyEvent): void {
|
||||||
|
this.processing$.next(true);
|
||||||
|
let response$;
|
||||||
|
if (event.target.type === 'eperson') {
|
||||||
|
response$ = this.resourcePolicyService.create(event.object, this.targetResourceUUID, event.target.uuid);
|
||||||
|
} else {
|
||||||
|
response$ = this.resourcePolicyService.create(event.object, this.targetResourceUUID, null, event.target.uuid);
|
||||||
|
}
|
||||||
|
response$.pipe(
|
||||||
|
first((response: RemoteData<ResourcePolicy>) => !response.isResponsePending)
|
||||||
|
).subscribe((responseRD: RemoteData<ResourcePolicy>) => {
|
||||||
|
this.processing$.next(false);
|
||||||
|
if (responseRD.hasSucceeded) {
|
||||||
|
this.notificationsService.success(null, this.translate.get('resource-policies.create.page.success.content'));
|
||||||
|
this.redirectToAuthorizationsPage();
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(null, this.translate.get('resource-policies.create.page.failure.content'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h4 class="mb-3">{{'resource-policies.edit.page.heading' | translate}} {{resourcePolicy.id}}</h4>
|
||||||
|
|
||||||
|
<ds-resource-policy-form [resourcePolicy]="resourcePolicy"
|
||||||
|
[isProcessing]="isProcessing()"
|
||||||
|
(reset)="redirectToAuthorizationsPage()"
|
||||||
|
(submit)="updateResourcePolicy($event)"></ds-resource-policy-form>
|
||||||
|
</div>
|
@@ -0,0 +1,220 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createFailedRemoteDataObject,
|
||||||
|
createSuccessfulRemoteDataObject
|
||||||
|
} from '../../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../testing/utils.test';
|
||||||
|
import { LinkService } from '../../../core/cache/builders/link.service';
|
||||||
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { NotificationsServiceStub } from '../../testing/notifications-service.stub';
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { getMockResourcePolicyService } from '../../mocks/mock-resource-policy-service';
|
||||||
|
import { getMockLinkService } from '../../mocks/link-service.mock';
|
||||||
|
import { RouterStub } from '../../testing/router.stub';
|
||||||
|
import { ResourcePolicyEvent } from '../form/resource-policy-form.component';
|
||||||
|
import { GroupMock } from '../../testing/group-mock';
|
||||||
|
import { submittedResourcePolicy } from '../form/resource-policy-form.component.spec';
|
||||||
|
import { PolicyType } from '../../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../../core/resource-policy/models/action-type.model';
|
||||||
|
import { ResourcePolicyEditComponent } from './resource-policy-edit.component';
|
||||||
|
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
|
||||||
|
|
||||||
|
describe('ResourcePolicyEditComponent test suite', () => {
|
||||||
|
let comp: ResourcePolicyEditComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ResourcePolicyEditComponent>;
|
||||||
|
let de;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
let eventPayload: ResourcePolicyEvent;
|
||||||
|
let updatedObject;
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject({})),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject(GroupMock))
|
||||||
|
};
|
||||||
|
|
||||||
|
const resourcePolicyService: any = getMockResourcePolicyService();
|
||||||
|
const linkService: any = getMockLinkService();
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
resourcePolicy: createSuccessfulRemoteDataObject(resourcePolicy)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `url/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ResourcePolicyEditComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: ResourcePolicyService, useValue: resourcePolicyService },
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
ResourcePolicyEditComponent,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Injector
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-resource-policy-edit></ds-resource-policy-edit>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ResourcePolicyEditComponent', inject([ResourcePolicyEditComponent], (app: ResourcePolicyEditComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyEditComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init component properly', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(compAsAny.resourcePolicy).toEqual(resourcePolicy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to authorizations page', () => {
|
||||||
|
comp.redirectToAuthorizationsPage();
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when is Processing', () => {
|
||||||
|
compAsAny.processing$.next(true);
|
||||||
|
expect(comp.isProcessing()).toBeObservable(cold('a', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when is not Processing', () => {
|
||||||
|
compAsAny.processing$.next(false);
|
||||||
|
expect(comp.isProcessing()).toBeObservable(cold('a', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(comp, 'redirectToAuthorizationsPage').and.callThrough();
|
||||||
|
compAsAny.resourcePolicyService.update.and.returnValue(observableOf(createSuccessfulRemoteDataObject(resourcePolicy)));
|
||||||
|
|
||||||
|
compAsAny.targetResourceUUID = 'itemUUID';
|
||||||
|
|
||||||
|
eventPayload = Object.create({});
|
||||||
|
eventPayload.object = submittedResourcePolicy;
|
||||||
|
eventPayload.target = {
|
||||||
|
type: 'group',
|
||||||
|
uuid: GroupMock.id
|
||||||
|
};
|
||||||
|
|
||||||
|
compAsAny.resourcePolicy = resourcePolicy;
|
||||||
|
|
||||||
|
updatedObject = Object.assign({}, submittedResourcePolicy, {
|
||||||
|
id: resourcePolicy.id,
|
||||||
|
type: RESOURCE_POLICY.value,
|
||||||
|
_links: resourcePolicy._links
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify success when update is successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.update.and.returnValue(observableOf(createSuccessfulRemoteDataObject(resourcePolicy)));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.updateResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.update).toHaveBeenCalledWith(updatedObject);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify error when update is not successful', () => {
|
||||||
|
compAsAny.resourcePolicyService.update.and.returnValue(observableOf(createFailedRemoteDataObject({})));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.updateResourcePolicy(eventPayload));
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyService.update).toHaveBeenCalledWith(updatedObject);
|
||||||
|
expect(comp.redirectToAuthorizationsPage).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,102 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
import { first, map, take } from 'rxjs/operators';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { ResourcePolicyEvent } from '../form/resource-policy-form.component';
|
||||||
|
import { ITEM_EDIT_AUTHORIZATIONS_PATH } from '../../../+item-page/edit-item-page/edit-item-page.routing.module';
|
||||||
|
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-resource-policy-edit',
|
||||||
|
templateUrl: './resource-policy-edit.component.html'
|
||||||
|
})
|
||||||
|
export class ResourcePolicyEditComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource policy object to edit
|
||||||
|
*/
|
||||||
|
public resourcePolicy: ResourcePolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if a submission editing operation is pending
|
||||||
|
* @type {BehaviorSubject<boolean>}
|
||||||
|
*/
|
||||||
|
private processing$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {NotificationsService} notificationsService
|
||||||
|
* @param {ResourcePolicyService} resourcePolicyService
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
* @param {Router} router
|
||||||
|
* @param {TranslateService} translate
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private resourcePolicyService: ResourcePolicyService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private translate: TranslateService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.route.data.pipe(
|
||||||
|
map((data) => data),
|
||||||
|
take(1)
|
||||||
|
).subscribe((data: any) => {
|
||||||
|
this.resourcePolicy = (data.resourcePolicy as RemoteData<ResourcePolicy>).payload;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing if an operation is pending
|
||||||
|
*
|
||||||
|
* @return {Observable<boolean>}
|
||||||
|
*/
|
||||||
|
isProcessing(): Observable<boolean> {
|
||||||
|
return this.processing$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to the authorizations page
|
||||||
|
*/
|
||||||
|
redirectToAuthorizationsPage() {
|
||||||
|
this.router.navigate([`../../${ITEM_EDIT_AUTHORIZATIONS_PATH}`], { relativeTo: this.route });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a resource policy
|
||||||
|
*
|
||||||
|
* @param event The {{ResourcePolicyEvent}} emitted
|
||||||
|
*/
|
||||||
|
updateResourcePolicy(event: ResourcePolicyEvent) {
|
||||||
|
this.processing$.next(true);
|
||||||
|
const updatedObject = Object.assign({}, event.object, {
|
||||||
|
id: this.resourcePolicy.id,
|
||||||
|
type: RESOURCE_POLICY.value,
|
||||||
|
_links: this.resourcePolicy._links
|
||||||
|
});
|
||||||
|
this.resourcePolicyService.update(updatedObject).pipe(
|
||||||
|
first((response: RemoteData<ResourcePolicy>) => !response.isResponsePending)
|
||||||
|
).subscribe((responseRD: RemoteData<ResourcePolicy>) => {
|
||||||
|
this.processing$.next(false);
|
||||||
|
if (responseRD.hasSucceeded) {
|
||||||
|
this.notificationsService.success(null, this.translate.get('resource-policies.edit.page.success.content'));
|
||||||
|
this.redirectToAuthorizationsPage();
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(null, this.translate.get('resource-policies.edit.page.failure.content'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
<div class="mt-3" @fadeInOut>
|
||||||
|
<ds-eperson-search-box *ngIf="isListOfEPerson" (search)="onSearch($event)"></ds-eperson-search-box>
|
||||||
|
<ds-group-search-box *ngIf="!isListOfEPerson" (search)="onSearch($event)"></ds-group-search-box>
|
||||||
|
|
||||||
|
<ds-pagination *ngIf="(getList() | async)?.payload?.totalElements > 0"
|
||||||
|
[paginationOptions]="paginationOptions"
|
||||||
|
[collectionSize]="(getList() | async)?.payload?.totalElements"
|
||||||
|
[disableRouteParameterUpdate]="true"
|
||||||
|
[hideGear]="true"
|
||||||
|
(pageChange)="onPageChange($event)">
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="groups" class="table table-sm table-striped table-hover table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-center">
|
||||||
|
<th>{{'resource-policies.form.eperson-group-list.table.headers.id' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.form.eperson-group-list.table.headers.name' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.form.eperson-group-list.table.headers.action' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let entry of (getList() | async)?.payload?.page"
|
||||||
|
[class.table-primary]="isSelected(entry) | async">
|
||||||
|
<td>{{entry.id}}</td>
|
||||||
|
<td>{{dsoNameService.getName(entry)}}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<button class="btn btn-sm btn-outline-primary" (click)="emitSelect(entry)">
|
||||||
|
{{'resource-policies.form.eperson-group-list.select.btn' | translate}}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ds-pagination>
|
||||||
|
</div>
|
@@ -0,0 +1,287 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
import { uniqueId } from 'lodash';
|
||||||
|
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../../testing/utils.test';
|
||||||
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
|
import { getMockRequestService } from '../../../mocks/request.service.mock';
|
||||||
|
import { EpersonGroupListComponent, SearchEvent } from './eperson-group-list.component';
|
||||||
|
import { EPersonMock } from '../../../testing/eperson.mock';
|
||||||
|
import { GroupMock } from '../../../testing/group-mock';
|
||||||
|
import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
describe('EpersonGroupListComponent test suite', () => {
|
||||||
|
let comp: EpersonGroupListComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<EpersonGroupListComponent>;
|
||||||
|
let de;
|
||||||
|
let groupService: any;
|
||||||
|
let epersonService: any;
|
||||||
|
|
||||||
|
const paginationOptions: PaginationComponentOptions = new PaginationComponentOptions()
|
||||||
|
paginationOptions.id = uniqueId('eperson-group-list-pagination-test');
|
||||||
|
paginationOptions.pageSize = 5;
|
||||||
|
|
||||||
|
const mockEpersonService = jasmine.createSpyObj('epersonService',
|
||||||
|
{
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
findAll: jasmine.createSpy('findAll'),
|
||||||
|
searchByScope: jasmine.createSpy('searchByScope'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
linkPath: 'epersons'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockGroupService = jasmine.createSpyObj('groupService',
|
||||||
|
{
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
findAll: jasmine.createSpy('findAll'),
|
||||||
|
searchGroups: jasmine.createSpy('searchGroups'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
linkPath: 'groups'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const epersonPaginatedList = new PaginatedList(new PageInfo(), [EPersonMock, EPersonMock]);
|
||||||
|
const epersonPaginatedListRD = createSuccessfulRemoteDataObject(epersonPaginatedList);
|
||||||
|
|
||||||
|
const groupPaginatedList = new PaginatedList(new PageInfo(), [GroupMock, GroupMock]);
|
||||||
|
const groupPaginatedListRD = createSuccessfulRemoteDataObject(groupPaginatedList);
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
NoopAnimationsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
EpersonGroupListComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: EPersonDataService, useValue: mockEpersonService },
|
||||||
|
{ provide: GroupDataService, useValue: mockGroupService },
|
||||||
|
{ provide: RequestService, useValue: getMockRequestService() },
|
||||||
|
EpersonGroupListComponent,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Injector
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-eperson-group-list [isListOfEPerson]="isListOfEPerson" [initSelected]="initSelected"></ds-eperson-group-list>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create EpersonGroupListComponent', inject([EpersonGroupListComponent], (app: EpersonGroupListComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when is list of eperson', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(EpersonGroupListComponent);
|
||||||
|
epersonService = TestBed.get(EPersonDataService);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
comp.isListOfEPerson = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject EPersonDataService', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.dataService).toBeDefined();
|
||||||
|
expect(comp.updateList).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init entrySelectedId', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.initSelected = EPersonMock.id;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.entrySelectedId.value).toBe(EPersonMock.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init the list of eperson', () => {
|
||||||
|
epersonService.searchByScope.and.returnValue(observableOf(epersonPaginatedListRD));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.list$.value).toEqual(epersonPaginatedListRD);
|
||||||
|
expect(comp.getList()).toBeObservable(cold('a', {
|
||||||
|
a: epersonPaginatedListRD
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit select event', () => {
|
||||||
|
spyOn(comp.select, 'emit');
|
||||||
|
comp.emitSelect(EPersonMock);
|
||||||
|
|
||||||
|
expect(comp.select.emit).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.entrySelectedId.value).toBe(EPersonMock.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when entry is selected', () => {
|
||||||
|
compAsAny.entrySelectedId.next(EPersonMock.id);
|
||||||
|
|
||||||
|
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when entry is not selected', () => {
|
||||||
|
compAsAny.entrySelectedId.next('');
|
||||||
|
|
||||||
|
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update list on page change', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.onPageChange(2);
|
||||||
|
|
||||||
|
expect(compAsAny.updateList).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when is list of group', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(EpersonGroupListComponent);
|
||||||
|
groupService = TestBed.get(GroupDataService);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
comp.isListOfEPerson = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should inject GroupDataService', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.dataService).toBeDefined();
|
||||||
|
expect(comp.updateList).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init entrySelectedId', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.initSelected = GroupMock.id;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.entrySelectedId.value).toBe(GroupMock.id)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init the list of group', () => {
|
||||||
|
groupService.searchGroups.and.returnValue(observableOf(groupPaginatedListRD));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.list$.value).toEqual(groupPaginatedListRD);
|
||||||
|
expect(comp.getList()).toBeObservable(cold('a', {
|
||||||
|
a: groupPaginatedListRD
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit select event', () => {
|
||||||
|
spyOn(comp.select, 'emit');
|
||||||
|
comp.emitSelect(GroupMock);
|
||||||
|
|
||||||
|
expect(comp.select.emit).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.entrySelectedId.value).toBe(GroupMock.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when entry is selected', () => {
|
||||||
|
compAsAny.entrySelectedId.next(EPersonMock.id);
|
||||||
|
|
||||||
|
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when entry is not selected', () => {
|
||||||
|
compAsAny.entrySelectedId.next('');
|
||||||
|
|
||||||
|
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update list on page change', () => {
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.onPageChange(2);
|
||||||
|
|
||||||
|
expect(compAsAny.updateList).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update list on search triggered', () => {
|
||||||
|
const options: PaginationComponentOptions = comp.paginationOptions
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: 'metadata',
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
spyOn(comp, 'updateList');
|
||||||
|
comp.onSearch(event);
|
||||||
|
|
||||||
|
expect(compAsAny.updateList).toHaveBeenCalledWith(options, 'metadata', 'test');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
isListOfEPerson = true;
|
||||||
|
initSelected = '';
|
||||||
|
}
|
@@ -0,0 +1,201 @@
|
|||||||
|
import { Component, EventEmitter, Injector, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
|
|
||||||
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
|
import { map, take } from 'rxjs/operators';
|
||||||
|
import { uniqueId } from 'lodash'
|
||||||
|
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
|
import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model';
|
||||||
|
import { DataService } from '../../../../core/data/data.service';
|
||||||
|
import { hasValue, isNotEmpty } from '../../../empty.util';
|
||||||
|
import { FindListOptions } from '../../../../core/data/request.models';
|
||||||
|
import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { getDataServiceFor } from '../../../../core/cache/builders/build-decorators';
|
||||||
|
import { EPERSON } from '../../../../core/eperson/models/eperson.resource-type';
|
||||||
|
import { GROUP } from '../../../../core/eperson/models/group.resource-type';
|
||||||
|
import { ResourceType } from '../../../../core/shared/resource-type';
|
||||||
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
|
import { fadeInOut } from '../../../animations/fade';
|
||||||
|
|
||||||
|
export interface SearchEvent {
|
||||||
|
scope: string;
|
||||||
|
query: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-eperson-group-list',
|
||||||
|
styleUrls: ['./eperson-group-list.component.scss'],
|
||||||
|
templateUrl: './eperson-group-list.component.html',
|
||||||
|
animations: [
|
||||||
|
fadeInOut
|
||||||
|
]
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that shows a list of eperson or group
|
||||||
|
*/
|
||||||
|
export class EpersonGroupListComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing id component should list eperson or group
|
||||||
|
*/
|
||||||
|
@Input() isListOfEPerson = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The uuid of eperson or group initially selected
|
||||||
|
*/
|
||||||
|
@Input() initSelected: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when a eperson or group is selected.
|
||||||
|
* Event's payload equals to DSpaceObject.
|
||||||
|
*/
|
||||||
|
@Output() select: EventEmitter<DSpaceObject> = new EventEmitter<DSpaceObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current search query
|
||||||
|
*/
|
||||||
|
public currentSearchQuery = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current search scope
|
||||||
|
*/
|
||||||
|
public currentSearchScope = 'metadata';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pagination config used to display the list
|
||||||
|
*/
|
||||||
|
public paginationOptions: PaginationComponentOptions = new PaginationComponentOptions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data service used to make request.
|
||||||
|
* It could be EPersonDataService or GroupDataService
|
||||||
|
*/
|
||||||
|
private dataService: DataService<DSpaceObject>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of eperson or group
|
||||||
|
*/
|
||||||
|
private list$: BehaviorSubject<RemoteData<PaginatedList<DSpaceObject>>> = new BehaviorSubject<RemoteData<PaginatedList<DSpaceObject>>>({} as any);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The eperson or group's id selected
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
private entrySelectedId: BehaviorSubject<string> = new BehaviorSubject<string>('');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables and inject the properly DataService
|
||||||
|
*
|
||||||
|
* @param {DSONameService} dsoNameService
|
||||||
|
* @param {Injector} parentInjector
|
||||||
|
*/
|
||||||
|
constructor(public dsoNameService: DSONameService, private parentInjector: Injector) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
const resourceType: ResourceType = (this.isListOfEPerson) ? EPERSON : GROUP;
|
||||||
|
const provider = getDataServiceFor(resourceType);
|
||||||
|
this.dataService = Injector.create({
|
||||||
|
providers: [],
|
||||||
|
parent: this.parentInjector
|
||||||
|
}).get(provider);
|
||||||
|
this.paginationOptions.id = uniqueId('eperson-group-list-pagination');
|
||||||
|
this.paginationOptions.pageSize = 5;
|
||||||
|
|
||||||
|
if (this.initSelected) {
|
||||||
|
this.entrySelectedId.next(this.initSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called when an entry is selected.
|
||||||
|
* Emit a new select Event
|
||||||
|
*
|
||||||
|
* @param entry The eperson or group selected
|
||||||
|
*/
|
||||||
|
emitSelect(entry: DSpaceObject): void {
|
||||||
|
this.select.emit(entry);
|
||||||
|
this.entrySelectedId.next(entry.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the list of eperson or group
|
||||||
|
*/
|
||||||
|
getList(): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
|
||||||
|
return this.list$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing if a table row is selected
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isSelected(entry: DSpaceObject): Observable<boolean> {
|
||||||
|
return this.entrySelectedId.asObservable().pipe(
|
||||||
|
map((selectedId) => isNotEmpty(selectedId) && selectedId === entry.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on page change
|
||||||
|
*/
|
||||||
|
onPageChange(page: number): void {
|
||||||
|
this.paginationOptions.currentPage = page;
|
||||||
|
this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on search
|
||||||
|
*/
|
||||||
|
onSearch(searchEvent: SearchEvent) {
|
||||||
|
this.currentSearchQuery = searchEvent.query;
|
||||||
|
this.currentSearchScope = searchEvent.scope;
|
||||||
|
this.paginationOptions.currentPage = 1;
|
||||||
|
this.updateList(this.paginationOptions, this.currentSearchScope, this.currentSearchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a paginate list of eperson or group
|
||||||
|
*/
|
||||||
|
updateList(config: PaginationComponentOptions, scope: string, query: string): void {
|
||||||
|
const options: FindListOptions = Object.assign({}, new FindListOptions(), {
|
||||||
|
elementsPerPage: config.pageSize,
|
||||||
|
currentPage: config.currentPage
|
||||||
|
});
|
||||||
|
|
||||||
|
const search$: Observable<RemoteData<PaginatedList<DSpaceObject>>> = this.isListOfEPerson ?
|
||||||
|
(this.dataService as EPersonDataService).searchByScope(scope, query, options) :
|
||||||
|
(this.dataService as GroupDataService).searchGroups(query, options);
|
||||||
|
|
||||||
|
this.subs.push(search$.pipe(take(1))
|
||||||
|
.subscribe((list: RemoteData<PaginatedList<DSpaceObject>>) => {
|
||||||
|
this.list$.next(list)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.list$ = null;
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
<form class="d-flex justify-content-between"
|
||||||
|
[formGroup]="searchForm"
|
||||||
|
(ngSubmit)="submit(searchForm.value); $event.stopImmediatePropagation();" >
|
||||||
|
<div>
|
||||||
|
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
|
||||||
|
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
|
||||||
|
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow-1 mr-3 ml-3">
|
||||||
|
<div class="form-group input-group">
|
||||||
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
|
class="form-control" aria-label="Search input">
|
||||||
|
<span class="input-group-append">
|
||||||
|
<button type="submit" class="search-button btn btn-secondary">
|
||||||
|
{{ labelPrefix + 'search.button' | translate }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="search-button btn btn-secondary" (click)="submit(null); reset()">
|
||||||
|
{{ labelPrefix + 'button.see-all' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@@ -0,0 +1,115 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { createTestComponent } from '../../../../testing/utils.test';
|
||||||
|
import { EpersonSearchBoxComponent } from './eperson-search-box.component';
|
||||||
|
import { SearchEvent } from '../eperson-group-list.component';
|
||||||
|
|
||||||
|
describe('EpersonSearchBoxComponent test suite', () => {
|
||||||
|
let comp: EpersonSearchBoxComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<EpersonSearchBoxComponent>;
|
||||||
|
let de;
|
||||||
|
let formBuilder: FormBuilder;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
EpersonSearchBoxComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
FormBuilder,
|
||||||
|
EpersonSearchBoxComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-group-search-box></ds-group-search-box>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create EpersonSearchBoxComponent', inject([EpersonSearchBoxComponent], (app: EpersonSearchBoxComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(EpersonSearchBoxComponent);
|
||||||
|
formBuilder = TestBed.get(FormBuilder);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset the form', () => {
|
||||||
|
comp.searchForm = formBuilder.group(({
|
||||||
|
query: 'test',
|
||||||
|
}));
|
||||||
|
|
||||||
|
comp.reset();
|
||||||
|
|
||||||
|
expect(comp.searchForm.controls.query.value).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit new search event', () => {
|
||||||
|
const data = {
|
||||||
|
scope: 'metadata',
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: 'metadata',
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
spyOn(comp.search, 'emit');
|
||||||
|
|
||||||
|
comp.submit(data);
|
||||||
|
|
||||||
|
expect(comp.search.emit).toHaveBeenCalledWith(event);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,65 @@
|
|||||||
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
import { SearchEvent } from '../eperson-group-list.component';
|
||||||
|
import { isNotNull } from '../../../../empty.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component used to show a search box for epersons.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-eperson-search-box',
|
||||||
|
templateUrl: './eperson-search-box.component.html',
|
||||||
|
})
|
||||||
|
export class EpersonSearchBoxComponent {
|
||||||
|
|
||||||
|
labelPrefix = 'admin.access-control.epeople.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The search form
|
||||||
|
*/
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when a search is triggred.
|
||||||
|
* Event's payload is a SearchEvent.
|
||||||
|
*/
|
||||||
|
@Output() search: EventEmitter<SearchEvent> = new EventEmitter<SearchEvent>();
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder) {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
scope: 'metadata',
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the search form
|
||||||
|
*/
|
||||||
|
reset() {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
scope: 'metadata',
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a new search event
|
||||||
|
* @param data Form data
|
||||||
|
*/
|
||||||
|
submit(data: any) {
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: isNotNull(data) ? data.scope : 'metadata',
|
||||||
|
query: isNotNull(data) ? data.query : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
this.search.emit(event)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,20 @@
|
|||||||
|
<form class="d-flex justify-content-between"
|
||||||
|
[formGroup]="searchForm"
|
||||||
|
(ngSubmit)="submit(searchForm.value); $event.stopImmediatePropagation();" >
|
||||||
|
<div class="flex-grow-1 mr-3">
|
||||||
|
<div class="form-group input-group">
|
||||||
|
<input type="text" name="query" id="query" formControlName="query"
|
||||||
|
class="form-control" aria-label="Search input">
|
||||||
|
<span class="input-group-append">
|
||||||
|
<button type="submit" class="search-button btn btn-secondary">
|
||||||
|
{{ labelPrefix + 'search.button' | translate }}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="search-button btn btn-secondary" (click)="submit(null); reset()">
|
||||||
|
{{ labelPrefix + 'button.see-all' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
@@ -0,0 +1,114 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { createTestComponent } from '../../../../testing/utils.test';
|
||||||
|
import { GroupSearchBoxComponent } from './group-search-box.component';
|
||||||
|
import { SearchEvent } from '../eperson-group-list.component';
|
||||||
|
|
||||||
|
describe('GroupSearchBoxComponent test suite', () => {
|
||||||
|
let comp: GroupSearchBoxComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<GroupSearchBoxComponent>;
|
||||||
|
let de;
|
||||||
|
let formBuilder: FormBuilder;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
GroupSearchBoxComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
FormBuilder,
|
||||||
|
GroupSearchBoxComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-group-search-box></ds-group-search-box>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create GroupSearchBoxComponent', inject([GroupSearchBoxComponent], (app: GroupSearchBoxComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(GroupSearchBoxComponent);
|
||||||
|
formBuilder = TestBed.get(FormBuilder);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reset the form', () => {
|
||||||
|
comp.searchForm = formBuilder.group(({
|
||||||
|
query: 'test',
|
||||||
|
}));
|
||||||
|
|
||||||
|
comp.reset();
|
||||||
|
|
||||||
|
expect(comp.searchForm.controls.query.value).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit new search event', () => {
|
||||||
|
const data = {
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: '',
|
||||||
|
query: 'test'
|
||||||
|
}
|
||||||
|
spyOn(comp.search, 'emit');
|
||||||
|
|
||||||
|
comp.submit(data);
|
||||||
|
|
||||||
|
expect(comp.search.emit).toHaveBeenCalledWith(event);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,61 @@
|
|||||||
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
import { FormBuilder } from '@angular/forms';
|
||||||
|
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
import { SearchEvent } from '../eperson-group-list.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component used to show a search box for groups.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-group-search-box',
|
||||||
|
templateUrl: './group-search-box.component.html',
|
||||||
|
})
|
||||||
|
export class GroupSearchBoxComponent {
|
||||||
|
|
||||||
|
labelPrefix = 'admin.access-control.groups.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The search form
|
||||||
|
*/
|
||||||
|
searchForm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions
|
||||||
|
*/
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when a search is triggred.
|
||||||
|
* Event's payload is a SearchEvent.
|
||||||
|
*/
|
||||||
|
@Output() search: EventEmitter<SearchEvent> = new EventEmitter<SearchEvent>();
|
||||||
|
|
||||||
|
constructor(private formBuilder: FormBuilder) {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the search form
|
||||||
|
*/
|
||||||
|
reset() {
|
||||||
|
this.searchForm = this.formBuilder.group(({
|
||||||
|
query: '',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emit a new search event
|
||||||
|
* @param data Form data
|
||||||
|
*/
|
||||||
|
submit(data: any) {
|
||||||
|
const event: SearchEvent = {
|
||||||
|
scope: '',
|
||||||
|
query: data.query
|
||||||
|
}
|
||||||
|
this.search.emit(event)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,47 @@
|
|||||||
|
<div>
|
||||||
|
<ds-form *ngIf="formModel"
|
||||||
|
#formRef="formComponent"
|
||||||
|
[formId]="formId"
|
||||||
|
[formModel]="formModel"
|
||||||
|
[displaySubmit]="false"></ds-form>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<label for="ResourcePolicyObject">{{'resource-policies.form.eperson-group-list.label' | translate}}</label>
|
||||||
|
<input id="ResourcePolicyObject" class="form-control mb-3" type="text" readonly [value]="getResourcePolicyTargetName()">
|
||||||
|
<ngb-tabset *ngIf="canSetGrant()" type="pills">
|
||||||
|
<ngb-tab [title]="'resource-policies.form.eperson-group-list.tab.eperson' | translate">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
<ds-eperson-group-list (select)="updateObjectSelected($event, true)"></ds-eperson-group-list>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
<ngb-tab [title]="'resource-policies.form.eperson-group-list.tab.group' | translate">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
<ds-eperson-group-list [isListOfEPerson]="false"
|
||||||
|
(select)="updateObjectSelected($event, false)"></ds-eperson-group-list>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
</ngb-tabset>
|
||||||
|
<div>
|
||||||
|
<hr>
|
||||||
|
<div class="form-group row">
|
||||||
|
|
||||||
|
<div class="col text-right">
|
||||||
|
<button type="reset"
|
||||||
|
class="btn btn-default"
|
||||||
|
[disabled]="(isProcessing | async)"
|
||||||
|
(click)="onReset()">{{'form.cancel' | translate}}</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
[disabled]="!(isFormValid() | async) || (isProcessing | async)"
|
||||||
|
(click)="onSubmit()">
|
||||||
|
<span *ngIf="(isProcessing | async)">
|
||||||
|
<i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="!(isProcessing | async)">
|
||||||
|
{{'form.submit' | translate}}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,427 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { BrowserModule, By } from '@angular/platform-browser';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { delay } from 'rxjs/operators';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../../testing/utils.test';
|
||||||
|
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
|
import { getMockRequestService } from '../../mocks/request.service.mock';
|
||||||
|
import { PolicyType } from '../../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../../core/resource-policy/models/action-type.model';
|
||||||
|
import { GroupMock } from '../../testing/group-mock';
|
||||||
|
import { ResourcePolicyEvent, ResourcePolicyFormComponent } from './resource-policy-form.component';
|
||||||
|
import { FormService } from '../../form/form.service';
|
||||||
|
import { getMockFormService } from '../../mocks/form-service.mock';
|
||||||
|
import { FormBuilderService } from '../../form/builder/form-builder.service';
|
||||||
|
import { EpersonGroupListComponent } from './eperson-group-list/eperson-group-list.component';
|
||||||
|
import { FormComponent } from '../../form/form.component';
|
||||||
|
import { stringToNgbDateStruct } from '../../date.util';
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
|
||||||
|
import { EPersonMock } from '../../testing/eperson.mock';
|
||||||
|
|
||||||
|
export const mockResourcePolicyFormData = {
|
||||||
|
name: [
|
||||||
|
{
|
||||||
|
value: 'name',
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: 'name',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
description: [
|
||||||
|
{
|
||||||
|
value: 'description',
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: 'description',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
policyType: [
|
||||||
|
{
|
||||||
|
value: 'TYPE_WORKFLOW',
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: 'TYPE_WORKFLOW',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
action: [
|
||||||
|
{
|
||||||
|
value: 'WRITE',
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: 'WRITE',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
date: {
|
||||||
|
start: [
|
||||||
|
{
|
||||||
|
value: { year: '2019', month: '04', day: '14' },
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: '2019-04-14',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
end: [
|
||||||
|
{
|
||||||
|
value: { year: '2020', month: '04', day: '14' },
|
||||||
|
language: null,
|
||||||
|
authority: null,
|
||||||
|
display: '2020-04-14',
|
||||||
|
confidence: -1,
|
||||||
|
place: 0,
|
||||||
|
otherInformation: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const submittedResourcePolicy = Object.assign(new ResourcePolicy(), {
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
policyType: PolicyType.TYPE_WORKFLOW,
|
||||||
|
action: ActionType.WRITE,
|
||||||
|
startDate: '2019-04-14T00:00:00Z',
|
||||||
|
endDate: '2020-04-14T00:00:00Z',
|
||||||
|
type: RESOURCE_POLICY
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ResourcePolicyFormComponent test suite', () => {
|
||||||
|
let comp: ResourcePolicyFormComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ResourcePolicyFormComponent>;
|
||||||
|
let de;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: '2019-04-14',
|
||||||
|
endDate: '2020-04-14',
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject({})),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject(GroupMock))
|
||||||
|
};
|
||||||
|
|
||||||
|
const epersonService = jasmine.createSpyObj('epersonService', {
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
findAll: jasmine.createSpy('findAll')
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupService = jasmine.createSpyObj('groupService', {
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
findAll: jasmine.createSpy('findAll')
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
FormComponent,
|
||||||
|
EpersonGroupListComponent,
|
||||||
|
ResourcePolicyFormComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: EPersonDataService, useValue: epersonService },
|
||||||
|
{ provide: FormService, useValue: getMockFormService() },
|
||||||
|
{ provide: GroupDataService, useValue: groupService },
|
||||||
|
{ provide: RequestService, useValue: getMockRequestService() },
|
||||||
|
FormBuilderService,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
ResourcePolicyFormComponent
|
||||||
|
],
|
||||||
|
schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-resource-policy-form [resourcePolicy]="resourcePolicy" [isProcessing]="isProcessing"></ds-resource-policy-form>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ResourcePolicyFormComponent', inject([ResourcePolicyFormComponent], (app: ResourcePolicyFormComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when resource policy is not provided', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyFormComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
comp.isProcessing = observableOf(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init form model properly', () => {
|
||||||
|
spyOn(compAsAny, 'isFormValid').and.returnValue(observableOf(false));
|
||||||
|
spyOn(compAsAny, 'initModelsValue').and.callThrough();
|
||||||
|
spyOn(compAsAny, 'buildResourcePolicyForm').and.callThrough();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.buildResourcePolicyForm).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.initModelsValue).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.formModel.length).toBe(5);
|
||||||
|
expect(compAsAny.subs.length).toBe(0);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should can set grant', () => {
|
||||||
|
expect(comp.canSetGrant()).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not have a target name', () => {
|
||||||
|
expect(comp.getResourcePolicyTargetName()).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit reset event', () => {
|
||||||
|
spyOn(compAsAny.reset, 'emit');
|
||||||
|
comp.onReset();
|
||||||
|
expect(compAsAny.reset.emit).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update resource policy grant object properly', () => {
|
||||||
|
comp.updateObjectSelected(EPersonMock, true);
|
||||||
|
|
||||||
|
expect(comp.resourcePolicyGrant).toEqual(EPersonMock);
|
||||||
|
expect(comp.resourcePolicyGrantType).toBe('eperson');
|
||||||
|
|
||||||
|
comp.updateObjectSelected(GroupMock, false);
|
||||||
|
|
||||||
|
expect(comp.resourcePolicyGrant).toEqual(GroupMock);
|
||||||
|
expect(comp.resourcePolicyGrantType).toBe('group');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when resource policy is provided', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// initTestScheduler();
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyFormComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
comp.resourcePolicy = resourcePolicy;
|
||||||
|
comp.isProcessing = observableOf(false);
|
||||||
|
compAsAny.ePersonService.findByHref.and.returnValue(
|
||||||
|
observableOf(createSuccessfulRemoteDataObject({})).pipe(delay(100))
|
||||||
|
);
|
||||||
|
compAsAny.groupService.findByHref.and.returnValue(observableOf(createSuccessfulRemoteDataObject(GroupMock)));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init form model properly', () => {
|
||||||
|
spyOn(compAsAny, 'isFormValid').and.returnValue(observableOf(false));
|
||||||
|
spyOn(compAsAny, 'initModelsValue').and.callThrough();
|
||||||
|
spyOn(compAsAny, 'buildResourcePolicyForm').and.callThrough();
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(compAsAny.buildResourcePolicyForm).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.initModelsValue).toHaveBeenCalled();
|
||||||
|
expect(compAsAny.formModel.length).toBe(5);
|
||||||
|
expect(compAsAny.subs.length).toBe(1);
|
||||||
|
expect(compAsAny.formModel[2].value).toBe('TYPE_SUBMISSION');
|
||||||
|
expect(compAsAny.formModel[3].value).toBe('READ');
|
||||||
|
expect(compAsAny.formModel[4].get(0).value).toEqual(stringToNgbDateStruct('2019-04-14'));
|
||||||
|
expect(compAsAny.formModel[4].get(1).value).toEqual(stringToNgbDateStruct('2020-04-14'));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init resourcePolicyGrant properly', () => {
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.ngOnInit());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePolicyGrant).toEqual(GroupMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not can set grant', () => {
|
||||||
|
expect(comp.canSetGrant()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a target name', () => {
|
||||||
|
compAsAny.resourcePolicyGrant = GroupMock;
|
||||||
|
|
||||||
|
expect(comp.getResourcePolicyTargetName()).toBe('testgroupname');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when form is valid', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyFormComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = comp;
|
||||||
|
comp.resourcePolicy = resourcePolicy;
|
||||||
|
comp.isProcessing = observableOf(false);
|
||||||
|
compAsAny.ePersonService.findByHref.and.returnValue(
|
||||||
|
observableOf(createSuccessfulRemoteDataObject({})).pipe(delay(100))
|
||||||
|
);
|
||||||
|
compAsAny.groupService.findByHref.and.returnValue(observableOf(createSuccessfulRemoteDataObject(GroupMock)));
|
||||||
|
compAsAny.formService.isValid.and.returnValue(observableOf(true));
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
comp.resourcePolicyGrant = GroupMock;
|
||||||
|
comp.resourcePolicyGrantType = 'group';
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not have submit button disabled when submission is valid', () => {
|
||||||
|
|
||||||
|
const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary'));
|
||||||
|
|
||||||
|
expect(depositBtn.nativeElement.disabled).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit submit event', () => {
|
||||||
|
spyOn(compAsAny.submit, 'emit');
|
||||||
|
spyOn(compAsAny, 'createResourcePolicyByFormData').and.callThrough();
|
||||||
|
compAsAny.formService.getFormData.and.returnValue(observableOf(mockResourcePolicyFormData));
|
||||||
|
const eventPayload: ResourcePolicyEvent = Object.create({});
|
||||||
|
eventPayload.object = submittedResourcePolicy;
|
||||||
|
eventPayload.target = {
|
||||||
|
type: 'group',
|
||||||
|
uuid: GroupMock.id
|
||||||
|
};
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.onSubmit());
|
||||||
|
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.submit.emit).toHaveBeenCalledWith(eventPayload);
|
||||||
|
expect(compAsAny.createResourcePolicyByFormData).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when form is not valid', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ResourcePolicyFormComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = comp;
|
||||||
|
comp.resourcePolicy = resourcePolicy;
|
||||||
|
comp.isProcessing = observableOf(false);
|
||||||
|
compAsAny.ePersonService.findByHref.and.returnValue(
|
||||||
|
observableOf(createSuccessfulRemoteDataObject({})).pipe(delay(100))
|
||||||
|
);
|
||||||
|
compAsAny.groupService.findByHref.and.returnValue(observableOf(createSuccessfulRemoteDataObject(GroupMock)));
|
||||||
|
compAsAny.formService.isValid.and.returnValue(observableOf(false));
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have submit button disabled when submission is valid', () => {
|
||||||
|
|
||||||
|
const depositBtn: any = fixture.debugElement.query(By.css('.btn-primary'));
|
||||||
|
|
||||||
|
expect(depositBtn.nativeElement.disabled).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
resourcePolicy = null;
|
||||||
|
isProcessing = observableOf(false);
|
||||||
|
}
|
@@ -0,0 +1,317 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
|
|
||||||
|
import { Observable, of as observableOf, race as observableRace } from 'rxjs';
|
||||||
|
import { filter, map, take } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
DynamicDatePickerModel,
|
||||||
|
DynamicFormControlModel,
|
||||||
|
DynamicFormGroupModel,
|
||||||
|
DynamicSelectModel
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { DsDynamicInputModel } from '../../form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||||
|
import {
|
||||||
|
RESOURCE_POLICY_FORM_ACTION_TYPE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_DATE_GROUP_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_DATE_GROUP_LAYOUT,
|
||||||
|
RESOURCE_POLICY_FORM_DESCRIPTION_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_END_DATE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_END_DATE_LAYOUT,
|
||||||
|
RESOURCE_POLICY_FORM_NAME_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_POLICY_TYPE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_START_DATE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_START_DATE_LAYOUT
|
||||||
|
} from './resource-policy-form.model';
|
||||||
|
import { DsDynamicTextAreaModel } from '../../form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { hasValue, isEmpty, isNotEmpty } from '../../empty.util';
|
||||||
|
import { FormService } from '../../form/form.service';
|
||||||
|
import { RESOURCE_POLICY } from '../../../core/resource-policy/models/resource-policy.resource-type';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
import { dateToISOFormat, stringToNgbDateStruct } from '../../date.util';
|
||||||
|
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||||
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
|
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
|
|
||||||
|
export interface ResourcePolicyEvent {
|
||||||
|
object: ResourcePolicy,
|
||||||
|
target: {
|
||||||
|
type: string,
|
||||||
|
uuid: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-resource-policy-form',
|
||||||
|
templateUrl: './resource-policy-form.component.html',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that show form for adding/editing a resource policy
|
||||||
|
*/
|
||||||
|
export class ResourcePolicyFormComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If given contains the resource policy to edit
|
||||||
|
* @type {ResourcePolicy}
|
||||||
|
*/
|
||||||
|
@Input() resourcePolicy: ResourcePolicy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if form submit operation is processing
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
@Input() isProcessing: Observable<boolean> = observableOf(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when form is canceled.
|
||||||
|
* Event's payload is empty.
|
||||||
|
*/
|
||||||
|
@Output() reset: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when form is submitted.
|
||||||
|
* Event's payload equals to a new ResourcePolicy.
|
||||||
|
*/
|
||||||
|
@Output() submit: EventEmitter<ResourcePolicyEvent> = new EventEmitter<ResourcePolicyEvent>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form id
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public formId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The form model
|
||||||
|
* @type {DynamicFormControlModel[]}
|
||||||
|
*/
|
||||||
|
public formModel: DynamicFormControlModel[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The eperson or group that will be grant of the permission
|
||||||
|
* @type {DSpaceObject}
|
||||||
|
*/
|
||||||
|
public resourcePolicyGrant: DSpaceObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of the object that will be grant of the permission. It could be 'eperson' or 'group'
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public resourcePolicyGrantType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if component is active
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
private isActive: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {DSONameService} dsoNameService
|
||||||
|
* @param {EPersonDataService} ePersonService
|
||||||
|
* @param {FormService} formService
|
||||||
|
* @param {GroupDataService} groupService
|
||||||
|
* @param {RequestService} requestService
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
private ePersonService: EPersonDataService,
|
||||||
|
private formService: FormService,
|
||||||
|
private groupService: GroupDataService,
|
||||||
|
private requestService: RequestService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component, setting up the form model
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.isActive = true;
|
||||||
|
this.formId = this.formService.getUniqueId('resource-policy-form');
|
||||||
|
this.formModel = this.buildResourcePolicyForm();
|
||||||
|
|
||||||
|
if (!this.canSetGrant()) {
|
||||||
|
this.requestService.removeByHrefSubstring(this.resourcePolicy._links.eperson.href);
|
||||||
|
this.requestService.removeByHrefSubstring(this.resourcePolicy._links.group.href);
|
||||||
|
const epersonRD$ = this.ePersonService.findByHref(this.resourcePolicy._links.eperson.href).pipe(
|
||||||
|
getSucceededRemoteData()
|
||||||
|
);
|
||||||
|
const groupRD$ = this.groupService.findByHref(this.resourcePolicy._links.group.href).pipe(
|
||||||
|
getSucceededRemoteData()
|
||||||
|
);
|
||||||
|
const dsoRD$: Observable<RemoteData<DSpaceObject>> = observableRace(epersonRD$, groupRD$);
|
||||||
|
this.subs.push(
|
||||||
|
dsoRD$.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
).subscribe((dsoRD: RemoteData<DSpaceObject>) => {
|
||||||
|
this.resourcePolicyGrant = dsoRD.payload;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to check if the form status is valid or not
|
||||||
|
*
|
||||||
|
* @return Observable that emits the form status
|
||||||
|
*/
|
||||||
|
isFormValid(): Observable<boolean> {
|
||||||
|
return this.formService.isValid(this.formId).pipe(
|
||||||
|
map((isValid: boolean) => isValid && isNotEmpty(this.resourcePolicyGrant))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the form model
|
||||||
|
*
|
||||||
|
* @return the form models
|
||||||
|
*/
|
||||||
|
private buildResourcePolicyForm(): DynamicFormControlModel[] {
|
||||||
|
const formModel: DynamicFormControlModel[] = [];
|
||||||
|
// TODO to be removed when https://jira.lyrasis.org/browse/DS-4477 will be implemented
|
||||||
|
const policyTypeConf = Object.assign({}, RESOURCE_POLICY_FORM_POLICY_TYPE_CONFIG, {
|
||||||
|
disabled: isNotEmpty(this.resourcePolicy)
|
||||||
|
});
|
||||||
|
// TODO to be removed when https://jira.lyrasis.org/browse/DS-4477 will be implemented
|
||||||
|
const actionConf = Object.assign({}, RESOURCE_POLICY_FORM_ACTION_TYPE_CONFIG, {
|
||||||
|
disabled: isNotEmpty(this.resourcePolicy)
|
||||||
|
});
|
||||||
|
formModel.push(
|
||||||
|
new DsDynamicInputModel(RESOURCE_POLICY_FORM_NAME_CONFIG),
|
||||||
|
new DsDynamicTextAreaModel(RESOURCE_POLICY_FORM_DESCRIPTION_CONFIG),
|
||||||
|
new DynamicSelectModel(policyTypeConf),
|
||||||
|
new DynamicSelectModel(actionConf)
|
||||||
|
);
|
||||||
|
|
||||||
|
const startDateModel = new DynamicDatePickerModel(
|
||||||
|
RESOURCE_POLICY_FORM_START_DATE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_START_DATE_LAYOUT
|
||||||
|
);
|
||||||
|
const endDateModel = new DynamicDatePickerModel(
|
||||||
|
RESOURCE_POLICY_FORM_END_DATE_CONFIG,
|
||||||
|
RESOURCE_POLICY_FORM_END_DATE_LAYOUT
|
||||||
|
);
|
||||||
|
const dateGroupConfig = Object.assign({}, RESOURCE_POLICY_FORM_DATE_GROUP_CONFIG, { group: [] });
|
||||||
|
dateGroupConfig.group.push(startDateModel, endDateModel);
|
||||||
|
formModel.push(new DynamicFormGroupModel(dateGroupConfig, RESOURCE_POLICY_FORM_DATE_GROUP_LAYOUT));
|
||||||
|
|
||||||
|
this.initModelsValue(formModel);
|
||||||
|
return formModel
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setting up the form models value
|
||||||
|
*
|
||||||
|
* @return the form models
|
||||||
|
*/
|
||||||
|
initModelsValue(formModel: DynamicFormControlModel[]): DynamicFormControlModel[] {
|
||||||
|
if (this.resourcePolicy) {
|
||||||
|
formModel.forEach((model: any) => {
|
||||||
|
if (model.id === 'date') {
|
||||||
|
if (hasValue(this.resourcePolicy.startDate)) {
|
||||||
|
model.get(0).valueUpdates.next(stringToNgbDateStruct(this.resourcePolicy.startDate));
|
||||||
|
}
|
||||||
|
if (hasValue(this.resourcePolicy.endDate)) {
|
||||||
|
model.get(1).valueUpdates.next(stringToNgbDateStruct(this.resourcePolicy.endDate));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (this.resourcePolicy.hasOwnProperty(model.id) && this.resourcePolicy[model.id]) {
|
||||||
|
model.valueUpdates.next(this.resourcePolicy[model.id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return formModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing If is possible to set policy grant
|
||||||
|
*
|
||||||
|
* @return true if is possible, false otherwise
|
||||||
|
*/
|
||||||
|
canSetGrant(): boolean {
|
||||||
|
return isEmpty(this.resourcePolicy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the name of the eperson or group that will be grant of the permission
|
||||||
|
*
|
||||||
|
* @return the object name
|
||||||
|
*/
|
||||||
|
getResourcePolicyTargetName(): string {
|
||||||
|
return isNotEmpty(this.resourcePolicyGrant) ? this.dsoNameService.getName(this.resourcePolicyGrant) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update reference to the eperson or group that will be grant of the permission
|
||||||
|
*/
|
||||||
|
updateObjectSelected(object: DSpaceObject, isEPerson: boolean): void {
|
||||||
|
this.resourcePolicyGrant = object;
|
||||||
|
this.resourcePolicyGrantType = isEPerson ? 'eperson' : 'group';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on reset
|
||||||
|
* Emit a new reset Event
|
||||||
|
*/
|
||||||
|
onReset(): void {
|
||||||
|
this.reset.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on submit.
|
||||||
|
* Emit a new submit Event whether the form is valid
|
||||||
|
*/
|
||||||
|
onSubmit(): void {
|
||||||
|
this.formService.getFormData(this.formId).pipe(take(1))
|
||||||
|
.subscribe((data) => {
|
||||||
|
const eventPayload: ResourcePolicyEvent = Object.create({});
|
||||||
|
eventPayload.object = this.createResourcePolicyByFormData(data);
|
||||||
|
eventPayload.target = {
|
||||||
|
type: this.resourcePolicyGrantType,
|
||||||
|
uuid: this.resourcePolicyGrant.id
|
||||||
|
};
|
||||||
|
this.submit.emit(eventPayload);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create e new ResourcePolicy by form data
|
||||||
|
*
|
||||||
|
* @return the new ResourcePolicy object
|
||||||
|
*/
|
||||||
|
createResourcePolicyByFormData(data): ResourcePolicy {
|
||||||
|
const resourcePolicy = new ResourcePolicy();
|
||||||
|
resourcePolicy.name = (data.name) ? data.name[0].value : null;
|
||||||
|
resourcePolicy.description = (data.description) ? data.description[0].value : null;
|
||||||
|
resourcePolicy.policyType = (data.policyType) ? data.policyType[0].value : null;
|
||||||
|
resourcePolicy.action = (data.action) ? data.action[0].value : null;
|
||||||
|
resourcePolicy.startDate = (data.date && data.date.start) ? dateToISOFormat(data.date.start[0].value) : null;
|
||||||
|
resourcePolicy.endDate = (data.date && data.date.end) ? dateToISOFormat(data.date.end[0].value) : null;
|
||||||
|
resourcePolicy.type = RESOURCE_POLICY;
|
||||||
|
|
||||||
|
return resourcePolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isActive = false;
|
||||||
|
this.formModel = null;
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe())
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,154 @@
|
|||||||
|
import {
|
||||||
|
DynamicDatePickerModelConfig,
|
||||||
|
DynamicFormControlLayout,
|
||||||
|
DynamicFormGroupModelConfig,
|
||||||
|
DynamicFormOptionConfig,
|
||||||
|
DynamicSelectModelConfig,
|
||||||
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
|
import { DsDynamicInputModelConfig } from '../../form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||||
|
import { DsDynamicTextAreaModelConfig } from '../../form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model';
|
||||||
|
import { PolicyType } from '../../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../../core/resource-policy/models/action-type.model';
|
||||||
|
|
||||||
|
const policyTypeList: Array<DynamicFormOptionConfig<any>> = [
|
||||||
|
{
|
||||||
|
label: PolicyType.TYPE_SUBMISSION,
|
||||||
|
value: PolicyType.TYPE_SUBMISSION
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: PolicyType.TYPE_WORKFLOW,
|
||||||
|
value: PolicyType.TYPE_WORKFLOW
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: PolicyType.TYPE_INHERITED,
|
||||||
|
value: PolicyType.TYPE_INHERITED
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: PolicyType.TYPE_CUSTOM,
|
||||||
|
value: PolicyType.TYPE_CUSTOM
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const policyActionList: Array<DynamicFormOptionConfig<any>> = [
|
||||||
|
{
|
||||||
|
label: ActionType.READ.toString(),
|
||||||
|
value: ActionType.READ
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.WRITE.toString(),
|
||||||
|
value: ActionType.WRITE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.REMOVE.toString(),
|
||||||
|
value: ActionType.REMOVE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.ADMIN.toString(),
|
||||||
|
value: ActionType.ADMIN
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.DELETE.toString(),
|
||||||
|
value: ActionType.DELETE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.WITHDRAWN_READ.toString(),
|
||||||
|
value: ActionType.WITHDRAWN_READ
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.DEFAULT_BITSTREAM_READ.toString(),
|
||||||
|
value: ActionType.DEFAULT_BITSTREAM_READ
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: ActionType.DEFAULT_ITEM_READ.toString(),
|
||||||
|
value: ActionType.DEFAULT_ITEM_READ
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_NAME_CONFIG: DsDynamicInputModelConfig = {
|
||||||
|
id: 'name',
|
||||||
|
label: 'resource-policies.form.name.label',
|
||||||
|
metadataFields: [],
|
||||||
|
repeatable: false,
|
||||||
|
submissionId: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_DESCRIPTION_CONFIG: DsDynamicTextAreaModelConfig = {
|
||||||
|
id: 'description',
|
||||||
|
label: 'resource-policies.form.description.label',
|
||||||
|
metadataFields: [],
|
||||||
|
repeatable: false,
|
||||||
|
rows: 10,
|
||||||
|
submissionId: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_POLICY_TYPE_CONFIG: DynamicSelectModelConfig<any> = {
|
||||||
|
id: 'policyType',
|
||||||
|
label: 'resource-policies.form.policy-type.label',
|
||||||
|
options: policyTypeList,
|
||||||
|
required: true,
|
||||||
|
validators: {
|
||||||
|
required: null
|
||||||
|
},
|
||||||
|
errorMessages: {
|
||||||
|
required: 'resource-policies.form.policy-type.required'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_ACTION_TYPE_CONFIG: DynamicSelectModelConfig<any> = {
|
||||||
|
id: 'action',
|
||||||
|
label: 'resource-policies.form.action-type.label',
|
||||||
|
options: policyActionList,
|
||||||
|
required: true,
|
||||||
|
validators: {
|
||||||
|
required: null
|
||||||
|
},
|
||||||
|
errorMessages: {
|
||||||
|
required: 'resource-policies.form.action-type.required'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_DATE_GROUP_CONFIG: DynamicFormGroupModelConfig = {
|
||||||
|
id: 'date',
|
||||||
|
group: []
|
||||||
|
};
|
||||||
|
export const RESOURCE_POLICY_FORM_DATE_GROUP_LAYOUT: DynamicFormControlLayout = {
|
||||||
|
element: {
|
||||||
|
control: 'form-row',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_START_DATE_CONFIG: DynamicDatePickerModelConfig = {
|
||||||
|
id: 'start',
|
||||||
|
label: 'resource-policies.form.date.start.label',
|
||||||
|
placeholder: 'resource-policies.form.date.start.label',
|
||||||
|
inline: false,
|
||||||
|
toggleIcon: 'far fa-calendar-alt'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_START_DATE_LAYOUT: DynamicFormControlLayout = {
|
||||||
|
element: {
|
||||||
|
container: 'p-0',
|
||||||
|
label: 'col-form-label'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
host: 'col-md-6'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RESOURCE_POLICY_FORM_END_DATE_CONFIG: DynamicDatePickerModelConfig = {
|
||||||
|
id: 'end',
|
||||||
|
label: 'resource-policies.form.date.end.label',
|
||||||
|
placeholder: 'resource-policies.form.date.end.label',
|
||||||
|
inline: false,
|
||||||
|
toggleIcon: 'far fa-calendar-alt'
|
||||||
|
};
|
||||||
|
export const RESOURCE_POLICY_FORM_END_DATE_LAYOUT: DynamicFormControlLayout = {
|
||||||
|
element: {
|
||||||
|
container: 'p-0',
|
||||||
|
label: 'col-form-label'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
host: 'col-md-6'
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,53 @@
|
|||||||
|
import { Injectable, Injector } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { find } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { getDataServiceFor } from '../../../core/cache/builders/build-decorators';
|
||||||
|
import { ResourceType } from '../../../core/shared/resource-type';
|
||||||
|
import { DataService } from '../../../core/data/data.service';
|
||||||
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { hasValue, isEmpty } from '../../empty.util';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a resolver that requests a specific item before the route is activated
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ResourcePolicyTargetResolver implements Resolve<RemoteData<DSpaceObject>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The data service used to make request.
|
||||||
|
*/
|
||||||
|
private dataService: DataService<DSpaceObject>;
|
||||||
|
|
||||||
|
constructor(private parentInjector: Injector, private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for resolving an item based on the parameters in the current route
|
||||||
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
|
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||||
|
* or an error if something went wrong
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<DSpaceObject>> {
|
||||||
|
const targetType = route.queryParamMap.get('targetType');
|
||||||
|
const policyTargetId = route.queryParamMap.get('policyTargetId');
|
||||||
|
|
||||||
|
if (isEmpty(targetType) || isEmpty(policyTargetId)) {
|
||||||
|
this.router.navigateByUrl('/404', { skipLocationChange: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = getDataServiceFor(new ResourceType(targetType));
|
||||||
|
this.dataService = Injector.create({
|
||||||
|
providers: [],
|
||||||
|
parent: this.parentInjector
|
||||||
|
}).get(provider);
|
||||||
|
|
||||||
|
return this.dataService.findById(policyTargetId).pipe(
|
||||||
|
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,40 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { find } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { hasValue, isEmpty } from '../../empty.util';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
|
import { followLink } from '../../utils/follow-link-config.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a resolver that requests a specific item before the route is activated
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ResourcePolicyResolver implements Resolve<RemoteData<ResourcePolicy>> {
|
||||||
|
|
||||||
|
constructor(private resourcePolicyService: ResourcePolicyService, private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for resolving an item based on the parameters in the current route
|
||||||
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
|
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||||
|
* or an error if something went wrong
|
||||||
|
*/
|
||||||
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<ResourcePolicy>> {
|
||||||
|
const policyId = route.queryParamMap.get('policyId');
|
||||||
|
|
||||||
|
if (isEmpty(policyId)) {
|
||||||
|
this.router.navigateByUrl('/404', { skipLocationChange: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.resourcePolicyService.findById(policyId, followLink('eperson'), followLink('group')).pipe(
|
||||||
|
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,99 @@
|
|||||||
|
<div *ngIf="(getResourcePolicies() | async)?.length > 0" class="table-responsive">
|
||||||
|
<table class="table table-striped table-bordered table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="10">
|
||||||
|
<div class="d-flex justify-content-between align-items-center m-0">
|
||||||
|
{{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} {{resourceUUID}}
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-danger float-right ml-1"
|
||||||
|
[disabled]="(!(canDelete() | async)) || (isProcessingDelete() | async)"
|
||||||
|
[title]="'resource-policies.delete.btn.title' | translate"
|
||||||
|
(click)="deleteSelectedResourcePolicies()">
|
||||||
|
<span *ngIf="(isProcessingDelete() | async)">
|
||||||
|
<i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="!(isProcessingDelete() | async)">
|
||||||
|
<i class='fas fa-trash-alt fa-fw'></i>
|
||||||
|
{{'resource-policies.delete.btn' | translate}}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-success float-right"
|
||||||
|
[disabled]="(isProcessingDelete() | async)"
|
||||||
|
[title]="'resource-policies.add.for.' + resourceType | translate"
|
||||||
|
(click)="redirectToResourcePolicyCreatePage()">
|
||||||
|
<i class='fas fa-plus'></i>
|
||||||
|
{{'resource-policies.add.button' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="text-center">
|
||||||
|
<th>
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="custom-control-input"
|
||||||
|
[id]="'selectAll_' + resourceUUID"
|
||||||
|
(change)="selectAllCheckbox($event)">
|
||||||
|
<label class="custom-control-label" [for]="'selectAll_' + resourceUUID"></label>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th>{{'resource-policies.table.headers.id' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.name' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.policyType' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.action' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.eperson' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.group' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.date.start' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.date.end' | translate}}</th>
|
||||||
|
<th>{{'resource-policies.table.headers.edit' | translate}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let entry of (getResourcePolicies() | async); trackById">
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="custom-control-input"
|
||||||
|
[id]="entry.id"
|
||||||
|
[ngModel]="entry.checked"
|
||||||
|
(ngModelChange)="selectCheckbox(entry, $event)">
|
||||||
|
<label class="custom-control-label" [for]="entry.id"></label>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<th scope="row">
|
||||||
|
{{entry.id}}
|
||||||
|
</th>
|
||||||
|
<td>{{entry.policy.name}}</td>
|
||||||
|
<td>{{entry.policy.policyType}}</td>
|
||||||
|
<td>{{entry.policy.action}}</td>
|
||||||
|
<td *ngIf="(hasEPerson(entry.policy) | async)">
|
||||||
|
{{getEPersonName(entry.policy) | async}}
|
||||||
|
</td>
|
||||||
|
<td *ngIf="!(hasEPerson(entry.policy) | async)"></td>
|
||||||
|
<td *ngIf="(hasGroup(entry.policy) | async)">
|
||||||
|
{{getGroupName(entry.policy) | async}}
|
||||||
|
</td>
|
||||||
|
<td *ngIf="!(hasGroup(entry.policy) | async)"></td>
|
||||||
|
<td>{{formatDate(entry.policy.startDate)}}</td>
|
||||||
|
<td>{{formatDate(entry.policy.endDate)}}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
|
||||||
|
<div class="btn-group edit-field">
|
||||||
|
<button class="btn btn-outline-primary btn-sm"
|
||||||
|
[title]="'resource-policies.table.headers.edit.policy' | translate"
|
||||||
|
(click)="redirectToResourcePolicyEditPage(entry.policy)">
|
||||||
|
<i class="fas fa-edit fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(hasGroup(entry.policy) | async)" class="btn btn-outline-primary btn-sm"
|
||||||
|
[title]="'resource-policies.table.headers.edit.group' | translate"
|
||||||
|
(click)="redirectToGroupEditPage(entry.policy)">
|
||||||
|
<i class="fas fa-users fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
@@ -0,0 +1,3 @@
|
|||||||
|
td .btn-link:focus {
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
@@ -0,0 +1,485 @@
|
|||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||||
|
|
||||||
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
|
import { Bundle } from '../../core/shared/bundle.model';
|
||||||
|
import { createMockRDPaginatedObs } from '../../+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { LinkService } from '../../core/cache/builders/link.service';
|
||||||
|
import { getMockLinkService } from '../mocks/link-service.mock';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../remote-data.utils';
|
||||||
|
import { createTestComponent } from '../testing/utils.test';
|
||||||
|
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
|
||||||
|
import { NotificationsService } from '../notifications/notifications.service';
|
||||||
|
import { NotificationsServiceStub } from '../testing/notifications-service.stub';
|
||||||
|
import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
|
||||||
|
import { getMockResourcePolicyService } from '../mocks/mock-resource-policy-service';
|
||||||
|
import { GroupDataService } from '../../core/eperson/group-data.service';
|
||||||
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
import { getMockRequestService } from '../mocks/request.service.mock';
|
||||||
|
import { RouterStub } from '../testing/router.stub';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
import { ResourcePoliciesComponent } from './resource-policies.component';
|
||||||
|
import { PolicyType } from '../../core/resource-policy/models/policy-type.model';
|
||||||
|
import { ActionType } from '../../core/resource-policy/models/action-type.model';
|
||||||
|
import { EPersonMock } from '../testing/eperson.mock';
|
||||||
|
import { GroupMock } from '../testing/group-mock';
|
||||||
|
|
||||||
|
describe('ResourcePoliciesComponent test suite', () => {
|
||||||
|
let comp: ResourcePoliciesComponent;
|
||||||
|
let compAsAny: any;
|
||||||
|
let fixture: ComponentFixture<ResourcePoliciesComponent>;
|
||||||
|
let de;
|
||||||
|
let routerStub: any;
|
||||||
|
let scheduler: TestScheduler;
|
||||||
|
const notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
const resourcePolicyService: any = getMockResourcePolicyService();
|
||||||
|
const linkService: any = getMockLinkService();
|
||||||
|
|
||||||
|
const resourcePolicy: any = {
|
||||||
|
id: '1',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.READ,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-1',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject({})),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject(GroupMock))
|
||||||
|
};
|
||||||
|
|
||||||
|
const anotherResourcePolicy: any = {
|
||||||
|
id: '2',
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
policyType: PolicyType.TYPE_SUBMISSION,
|
||||||
|
action: ActionType.WRITE,
|
||||||
|
startDate: null,
|
||||||
|
endDate: null,
|
||||||
|
type: 'resourcepolicy',
|
||||||
|
uuid: 'resource-policy-2',
|
||||||
|
_links: {
|
||||||
|
eperson: {
|
||||||
|
href: 'https://rest.api/rest/api/eperson'
|
||||||
|
},
|
||||||
|
group: {
|
||||||
|
href: 'https://rest.api/rest/api/group'
|
||||||
|
},
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/rest/api/resourcepolicies/1'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
eperson: observableOf(createSuccessfulRemoteDataObject(EPersonMock)),
|
||||||
|
group: observableOf(createSuccessfulRemoteDataObject({}))
|
||||||
|
};
|
||||||
|
|
||||||
|
const bitstream1 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream1',
|
||||||
|
uuid: 'bitstream1'
|
||||||
|
});
|
||||||
|
const bitstream2 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream2',
|
||||||
|
uuid: 'bitstream2'
|
||||||
|
});
|
||||||
|
const bitstream3 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream3',
|
||||||
|
uuid: 'bitstream3'
|
||||||
|
});
|
||||||
|
const bitstream4 = Object.assign(new Bitstream(), {
|
||||||
|
id: 'bitstream4',
|
||||||
|
uuid: 'bitstream4'
|
||||||
|
});
|
||||||
|
const bundle1 = Object.assign(new Bundle(), {
|
||||||
|
id: 'bundle1',
|
||||||
|
uuid: 'bundle1',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'bundle1-selflink' }
|
||||||
|
},
|
||||||
|
bitstreams: createMockRDPaginatedObs([bitstream1, bitstream2])
|
||||||
|
});
|
||||||
|
const bundle2 = Object.assign(new Bundle(), {
|
||||||
|
id: 'bundle2',
|
||||||
|
uuid: 'bundle2',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'bundle2-selflink' }
|
||||||
|
},
|
||||||
|
bitstreams: createMockRDPaginatedObs([bitstream3, bitstream4])
|
||||||
|
});
|
||||||
|
|
||||||
|
const item = Object.assign(new Item(), {
|
||||||
|
uuid: 'itemUUID',
|
||||||
|
id: 'itemUUID',
|
||||||
|
_links: {
|
||||||
|
self: { href: 'item-selflink' }
|
||||||
|
},
|
||||||
|
bundles: createMockRDPaginatedObs([bundle1, bundle2])
|
||||||
|
});
|
||||||
|
|
||||||
|
const routeStub = {
|
||||||
|
data: observableOf({
|
||||||
|
item: createSuccessfulRemoteDataObject(item)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const epersonService = jasmine.createSpyObj('epersonService', {
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupService = jasmine.createSpyObj('groupService', {
|
||||||
|
findByHref: jasmine.createSpy('findByHref'),
|
||||||
|
});
|
||||||
|
|
||||||
|
routerStub = Object.assign(new RouterStub(), {
|
||||||
|
url: `url/edit`
|
||||||
|
});
|
||||||
|
|
||||||
|
const getInitEntries = () => {
|
||||||
|
return [
|
||||||
|
Object.assign({}, {
|
||||||
|
id: resourcePolicy.id,
|
||||||
|
policy: resourcePolicy,
|
||||||
|
checked: false
|
||||||
|
}),
|
||||||
|
Object.assign({}, {
|
||||||
|
id: anotherResourcePolicy.id,
|
||||||
|
policy: anotherResourcePolicy,
|
||||||
|
checked: false
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourcePolicySelectedEntries = [
|
||||||
|
{
|
||||||
|
id: resourcePolicy.id,
|
||||||
|
policy: resourcePolicy,
|
||||||
|
checked: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: anotherResourcePolicy.id,
|
||||||
|
policy: anotherResourcePolicy,
|
||||||
|
checked: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const pageInfo = new PageInfo();
|
||||||
|
const array = [resourcePolicy, anotherResourcePolicy];
|
||||||
|
const paginatedList = new PaginatedList(pageInfo, array);
|
||||||
|
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ResourcePoliciesComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
|
{ provide: EPersonDataService, useValue: epersonService },
|
||||||
|
{ provide: GroupDataService, useValue: groupService },
|
||||||
|
{ provide: NotificationsService, useValue: notificationsServiceStub },
|
||||||
|
{ provide: ResourcePolicyService, useValue: resourcePolicyService },
|
||||||
|
{ provide: RequestService, useValue: getMockRequestService() },
|
||||||
|
{ provide: Router, useValue: routerStub },
|
||||||
|
ChangeDetectorRef,
|
||||||
|
ResourcePoliciesComponent
|
||||||
|
], schemas: [
|
||||||
|
NO_ERRORS_SCHEMA
|
||||||
|
]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-resource-policies [resourceUUID]="resourceUUID" [resourceType]="resourceType"></ds-resource-policies>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create ResourcePoliciesComponent', inject([ResourcePoliciesComponent], (app: ResourcePoliciesComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ResourcePoliciesComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
linkService.resolveLink.and.callFake((object, link) => object);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init component properly', () => {
|
||||||
|
spyOn(comp, 'initResourcePolicyLIst');
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(compAsAny.isActive).toBeTruthy();
|
||||||
|
expect(comp.initResourcePolicyLIst).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should init resource policies list properly', () => {
|
||||||
|
const expected = getInitEntries();
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
resourcePolicyService.searchByResource.and.returnValue(hot('a|', {
|
||||||
|
a: paginatedListRD
|
||||||
|
}));
|
||||||
|
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.initResourcePolicyLIst());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(compAsAny.resourcePoliciesEntries$.value).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ResourcePoliciesComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
compAsAny = fixture.componentInstance;
|
||||||
|
linkService.resolveLink.and.callFake((object, link) => object);
|
||||||
|
compAsAny.isActive = true;
|
||||||
|
const initResourcePolicyEntries = getInitEntries();
|
||||||
|
compAsAny.resourcePoliciesEntries$.next(initResourcePolicyEntries);
|
||||||
|
resourcePolicyService.searchByResource.and.returnValue(observableOf({}));
|
||||||
|
spyOn(comp, 'initResourcePolicyLIst').and.callFake(() => ({}));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('canDelete', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const initResourcePolicyEntries = getInitEntries();
|
||||||
|
compAsAny.resourcePoliciesEntries$.next(initResourcePolicyEntries);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
comp = null;
|
||||||
|
compAsAny = null;
|
||||||
|
de = null;
|
||||||
|
fixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when no row is selected', () => {
|
||||||
|
expect(comp.canDelete()).toBeObservable(cold('(a|)', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when al least is selected', () => {
|
||||||
|
const checkbox = fixture.debugElement.query(By.css('table > tbody > tr:nth-child(1) input'));
|
||||||
|
const event = { target: { checked: true } };
|
||||||
|
checkbox.triggerEventHandler('change', event);
|
||||||
|
expect(comp.canDelete()).toBeObservable(cold('(a|)', {
|
||||||
|
a: true
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render a table with a row for each policy', () => {
|
||||||
|
const rows = fixture.debugElement.queryAll(By.css('table > tbody > tr'));
|
||||||
|
expect(rows.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteSelectedResourcePolicies', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
compAsAny.resourcePoliciesEntries$.next(resourcePolicySelectedEntries);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify success when delete is successful', () => {
|
||||||
|
|
||||||
|
resourcePolicyService.delete.and.returnValue(observableOf(true));
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.deleteSelectedResourcePolicies());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(notificationsServiceStub.success).toHaveBeenCalled();
|
||||||
|
expect(comp.initResourcePolicyLIst).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should notify error when delete is not successful', () => {
|
||||||
|
|
||||||
|
resourcePolicyService.delete.and.returnValue(observableOf(false));
|
||||||
|
scheduler = getTestScheduler();
|
||||||
|
scheduler.schedule(() => comp.deleteSelectedResourcePolicies());
|
||||||
|
scheduler.flush();
|
||||||
|
|
||||||
|
expect(notificationsServiceStub.error).toHaveBeenCalled();
|
||||||
|
expect(comp.initResourcePolicyLIst).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get the resource\'s policy list', () => {
|
||||||
|
const initResourcePolicyEntries = getInitEntries();
|
||||||
|
expect(comp.getResourcePolicies()).toBeObservable(cold('a', {
|
||||||
|
a: initResourcePolicyEntries
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasEPerson', () => {
|
||||||
|
it('should true when policy is link to the eperson', () => {
|
||||||
|
|
||||||
|
expect(comp.hasEPerson(anotherResourcePolicy)).toBeObservable(cold('(ab|)', {
|
||||||
|
a: false,
|
||||||
|
b: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should false when policy is not link to the eperson', () => {
|
||||||
|
|
||||||
|
expect(comp.hasEPerson(resourcePolicy)).toBeObservable(cold('(aa|)', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasGroup', () => {
|
||||||
|
it('should true when policy is link to the group', () => {
|
||||||
|
|
||||||
|
expect(comp.hasGroup(resourcePolicy)).toBeObservable(cold('(ab|)', {
|
||||||
|
a: false,
|
||||||
|
b: true
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should false when policy is not link to the group', () => {
|
||||||
|
|
||||||
|
expect(comp.hasGroup(anotherResourcePolicy)).toBeObservable(cold('(aa|)', {
|
||||||
|
a: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getEPersonName', () => {
|
||||||
|
it('should return the eperson name', () => {
|
||||||
|
|
||||||
|
expect(comp.getEPersonName(anotherResourcePolicy)).toBeObservable(cold('(ab|)', {
|
||||||
|
a: '',
|
||||||
|
b: 'User Test'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getGroupName', () => {
|
||||||
|
it('should return the group name', () => {
|
||||||
|
|
||||||
|
expect(comp.getGroupName(resourcePolicy)).toBeObservable(cold('(ab|)', {
|
||||||
|
a: '',
|
||||||
|
b: 'testgroupname'
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format date properly', () => {
|
||||||
|
expect(comp.formatDate('2020-04-14T12:00:00Z')).toBe('2020-04-14');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select All Checkbox', () => {
|
||||||
|
spyOn(comp, 'selectAllCheckbox').and.callThrough();
|
||||||
|
const checkbox = fixture.debugElement.query(By.css('table > thead > tr:nth-child(2) input'));
|
||||||
|
|
||||||
|
const event = { target: { checked: true } };
|
||||||
|
checkbox.triggerEventHandler('change', event);
|
||||||
|
expect(comp.selectAllCheckbox).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should select a Checkbox', () => {
|
||||||
|
spyOn(comp, 'selectCheckbox').and.callThrough();
|
||||||
|
const checkbox = fixture.debugElement.query(By.css('table > tbody > tr:nth-child(1) input'));
|
||||||
|
|
||||||
|
const event = { target: { checked: true } };
|
||||||
|
checkbox.triggerEventHandler('change', event);
|
||||||
|
expect(comp.selectCheckbox).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to create resource policy page', () => {
|
||||||
|
|
||||||
|
comp.redirectToResourcePolicyCreatePage();
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to resource policy edit page', () => {
|
||||||
|
|
||||||
|
comp.redirectToResourcePolicyEditPage(resourcePolicy);
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redirect to resource policy edit page', () => {
|
||||||
|
compAsAny.groupService.findByHref.and.returnValue(observableOf(createSuccessfulRemoteDataObject(GroupMock)));
|
||||||
|
|
||||||
|
comp.redirectToGroupEditPage(resourcePolicy);
|
||||||
|
expect(compAsAny.router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
resourceUUID = 'itemUUID';
|
||||||
|
resourceType = 'item';
|
||||||
|
}
|
345
src/app/shared/resource-policies/resource-policies.component.ts
Normal file
345
src/app/shared/resource-policies/resource-policies.component.ts
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
|
import { BehaviorSubject, from as observableFrom, Observable, Subscription } from 'rxjs';
|
||||||
|
import { concatMap, distinctUntilChanged, filter, map, reduce, scan, startWith, take } from 'rxjs/operators';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
|
||||||
|
import {
|
||||||
|
getFirstSucceededRemoteDataPayload,
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload,
|
||||||
|
getSucceededRemoteData
|
||||||
|
} from '../../core/shared/operators';
|
||||||
|
import { ResourcePolicy } from '../../core/resource-policy/models/resource-policy.model';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { Group } from '../../core/eperson/models/group.model';
|
||||||
|
import { GroupDataService } from '../../core/eperson/group-data.service';
|
||||||
|
import { hasValue, isEmpty, isNotEmpty } from '../empty.util';
|
||||||
|
import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||||
|
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
|
||||||
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
import { NotificationsService } from '../notifications/notifications.service';
|
||||||
|
import { dateToString, stringToNgbDateStruct } from '../date.util';
|
||||||
|
import { followLink } from '../utils/follow-link-config.model';
|
||||||
|
import { ADMIN_MODULE_PATH } from '../../app-routing.module';
|
||||||
|
import { ACCESS_CONTROL_MODULE_PATH } from '../../+admin/admin-routing.module';
|
||||||
|
import { GROUP_EDIT_PATH } from '../../+admin/admin-access-control/admin-access-control-routing.module';
|
||||||
|
|
||||||
|
interface ResourcePolicyCheckboxEntry {
|
||||||
|
id: string;
|
||||||
|
policy: ResourcePolicy;
|
||||||
|
checked: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-resource-policies',
|
||||||
|
styleUrls: ['./resource-policies.component.scss'],
|
||||||
|
templateUrl: './resource-policies.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component that shows the policies for given resource
|
||||||
|
*/
|
||||||
|
export class ResourcePoliciesComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource UUID
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
@Input() public resourceUUID: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type (e.g. 'item', 'bundle' etc) used as key to build automatically translation label
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
@Input() public resourceType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if component is active
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
private isActive: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A boolean representing if a submission delete operation is pending
|
||||||
|
* @type {BehaviorSubject<boolean>}
|
||||||
|
*/
|
||||||
|
private processingDelete$ = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of policies for given resource
|
||||||
|
* @type {BehaviorSubject<ResourcePolicyCheckboxEntry[]>}
|
||||||
|
*/
|
||||||
|
private resourcePoliciesEntries$: BehaviorSubject<ResourcePolicyCheckboxEntry[]> =
|
||||||
|
new BehaviorSubject<ResourcePolicyCheckboxEntry[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {ChangeDetectorRef} cdr
|
||||||
|
* @param {DSONameService} dsoNameService
|
||||||
|
* @param {EPersonDataService} ePersonService
|
||||||
|
* @param {GroupDataService} groupService
|
||||||
|
* @param {NotificationsService} notificationsService
|
||||||
|
* @param {RequestService} requestService
|
||||||
|
* @param {ResourcePolicyService} resourcePolicyService
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
* @param {Router} router
|
||||||
|
* @param {TranslateService} translate
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private cdr: ChangeDetectorRef,
|
||||||
|
private dsoNameService: DSONameService,
|
||||||
|
private ePersonService: EPersonDataService,
|
||||||
|
private groupService: GroupDataService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private requestService: RequestService,
|
||||||
|
private resourcePolicyService: ResourcePolicyService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
private translate: TranslateService
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the component, setting up the resource's policies
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.isActive = true;
|
||||||
|
this.initResourcePolicyLIst();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there are any selected resource's policies to be deleted
|
||||||
|
*
|
||||||
|
* @return {Observable<boolean>}
|
||||||
|
*/
|
||||||
|
canDelete(): Observable<boolean> {
|
||||||
|
return observableFrom(this.resourcePoliciesEntries$.value).pipe(
|
||||||
|
filter((entry: ResourcePolicyCheckboxEntry) => entry.checked),
|
||||||
|
reduce((acc: any, value: any) => [...acc, ...value], []),
|
||||||
|
map((entries: ResourcePolicyCheckboxEntry[]) => isNotEmpty(entries)),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the selected resource's policies
|
||||||
|
*/
|
||||||
|
deleteSelectedResourcePolicies(): void {
|
||||||
|
this.processingDelete$.next(true);
|
||||||
|
const policiesToDelete: ResourcePolicyCheckboxEntry[] = this.resourcePoliciesEntries$.value
|
||||||
|
.filter((entry: ResourcePolicyCheckboxEntry) => entry.checked);
|
||||||
|
this.subs.push(
|
||||||
|
observableFrom(policiesToDelete).pipe(
|
||||||
|
concatMap((entry: ResourcePolicyCheckboxEntry) => this.resourcePolicyService.delete(entry.policy.id)),
|
||||||
|
scan((acc: any, value: any) => [...acc, ...value], []),
|
||||||
|
filter((results: boolean[]) => results.length === policiesToDelete.length),
|
||||||
|
take(1),
|
||||||
|
).subscribe((results: boolean[]) => {
|
||||||
|
const failureResults = results.filter((result: boolean) => !result);
|
||||||
|
if (isEmpty(failureResults)) {
|
||||||
|
this.notificationsService.success(null, this.translate.get('resource-policies.delete.success.content'));
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(null, this.translate.get('resource-policies.delete.failure.content'));
|
||||||
|
}
|
||||||
|
this.initResourcePolicyLIst();
|
||||||
|
this.processingDelete$.next(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a date in simplified format (YYYY-MM-DD).
|
||||||
|
*
|
||||||
|
* @param date
|
||||||
|
* @return a string with formatted date
|
||||||
|
*/
|
||||||
|
formatDate(date: string): string {
|
||||||
|
return isNotEmpty(date) ? dateToString(stringToNgbDateStruct(date)) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the ePerson's name which the given policy is linked to
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
*/
|
||||||
|
getEPersonName(policy: ResourcePolicy): Observable<string> {
|
||||||
|
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
|
||||||
|
// return this.ePersonService.findByHref(policy._links.eperson.href).pipe(
|
||||||
|
return policy.eperson.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||||
|
map((eperson: EPerson) => this.dsoNameService.getName(eperson)),
|
||||||
|
startWith('')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the group's name which the given policy is linked to
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
*/
|
||||||
|
getGroupName(policy: ResourcePolicy): Observable<string> {
|
||||||
|
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
|
||||||
|
// return this.groupService.findByHref(policy._links.group.href).pipe(
|
||||||
|
return policy.group.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||||
|
map((group: Group) => this.dsoNameService.getName(group)),
|
||||||
|
startWith('')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all resource's policies
|
||||||
|
*
|
||||||
|
* @return an observable that emits all resource's policies
|
||||||
|
*/
|
||||||
|
getResourcePolicies(): Observable<ResourcePolicyCheckboxEntry[]> {
|
||||||
|
return this.resourcePoliciesEntries$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given policy is linked to a ePerson
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
* @return an observable that emits true when the policy is linked to a ePerson, false otherwise
|
||||||
|
*/
|
||||||
|
hasEPerson(policy): Observable<boolean> {
|
||||||
|
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
|
||||||
|
// return this.ePersonService.findByHref(policy._links.eperson.href).pipe(
|
||||||
|
return policy.eperson.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((eperson: EPerson) => isNotEmpty(eperson)),
|
||||||
|
startWith(false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given policy is linked to a group
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
* @return an observable that emits true when the policy is linked to a group, false otherwise
|
||||||
|
*/
|
||||||
|
hasGroup(policy): Observable<boolean> {
|
||||||
|
// TODO to be reviewed when https://github.com/DSpace/dspace-angular/issues/644 will be resolved
|
||||||
|
// return this.groupService.findByHref(policy._links.group.href).pipe(
|
||||||
|
return policy.group.pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((group: Group) => isNotEmpty(group)),
|
||||||
|
startWith(false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the resource's policies list
|
||||||
|
*/
|
||||||
|
initResourcePolicyLIst() {
|
||||||
|
this.resourcePolicyService.searchByResource(this.resourceUUID, null,
|
||||||
|
followLink('eperson'), followLink('group')).pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
take(1)
|
||||||
|
).subscribe((result) => {
|
||||||
|
const entries = result.payload.page.map((policy: ResourcePolicy) => ({
|
||||||
|
id: policy.id,
|
||||||
|
policy: policy,
|
||||||
|
checked: false
|
||||||
|
}));
|
||||||
|
this.resourcePoliciesEntries$.next(entries);
|
||||||
|
// Remove cached request
|
||||||
|
this.requestService.removeByHrefSubstring(this.resourceUUID);
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a boolean representing if a delete operation is pending
|
||||||
|
*
|
||||||
|
* @return {Observable<boolean>}
|
||||||
|
*/
|
||||||
|
isProcessingDelete(): Observable<boolean> {
|
||||||
|
return this.processingDelete$.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to resource policy creation page
|
||||||
|
*/
|
||||||
|
redirectToResourcePolicyCreatePage(): void {
|
||||||
|
this.router.navigate([`../create`], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: {
|
||||||
|
policyTargetId: this.resourceUUID,
|
||||||
|
targetType: this.resourceType
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to resource policy editing page
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
*/
|
||||||
|
redirectToResourcePolicyEditPage(policy: ResourcePolicy): void {
|
||||||
|
this.router.navigate([`../edit`], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: {
|
||||||
|
policyId: policy.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redirect to group edit page
|
||||||
|
*
|
||||||
|
* @param policy The resource policy
|
||||||
|
*/
|
||||||
|
redirectToGroupEditPage(policy: ResourcePolicy): void {
|
||||||
|
this.requestService.removeByHrefSubstring(policy._links.group.href);
|
||||||
|
this.subs.push(
|
||||||
|
this.groupService.findByHref(policy._links.group.href).pipe(
|
||||||
|
filter(() => this.isActive),
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((group: Group) => group.id)
|
||||||
|
).subscribe((groupUUID) => {
|
||||||
|
this.router.navigate([ADMIN_MODULE_PATH, ACCESS_CONTROL_MODULE_PATH, GROUP_EDIT_PATH, groupUUID])
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select/unselect all checkbox in the list
|
||||||
|
*/
|
||||||
|
selectAllCheckbox(event: any): void {
|
||||||
|
const checked = event.target.checked;
|
||||||
|
this.resourcePoliciesEntries$.value.forEach((entry: ResourcePolicyCheckboxEntry) => entry.checked = checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select/unselect checkbox
|
||||||
|
*/
|
||||||
|
selectCheckbox(policyEntry: ResourcePolicyCheckboxEntry, checked: boolean) {
|
||||||
|
policyEntry.checked = checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isActive = false;
|
||||||
|
this.resourcePoliciesEntries$ = null;
|
||||||
|
this.subs
|
||||||
|
.filter((subscription) => hasValue(subscription))
|
||||||
|
.forEach((subscription) => subscription.unsubscribe())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -194,6 +194,14 @@ import { ClaimedTaskActionsLoaderComponent } from './mydspace-actions/claimed-ta
|
|||||||
import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/switcher/claimed-task-actions.directive';
|
import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/switcher/claimed-task-actions.directive';
|
||||||
import { ClaimedTaskActionsEditMetadataComponent } from './mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component';
|
import { ClaimedTaskActionsEditMetadataComponent } from './mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component';
|
||||||
import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component';
|
import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component';
|
||||||
|
import { ResourcePoliciesComponent } from './resource-policies/resource-policies.component';
|
||||||
|
import { NgForTrackByIdDirective } from './ng-for-track-by-id.directive';
|
||||||
|
import { ResourcePolicyFormComponent } from './resource-policies/form/resource-policy-form.component';
|
||||||
|
import { EpersonGroupListComponent } from './resource-policies/form/eperson-group-list/eperson-group-list.component';
|
||||||
|
import { ResourcePolicyTargetResolver } from './resource-policies/resolvers/resource-policy-target.resolver';
|
||||||
|
import { ResourcePolicyResolver } from './resource-policies/resolvers/resource-policy.resolver';
|
||||||
|
import { EpersonSearchBoxComponent } from './resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component';
|
||||||
|
import { GroupSearchBoxComponent } from './resource-policies/form/eperson-group-list/group-search-box/group-search-box.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -373,7 +381,12 @@ const COMPONENTS = [
|
|||||||
PublicationSearchResultListElementComponent,
|
PublicationSearchResultListElementComponent,
|
||||||
ItemVersionsNoticeComponent,
|
ItemVersionsNoticeComponent,
|
||||||
ModifyItemOverviewComponent,
|
ModifyItemOverviewComponent,
|
||||||
ImpersonateNavbarComponent
|
ImpersonateNavbarComponent,
|
||||||
|
ResourcePoliciesComponent,
|
||||||
|
ResourcePolicyFormComponent,
|
||||||
|
EpersonGroupListComponent,
|
||||||
|
EpersonSearchBoxComponent,
|
||||||
|
GroupSearchBoxComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
@@ -461,7 +474,9 @@ const PROVIDERS = [
|
|||||||
{
|
{
|
||||||
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
|
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
|
||||||
useValue: dsDynamicFormControlMapFn
|
useValue: dsDynamicFormControlMapFn
|
||||||
}
|
},
|
||||||
|
ResourcePolicyResolver,
|
||||||
|
ResourcePolicyTargetResolver
|
||||||
];
|
];
|
||||||
|
|
||||||
const DIRECTIVES = [
|
const DIRECTIVES = [
|
||||||
@@ -475,7 +490,8 @@ const DIRECTIVES = [
|
|||||||
RoleDirective,
|
RoleDirective,
|
||||||
MetadataRepresentationDirective,
|
MetadataRepresentationDirective,
|
||||||
ListableObjectDirective,
|
ListableObjectDirective,
|
||||||
ClaimedTaskActionsDirective
|
ClaimedTaskActionsDirective,
|
||||||
|
NgForTrackByIdDirective
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -14,6 +14,7 @@ export const GroupMock2: Group = Object.assign(new Group(), {
|
|||||||
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/subgroups' },
|
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/subgroups' },
|
||||||
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons' }
|
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons' }
|
||||||
},
|
},
|
||||||
|
_name: 'testgroupname2',
|
||||||
id: 'testgroupid2',
|
id: 'testgroupid2',
|
||||||
uuid: 'testgroupid2',
|
uuid: 'testgroupid2',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
@@ -32,6 +33,7 @@ export const GroupMock: Group = Object.assign(new Group(), {
|
|||||||
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/subgroups' },
|
subgroups: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/subgroups' },
|
||||||
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons' }
|
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons' }
|
||||||
},
|
},
|
||||||
|
_name: 'testgroupname',
|
||||||
id: 'testgroupid',
|
id: 'testgroupid',
|
||||||
uuid: 'testgroupid',
|
uuid: 'testgroupid',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
|
@@ -3,7 +3,7 @@ import { Component, Input, OnInit } from '@angular/core';
|
|||||||
import { find } from 'rxjs/operators';
|
import { find } from 'rxjs/operators';
|
||||||
|
|
||||||
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { ResourcePolicy } from '../../../../core/shared/resource-policy.model';
|
import { ResourcePolicy } from '../../../../core/resource-policy/models/resource-policy.model';
|
||||||
import { isEmpty } from '../../../../shared/empty.util';
|
import { isEmpty } from '../../../../shared/empty.util';
|
||||||
import { Group } from '../../../../core/eperson/models/group.model';
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
@@ -42,7 +42,7 @@ export class SubmissionSectionUploadAccessConditionsComponent implements OnInit
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.accessConditions.forEach((accessCondition: ResourcePolicy) => {
|
this.accessConditions.forEach((accessCondition: ResourcePolicy) => {
|
||||||
if (isEmpty(accessCondition.name)) {
|
if (isEmpty(accessCondition.name)) {
|
||||||
this.groupService.findById(accessCondition.groupUUID).pipe(
|
this.groupService.findByHref(accessCondition._links.group.href).pipe(
|
||||||
find((rd: RemoteData<Group>) => !rd.isResponsePending && rd.hasSucceeded))
|
find((rd: RemoteData<Group>) => !rd.isResponsePending && rd.hasSucceeded))
|
||||||
.subscribe((rd: RemoteData<Group>) => {
|
.subscribe((rd: RemoteData<Group>) => {
|
||||||
const group: Group = rd.payload;
|
const group: Group = rd.payload;
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
@@ -20,19 +23,17 @@ import {
|
|||||||
mockSubmissionId,
|
mockSubmissionId,
|
||||||
mockSubmissionState,
|
mockSubmissionState,
|
||||||
mockUploadConfigResponse,
|
mockUploadConfigResponse,
|
||||||
mockUploadConfigResponseNotRequired, mockUploadFiles,
|
mockUploadConfigResponseNotRequired,
|
||||||
|
mockUploadFiles,
|
||||||
} from '../../../shared/mocks/submission.mock';
|
} from '../../../shared/mocks/submission.mock';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { SubmissionUploadsConfigService } from '../../../core/config/submission-uploads-config.service';
|
import { SubmissionUploadsConfigService } from '../../../core/config/submission-uploads-config.service';
|
||||||
import { SectionUploadService } from './section-upload.service';
|
import { SectionUploadService } from './section-upload.service';
|
||||||
import { SubmissionSectionUploadComponent } from './section-upload.component';
|
import { SubmissionSectionUploadComponent } from './section-upload.component';
|
||||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||||
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { ResourcePolicy } from '../../../core/shared/resource-policy.model';
|
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||||
import { ResourcePolicyService } from '../../../core/data/resource-policy.service';
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
import { ConfigData } from '../../../core/config/config-data';
|
import { ConfigData } from '../../../core/config/config-data';
|
||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
import { Group } from '../../../core/eperson/models/group.model';
|
import { Group } from '../../../core/eperson/models/group.model';
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
||||||
|
|
||||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription} from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
||||||
import { distinctUntilChanged, filter, find, flatMap, map, reduce, take, tap } from 'rxjs/operators';
|
import { distinctUntilChanged, filter, find, flatMap, map, reduce, take, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { SectionModelComponent } from '../models/section.model';
|
import { SectionModelComponent } from '../models/section.model';
|
||||||
@@ -8,7 +8,7 @@ import { hasValue, isNotEmpty, isNotUndefined, isUndefined } from '../../../shar
|
|||||||
import { SectionUploadService } from './section-upload.service';
|
import { SectionUploadService } from './section-upload.service';
|
||||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||||
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||||
import { ResourcePolicyService } from '../../../core/data/resource-policy.service';
|
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||||
import { SubmissionUploadsConfigService } from '../../../core/config/submission-uploads-config.service';
|
import { SubmissionUploadsConfigService } from '../../../core/config/submission-uploads-config.service';
|
||||||
import { SubmissionUploadsModel } from '../../../core/config/models/config-submission-uploads.model';
|
import { SubmissionUploadsModel } from '../../../core/config/models/config-submission-uploads.model';
|
||||||
import { SubmissionFormsModel } from '../../../core/config/models/config-submission-forms.model';
|
import { SubmissionFormsModel } from '../../../core/config/models/config-submission-forms.model';
|
||||||
|
@@ -996,6 +996,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.",
|
||||||
|
|
||||||
|
"item.edit.authorizations.title": "Edit item's Policies",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"item.bitstreams.upload.bundle": "Bundle",
|
"item.bitstreams.upload.bundle": "Bundle",
|
||||||
|
|
||||||
@@ -2066,6 +2071,100 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"resource-policies.add.button": "Add",
|
||||||
|
|
||||||
|
"resource-policies.add.for.": "Add a new policy",
|
||||||
|
|
||||||
|
"resource-policies.add.for.bitstream": "Add a new Bitstream policy",
|
||||||
|
|
||||||
|
"resource-policies.add.for.bundle": "Add a new Bundle policy",
|
||||||
|
|
||||||
|
"resource-policies.add.for.item": "Add a new Item policy",
|
||||||
|
|
||||||
|
"resource-policies.create.page.heading": "Create new resource policy for ",
|
||||||
|
|
||||||
|
"resource-policies.create.page.failure.content": "An error occurred while creating the resource policy.",
|
||||||
|
|
||||||
|
"resource-policies.create.page.success.content": "Operation successful",
|
||||||
|
|
||||||
|
"resource-policies.create.page.title": "Create new resource policy",
|
||||||
|
|
||||||
|
"resource-policies.delete.btn": "Delete selected",
|
||||||
|
|
||||||
|
"resource-policies.delete.btn.title": "Delete selected resource policies",
|
||||||
|
|
||||||
|
"resource-policies.delete.failure.content": "An error occurred while deleting selected resource policies.",
|
||||||
|
|
||||||
|
"resource-policies.delete.success.content": "Operation successful",
|
||||||
|
|
||||||
|
"resource-policies.edit.page.heading": "Edit resource policy ",
|
||||||
|
|
||||||
|
"resource-policies.edit.page.failure.content": "An error occurred while editing the resource policy.",
|
||||||
|
|
||||||
|
"resource-policies.edit.page.success.content": "Operation successful",
|
||||||
|
|
||||||
|
"resource-policies.edit.page.title": "Edit resource policy",
|
||||||
|
|
||||||
|
"resource-policies.form.action-type.label": "Select the action type",
|
||||||
|
|
||||||
|
"resource-policies.form.action-type.required": "You must select the resource policy action.",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.label": "The eperson or group that will be grant of the permission",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.select.btn": "Select",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.tab.eperson": "Search for a ePerson",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.tab.group": "Search for a group",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.table.headers.action": "Action",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.table.headers.id": "ID",
|
||||||
|
|
||||||
|
"resource-policies.form.eperson-group-list.table.headers.name": "Name",
|
||||||
|
|
||||||
|
"resource-policies.form.date.end.label": "End Date",
|
||||||
|
|
||||||
|
"resource-policies.form.date.start.label": "Start Date",
|
||||||
|
|
||||||
|
"resource-policies.form.description.label": "Description",
|
||||||
|
|
||||||
|
"resource-policies.form.name.label": "Name",
|
||||||
|
|
||||||
|
"resource-policies.form.policy-type.label": "Select the policy type",
|
||||||
|
|
||||||
|
"resource-policies.form.policy-type.required": "You must select the resource policy type.",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.action": "Action",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.date.end": "End Date",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.date.start": "Start Date",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.edit": "Edit",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.edit.group": "Edit group",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.edit.policy": "Edit policy",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.eperson": "EPerson",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.group": "Group",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.id": "ID",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.name": "Name",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.policyType": "type",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.title.for.bitstream": "Policies for Bitstream",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.title.for.bundle": "Policies for Bundle",
|
||||||
|
|
||||||
|
"resource-policies.table.headers.title.for.item": "Policies for Item",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"search.description": "",
|
"search.description": "",
|
||||||
|
|
||||||
"search.switch-configuration.title": "Show",
|
"search.switch-configuration.title": "Show",
|
||||||
|
Reference in New Issue
Block a user