mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +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 { getAccessControlModulePath } from '../admin-routing.module';
|
||||
|
||||
const GROUP_EDIT_PATH = 'groups';
|
||||
export const GROUP_EDIT_PATH = 'groups';
|
||||
|
||||
export function getGroupEditPath(id: string) {
|
||||
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';
|
||||
|
||||
const REGISTRIES_MODULE_PATH = 'registries';
|
||||
const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
||||
export const ACCESS_CONTROL_MODULE_PATH = 'access-control';
|
||||
|
||||
export function getRegistriesModulePath() {
|
||||
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 { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.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
|
||||
@@ -67,6 +70,9 @@ import { ItemVersionHistoryComponent } from './item-version-history/item-version
|
||||
ItemMoveComponent,
|
||||
ItemEditBitstreamDragHandleComponent,
|
||||
VirtualMetadataComponent,
|
||||
ItemAuthorizationsComponent,
|
||||
ResourcePolicyEditComponent,
|
||||
ResourcePolicyCreateComponent,
|
||||
],
|
||||
providers: [
|
||||
BundleDataService
|
||||
|
@@ -14,6 +14,12 @@ import { ItemMoveComponent } from './item-move/item-move.component';
|
||||
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
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_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_DELETE_PATH = 'delete';
|
||||
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
|
||||
@@ -111,12 +118,43 @@ export const ITEM_EDIT_MOVE_PATH = 'move';
|
||||
path: ITEM_EDIT_MOVE_PATH,
|
||||
component: ItemMoveComponent,
|
||||
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 {
|
||||
|
||||
|
@@ -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
|
||||
*/
|
||||
this.operations = [];
|
||||
this.operations.push(new ItemOperation('authorizations', this.getCurrentUrl(item) + '/authorizations'));
|
||||
this.operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl(item) + '/mapper'));
|
||||
if (item.isWithdrawn) {
|
||||
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 { find } from 'rxjs/operators';
|
||||
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
|
||||
@@ -26,7 +27,7 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||
return this.itemService.findById(route.params.id,
|
||||
followLink('owningCollection'),
|
||||
followLink('bundles'),
|
||||
followLink('bundles', new FindListOptions(), true, followLink('bitstreams')),
|
||||
followLink('relationships'),
|
||||
followLink('version', undefined, true, followLink('versionhistory')),
|
||||
).pipe(
|
||||
|
@@ -33,7 +33,7 @@ export function getBitstreamModulePath() {
|
||||
return `/${BITSTREAM_MODULE_PATH}`;
|
||||
}
|
||||
|
||||
const ADMIN_MODULE_PATH = 'admin';
|
||||
export const ADMIN_MODULE_PATH = 'admin';
|
||||
|
||||
export function getAdminModulePath() {
|
||||
return `/${ADMIN_MODULE_PATH}`;
|
||||
|
@@ -28,7 +28,8 @@ export class DSONameService {
|
||||
return dso.firstMetadataValue('organization.legalName');
|
||||
},
|
||||
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
|
||||
*/
|
||||
export class SearchParam {
|
||||
export class RequestParam {
|
||||
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 { RelationshipTypeService } from './data/relationship-type.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 { SiteDataService } from './data/site-data.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 { Item } from './shared/item.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 { SearchFilterService } from './shared/search/search-filter.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 { dataService } from '../cache/builders/build-decorators';
|
||||
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 { ContentSourceSuccessResponse, RestResponse } from '../cache/response.models';
|
||||
import { CoreState } from '../core.reducers';
|
||||
@@ -94,7 +94,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||
const searchHref = 'findAuthorizedByCommunity';
|
||||
options = Object.assign({}, options, {
|
||||
searchParams: [new SearchParam('uuid', communityId)]
|
||||
searchParams: [new RequestParam('uuid', communityId)]
|
||||
});
|
||||
|
||||
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 { getClassForType } from '../cache/builders/build-decorators';
|
||||
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 { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { ErrorResponse, RestResponse } from '../cache/response.models';
|
||||
@@ -111,7 +111,7 @@ export abstract class DataService<T extends CacheableObject> {
|
||||
result$ = this.getSearchEndpoint(searchMethod);
|
||||
|
||||
if (hasValue(options.searchParams)) {
|
||||
options.searchParams.forEach((param: SearchParam) => {
|
||||
options.searchParams.forEach((param: RequestParam) => {
|
||||
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
|
||||
* @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
|
||||
*/
|
||||
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)),
|
||||
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
|
||||
* The object to create
|
||||
* @param {string} parentUUID
|
||||
* The UUID of the parent to create the new object under
|
||||
* @param {RequestParam[]} params
|
||||
* 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 endpoint$ = this.halService.getEndpoint(this.linkPath).pipe(
|
||||
isNotEmptyOperator(),
|
||||
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);
|
||||
@@ -479,7 +506,7 @@ export abstract class DataService<T extends CacheableObject> {
|
||||
const requestId = this.deleteAndReturnRequestId(dsoID, copyVirtualMetadata);
|
||||
|
||||
return this.requestService.getByUUID(requestId).pipe(
|
||||
find((request: RequestEntry) => request.completed),
|
||||
find((request: RequestEntry) => isNotEmpty(request) && request.completed),
|
||||
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 { dataService } from '../cache/builders/build-decorators';
|
||||
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 { RestResponse } from '../cache/response.models';
|
||||
import { CoreState } from '../core.reducers';
|
||||
@@ -257,7 +257,7 @@ export class RelationshipService extends DataService<Relationship> {
|
||||
if (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) {
|
||||
findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams];
|
||||
} else {
|
||||
|
@@ -11,7 +11,7 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { SubmissionResponseParsingService } from '../submission/submission-response-parsing.service';
|
||||
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
|
||||
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 { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
|
||||
import { MetadataschemaParsingService } from './metadataschema-parsing.service';
|
||||
@@ -146,7 +146,7 @@ export class FindListOptions {
|
||||
elementsPerPage?: number;
|
||||
currentPage?: number;
|
||||
sort?: SortOptions;
|
||||
searchParams?: SearchParam[];
|
||||
searchParams?: RequestParam[];
|
||||
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 { TestScheduler } from 'rxjs/testing';
|
||||
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 { ChangeAnalyzer } from '../data/change-analyzer';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
@@ -105,7 +105,7 @@ describe('EPersonDataService', () => {
|
||||
it('search by default scope (byMetadata) and no query', () => {
|
||||
service.searchByScope(null, '');
|
||||
const options = Object.assign(new FindListOptions(), {
|
||||
searchParams: [Object.assign(new SearchParam('query', ''))]
|
||||
searchParams: [Object.assign(new RequestParam('query', ''))]
|
||||
});
|
||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||
});
|
||||
@@ -113,7 +113,7 @@ describe('EPersonDataService', () => {
|
||||
it('search metadata scope and no query', () => {
|
||||
service.searchByScope('metadata', '');
|
||||
const options = Object.assign(new FindListOptions(), {
|
||||
searchParams: [Object.assign(new SearchParam('query', ''))]
|
||||
searchParams: [Object.assign(new RequestParam('query', ''))]
|
||||
});
|
||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||
});
|
||||
@@ -121,7 +121,7 @@ describe('EPersonDataService', () => {
|
||||
it('search metadata scope and with query', () => {
|
||||
service.searchByScope('metadata', 'test');
|
||||
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);
|
||||
});
|
||||
@@ -129,7 +129,7 @@ describe('EPersonDataService', () => {
|
||||
it('search email scope and no query', () => {
|
||||
service.searchByScope('email', '');
|
||||
const options = Object.assign(new FindListOptions(), {
|
||||
searchParams: [Object.assign(new SearchParam('email', ''))]
|
||||
searchParams: [Object.assign(new RequestParam('email', ''))]
|
||||
});
|
||||
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 { dataService } from '../cache/builders/build-decorators';
|
||||
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 { RestResponse } from '../cache/response.models';
|
||||
import { DataService } from '../data/data.service';
|
||||
@@ -97,7 +97,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
||||
* @param linksToFollow
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
||||
* @param linksToFollow
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ export class EPersonDataService extends DataService<EPerson> {
|
||||
* @param options
|
||||
* @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();
|
||||
if (options) {
|
||||
findListOptions = Object.assign(new FindListOptions(), options);
|
||||
|
@@ -11,7 +11,7 @@ import {
|
||||
GroupRegistryEditGroupAction
|
||||
} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
|
||||
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 { ChangeAnalyzer } from '../data/change-analyzer';
|
||||
import { PaginatedList } from '../data/paginated-list';
|
||||
@@ -103,7 +103,7 @@ describe('GroupDataService', () => {
|
||||
it('search with empty query', () => {
|
||||
service.searchGroups('');
|
||||
const options = Object.assign(new FindListOptions(), {
|
||||
searchParams: [Object.assign(new SearchParam('query', ''))]
|
||||
searchParams: [Object.assign(new RequestParam('query', ''))]
|
||||
});
|
||||
expect(service.searchBy).toHaveBeenCalledWith('byMetadata', options);
|
||||
});
|
||||
@@ -111,7 +111,7 @@ describe('GroupDataService', () => {
|
||||
it('search with query', () => {
|
||||
service.searchGroups('test');
|
||||
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);
|
||||
});
|
||||
|
@@ -14,7 +14,7 @@ import { hasValue } from '../../shared/empty.util';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
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 { RestResponse } from '../cache/response.models';
|
||||
import { DataService } from '../data/data.service';
|
||||
@@ -97,7 +97,7 @@ export class GroupDataService extends DataService<Group> {
|
||||
* @param linksToFollow
|
||||
*/
|
||||
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();
|
||||
if (options) {
|
||||
findListOptions = Object.assign(new FindListOptions(), options);
|
||||
@@ -121,7 +121,7 @@ export class GroupDataService extends DataService<Group> {
|
||||
isMemberOf(groupName: string): Observable<boolean> {
|
||||
const searchHref = 'isMemberOf';
|
||||
const options = new FindListOptions();
|
||||
options.searchParams = [new SearchParam('groupName', groupName)];
|
||||
options.searchParams = [new RequestParam('groupName', groupName)];
|
||||
|
||||
return this.searchBy(searchHref, options).pipe(
|
||||
filter((groups: RemoteData<PaginatedList<Group>>) => !groups.isResponsePending),
|
||||
|
@@ -5,27 +5,27 @@ export enum ActionType {
|
||||
/**
|
||||
* Action of reading, viewing or downloading something
|
||||
*/
|
||||
READ = 0,
|
||||
READ = 'READ',
|
||||
|
||||
/**
|
||||
* Action of modifying something
|
||||
*/
|
||||
WRITE = 1,
|
||||
WRITE = 'WRITE',
|
||||
|
||||
/**
|
||||
* Action of deleting something
|
||||
*/
|
||||
DELETE = 2,
|
||||
DELETE = 'DELETE',
|
||||
|
||||
/**
|
||||
* Action of adding something to a container
|
||||
*/
|
||||
ADD = 3,
|
||||
ADD = 'ADD',
|
||||
|
||||
/**
|
||||
* Action of removing something from a container
|
||||
*/
|
||||
REMOVE = 4,
|
||||
REMOVE = 'REMOVE',
|
||||
|
||||
/**
|
||||
* Action of performing workflow step 1
|
||||
@@ -50,15 +50,20 @@ export enum ActionType {
|
||||
/**
|
||||
* 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_ITEM_READ = 10,
|
||||
DEFAULT_ITEM_READ = 'DEFAULT_ITEM_READ',
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -6,4 +6,4 @@ import { ResourceType } from './resource-type';
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* 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 { 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 { DSpaceObject } from './dspace-object.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
|
||||
@inheritSerialization(DSpaceObject)
|
||||
@@ -17,5 +24,19 @@ export class Bundle extends DSpaceObject {
|
||||
self: HALLink;
|
||||
primaryBitstream: 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 { License } from './license.model';
|
||||
import { LICENSE } from './license.resource-type';
|
||||
import { ResourcePolicy } from './resource-policy.model';
|
||||
import { RESOURCE_POLICY } from './resource-policy.resource-type';
|
||||
import { ResourcePolicy } from '../resource-policy/models/resource-policy.model';
|
||||
import { RESOURCE_POLICY } from '../resource-policy/models/resource-policy.resource-type';
|
||||
import { COMMUNITY } from './community.resource-type';
|
||||
import { Community } from './community.model';
|
||||
import { ChildHALResource } from './child-hal-resource.model';
|
||||
|
@@ -67,6 +67,10 @@ export const getSucceededRemoteData = () =>
|
||||
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
|
||||
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
|
||||
*
|
||||
@@ -84,6 +88,23 @@ export const getFirstSucceededRemoteDataPayload = () =>
|
||||
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
|
||||
*
|
||||
|
@@ -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 { hasValue, isNotEmpty, isNotUndefined } from '../../empty.util';
|
||||
import { NotificationsService } from '../../notifications/notifications.service';
|
||||
import { RequestParam } from '../../../core/cache/models/request-param.model';
|
||||
|
||||
/**
|
||||
* Component representing the create page for communities and collections
|
||||
@@ -76,7 +77,7 @@ export class CreateComColPageComponent<TDomain extends DSpaceObject> implements
|
||||
const uploader = event.uploader;
|
||||
|
||||
this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => {
|
||||
this.dsoDataService.create(dso, uuid)
|
||||
this.dsoDataService.create(dso, new RequestParam('parent', uuid))
|
||||
.pipe(getSucceededRemoteData())
|
||||
.subscribe((dsoRD: RemoteData<TDomain>) => {
|
||||
if (isNotUndefined(dsoRD)) {
|
||||
|
@@ -3,6 +3,8 @@ import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { isObject } from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
|
||||
import { isNull } from './empty.util';
|
||||
|
||||
/**
|
||||
* Returns true if the passed value is a NgbDateStruct.
|
||||
*
|
||||
@@ -13,7 +15,7 @@ import * as moment from 'moment';
|
||||
*/
|
||||
export function isNgbDateStruct(value: object): boolean {
|
||||
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 {
|
||||
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 { ClaimedTaskActionsEditMetadataComponent } from './mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.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 = [
|
||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||
@@ -373,7 +381,12 @@ const COMPONENTS = [
|
||||
PublicationSearchResultListElementComponent,
|
||||
ItemVersionsNoticeComponent,
|
||||
ModifyItemOverviewComponent,
|
||||
ImpersonateNavbarComponent
|
||||
ImpersonateNavbarComponent,
|
||||
ResourcePoliciesComponent,
|
||||
ResourcePolicyFormComponent,
|
||||
EpersonGroupListComponent,
|
||||
EpersonSearchBoxComponent,
|
||||
GroupSearchBoxComponent
|
||||
];
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
@@ -461,7 +474,9 @@ const PROVIDERS = [
|
||||
{
|
||||
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
|
||||
useValue: dsDynamicFormControlMapFn
|
||||
}
|
||||
},
|
||||
ResourcePolicyResolver,
|
||||
ResourcePolicyTargetResolver
|
||||
];
|
||||
|
||||
const DIRECTIVES = [
|
||||
@@ -475,7 +490,8 @@ const DIRECTIVES = [
|
||||
RoleDirective,
|
||||
MetadataRepresentationDirective,
|
||||
ListableObjectDirective,
|
||||
ClaimedTaskActionsDirective
|
||||
ClaimedTaskActionsDirective,
|
||||
NgForTrackByIdDirective
|
||||
];
|
||||
|
||||
@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' },
|
||||
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons' }
|
||||
},
|
||||
_name: 'testgroupname2',
|
||||
id: 'testgroupid2',
|
||||
uuid: 'testgroupid2',
|
||||
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' },
|
||||
epersons: { href: 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid/epersons' }
|
||||
},
|
||||
_name: 'testgroupname',
|
||||
id: 'testgroupid',
|
||||
uuid: 'testgroupid',
|
||||
type: 'group',
|
||||
|
@@ -3,7 +3,7 @@ import { Component, Input, OnInit } from '@angular/core';
|
||||
import { find } from 'rxjs/operators';
|
||||
|
||||
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 { Group } from '../../../../core/eperson/models/group.model';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
@@ -42,7 +42,7 @@ export class SubmissionSectionUploadAccessConditionsComponent implements OnInit
|
||||
ngOnInit() {
|
||||
this.accessConditions.forEach((accessCondition: ResourcePolicy) => {
|
||||
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))
|
||||
.subscribe((rd: RemoteData<Group>) => {
|
||||
const group: Group = rd.payload;
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
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 { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
@@ -20,19 +23,17 @@ import {
|
||||
mockSubmissionId,
|
||||
mockSubmissionState,
|
||||
mockUploadConfigResponse,
|
||||
mockUploadConfigResponseNotRequired, mockUploadFiles,
|
||||
mockUploadConfigResponseNotRequired,
|
||||
mockUploadFiles,
|
||||
} 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 { SectionUploadService } from './section-upload.service';
|
||||
import { SubmissionSectionUploadComponent } from './section-upload.component';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { GroupDataService } from '../../../core/eperson/group-data.service';
|
||||
import { cold, hot } from 'jasmine-marbles';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { ResourcePolicy } from '../../../core/shared/resource-policy.model';
|
||||
import { ResourcePolicyService } from '../../../core/data/resource-policy.service';
|
||||
import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model';
|
||||
import { ResourcePolicyService } from '../../../core/resource-policy/resource-policy.service';
|
||||
import { ConfigData } from '../../../core/config/config-data';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
import { Group } from '../../../core/eperson/models/group.model';
|
||||
|
@@ -1,6 +1,6 @@
|
||||
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 { SectionModelComponent } from '../models/section.model';
|
||||
@@ -8,7 +8,7 @@ import { hasValue, isNotEmpty, isNotUndefined, isUndefined } from '../../../shar
|
||||
import { SectionUploadService } from './section-upload.service';
|
||||
import { CollectionDataService } from '../../../core/data/collection-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 { SubmissionUploadsModel } from '../../../core/config/models/config-submission-uploads.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",
|
||||
|
||||
@@ -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.switch-configuration.title": "Show",
|
||||
|
Reference in New Issue
Block a user