Intermediate commit

This commit is contained in:
Giuseppe Digilio
2020-04-03 20:25:58 +02:00
parent 1ac52edbf7
commit 6314e17f27
19 changed files with 443 additions and 106 deletions

View File

@@ -1675,9 +1675,21 @@
"resource-policies.add.for.item": "Add a new Item policy",
"resource-policies.create.modal.head": "Create new resource policy",
"resource-policies.create.page.heading": "Create new resource policy for ",
"resource-policies.edit.modal.head": "Edit resource policy",
"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.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",
@@ -1721,9 +1733,11 @@
"resource-policies.table.headers.group": "Group",
"resource-policies.table.headers.id": "ID",
"resource-policies.table.headers.name": "Name",
"resource-policies.table.headers.id": "ID",
"resource-policies.table.headers.policyType": "type",
"resource-policies.table.headers.title.for.bitstream": "Policies for Bitstream",

View File

@@ -24,6 +24,8 @@ import { ItemMoveComponent } from './item-move/item-move.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
@@ -56,7 +58,9 @@ import { ItemAuthorizationsComponent } from './item-authorizations/item-authoriz
ItemCollectionMapperComponent,
ItemMoveComponent,
VirtualMetadataComponent,
ItemAuthorizationsComponent
ItemAuthorizationsComponent,
ResourcePolicyEditComponent,
ResourcePolicyCreateComponent,
]
})
export class EditItemPageModule {

View File

@@ -15,8 +15,10 @@ import { ItemRelationshipsComponent } from './item-relationships/item-relationsh
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 { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.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';
export const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
export const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
@@ -118,19 +120,27 @@ export const ITEM_EDIT_AUTHORIZATIONS_PATH = 'authorizations';
},
{
path: ITEM_EDIT_AUTHORIZATIONS_PATH,
data: { title: 'item.edit.authorizations.title' },
children: [
{
path: ':dso/create',
path: 'create',
resolve: {
resourcePolicyTarget: ResourcePolicyTargetResolver
},
component: ResourcePolicyCreateComponent,
data: { title: 'resource-policies.create.page.title' }
},
{
path: ':dso/:policy/edit',
path: 'edit',
resolve: {
resourcePolicy: ResourcePolicyResolver
},
component: ResourcePolicyEditComponent,
data: { title: 'resource-policies.edit.page.title' }
},
{
path: '',
component: ItemAuthorizationsComponent
component: ItemAuthorizationsComponent,
data: { title: 'item.edit.authorizations.title' }
}
]
}
@@ -138,7 +148,10 @@ export const ITEM_EDIT_AUTHORIZATIONS_PATH = 'authorizations';
}
])
],
providers: []
providers: [
ResourcePolicyResolver,
ResourcePolicyTargetResolver
]
})
export class EditItemPageRoutingModule {

View File

@@ -1,11 +1,11 @@
<div class="container">
<ds-alert [type]="'alert-info'" [content]="'item.edit.authorizations.heading'"></ds-alert>
<ds-resource-policies [resourceKey]="'item'" [resourceUUID]="(getItemUUID() | async)"></ds-resource-policies>
<ds-resource-policies [resourceType]="'item'" [resourceUUID]="(getItemUUID() | async)"></ds-resource-policies>
<ng-container *ngFor="let bundle of (getItemBundles() | async)?.page; trackById">
<ds-resource-policies [resourceKey]="'bundle'"
<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 [resourceKey]="'bitstream'"
<ds-resource-policies [resourceType]="'bitstream'"
[resourceUUID]="bitstream.id"></ds-resource-policies>
</ng-container>
</ng-container>

View File

@@ -170,18 +170,6 @@ describe('ResourcePolicyService', () => {
});
describe('delete', () => {
beforeEach(async(() => {
scheduler = getTestScheduler();
responseCacheEntry.completed = true;
requestService = jasmine.createSpyObj('requestService', {
configure: {},
getByHref: observableOf(responseCacheEntry),
getByUUID: hot('a', { a: responseCacheEntry }),
generateRequestId: 'request-id',
removeByHrefSubstring: {}
});
}));
it('should proxy the call to dataservice.create', () => {
scheduler.schedule(() => service.delete(resourcePolicyId));
scheduler.flush();
@@ -190,6 +178,15 @@ describe('ResourcePolicyService', () => {
});
});
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));

View File

@@ -103,6 +103,15 @@ export class ResourcePolicyService {
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}

View File

@@ -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
*

View File

@@ -1,6 +1,6 @@
<div class="container">
<h4 class="mb-3">{{'resource-policies.create.modal.head' | translate}}</h4>
<h4 class="mb-3">{{'resource-policies.edit.page.heading' | translate}} {{targetResourceName}}</h4>
<ds-resource-policy-form (reset)="redirectToPreviousPage()"
<ds-resource-policy-form (reset)="redirectToAuthorizationsPage()"
(submit)="createResourcePolicy($event)"></ds-resource-policy-form>
</div>

View File

@@ -1,32 +1,69 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { take } from 'rxjs/operators';
import { first, map, take } from 'rxjs/operators';
import { ResourcePolicyEvent } from '../form/resource-policy-form';
import { RouteService } from '../../../core/services/route.service';
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';
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 {
export class ResourcePolicyCreateComponent implements OnInit {
/**
* The uuid of the resource target of the policy
*/
private targetResourceUUID: string;
public targetResourceName: string;
constructor(
protected resourcePolicy: ResourcePolicyService,
protected router: Router,
protected routeService: RouteService) {
private dsoNameService: DSONameService,
private notificationsService: NotificationsService,
private resourcePolicyService: ResourcePolicyService,
private route: ActivatedRoute,
private router: Router) {
}
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);
});
}
redirectToAuthorizationsPage() {
this.router.navigate([`../../${ITEM_EDIT_AUTHORIZATIONS_PATH}`], { relativeTo: this.route });
}
createResourcePolicy(event: ResourcePolicyEvent) {
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>) => {
if (responseRD.hasSucceeded) {
this.notificationsService.success(null, 'resource-policies.create.page.success.content');
this.redirectToAuthorizationsPage();
} else {
this.notificationsService.error(null, 'resource-policies.create.page.failure.content');
}
})
}
redirectToPreviousPage() {
this.routeService.getPreviousUrl().pipe(take(1))
.subscribe((url) => {
this.router.navigateByUrl(url);
})
}
}

View File

@@ -1,5 +1,7 @@
<div class="container">
<h4 class="mb-3">{{'resource-policies.edit.modal.head' | translate}}</h4>
<h4 class="mb-3">{{'resource-policies.edit.page.heading' | translate}} {{resourcePolicy.id}}</h4>
<ds-resource-policy-form></ds-resource-policy-form>
<ds-resource-policy-form [resourcePolicy]="resourcePolicy"
(reset)="redirectToAuthorizationsPage()"
(submit)="updateResourcePolicy($event)"></ds-resource-policy-form>
</div>

View File

@@ -1,9 +1,60 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { first, map, take } from 'rxjs/operators';
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';
import { ITEM_EDIT_AUTHORIZATIONS_PATH } from '../../../+item-page/edit-item-page/edit-item-page.routing.module';
@Component({
selector: 'ds-resource-policy-edit',
templateUrl: './resource-policy-edit.component.html'
})
export class ResourcePolicyEditComponent {
export class ResourcePolicyEditComponent implements OnInit {
/**
* The resource policy object to edit
*/
public resourcePolicy: ResourcePolicy;
constructor(
private notificationsService: NotificationsService,
private resourcePolicyService: ResourcePolicyService,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit(): void {
this.route.data.pipe(
map((data) => data),
take(1)
).subscribe((data: any) => {
this.resourcePolicy = (data.resourcePolicy as RemoteData<ResourcePolicy>).payload;
console.log(data)
});
}
redirectToAuthorizationsPage() {
this.router.navigate([`../../${ITEM_EDIT_AUTHORIZATIONS_PATH}`], { relativeTo: this.route });
}
updateResourcePolicy(event: ResourcePolicyEvent) {
const updatedObject = Object.assign({}, event.object, {
_links: this.resourcePolicy._links
});
this.resourcePolicyService.update(updatedObject).pipe(
first((response: RemoteData<ResourcePolicy>) => !response.isResponsePending)
).subscribe((responseRD: RemoteData<ResourcePolicy>) => {
if (responseRD.hasSucceeded) {
this.notificationsService.success(null, 'resource-policies.edit.page.success.content');
this.redirectToAuthorizationsPage();
} else {
this.notificationsService.error(null, 'resource-policies.edit.page.failure.content');
}
})
}
}

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Injector, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { map, take } from 'rxjs/operators';
@@ -7,13 +7,15 @@ 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 { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
import { GroupDataService } from '../../../../core/eperson/group-data.service';
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';
@Component({
selector: 'ds-eperson-group-list',
@@ -69,9 +71,13 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
*/
private subs: Subscription[] = [];
constructor(public dsoNameService: DSONameService,
private epersonService: EPersonDataService,
private groupsService: GroupDataService) {
constructor(public dsoNameService: DSONameService, private parentInjector: Injector) {
const resourceType: ResourceType = (this.isListOfEPerson) ? EPERSON : GROUP;
const provider = getDataServiceFor(resourceType);
this.dataService = Injector.create({
providers: [],
parent: this.parentInjector
}).get(provider);
}
/**
@@ -80,7 +86,6 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.paginationOptions.id = uniqueId('eperson-group-list-pagination');
this.paginationOptions.pageSize = 5;
this.dataService = (this.isListOfEPerson) ? this.epersonService : this.groupsService;
if (this.initSelected) {
this.entrySelectedId.next(this.initSelected);

View File

@@ -7,7 +7,7 @@
<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 type="pills">
<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>

View File

@@ -1,6 +1,6 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { Observable } from 'rxjs';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { DynamicFormControlModel, DynamicFormGroupModel, DynamicSelectModel } from '@ng-dynamic-forms/core';
@@ -22,8 +22,11 @@ import { DsDynamicTextAreaModel } from '../../form/builder/ds-dynamic-form-ui/mo
import { DynamicDsDatePickerModel } from '../../form/builder/ds-dynamic-form-ui/models/date-picker/date-picker.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
import { hasValue, isNotEmpty } from '../../empty.util';
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';
export interface ResourcePolicyEvent {
object: ResourcePolicy,
@@ -40,10 +43,10 @@ export interface ResourcePolicyEvent {
/**
* Component that show form for adding/editing a resource policy
*/
export class ResourcePolicyFormComponent implements OnInit {
export class ResourcePolicyFormComponent implements OnInit, OnDestroy {
/**
* The resource policy to edit
* If given contains the resource policy to edit
* @type {ResourcePolicy}
*/
@Input() resourcePolicy: ResourcePolicy;
@@ -76,13 +79,19 @@ export class ResourcePolicyFormComponent implements OnInit {
* The eperson or group that will be grant of the permission
* @type {DSpaceObject}
*/
public resourcePolicyTarget: 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 resourcePolicyTargetType: string;
public resourcePolicyGrantType: string;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
private subs: Subscription[] = [];
/**
* Initialize instance variables
@@ -102,6 +111,14 @@ export class ResourcePolicyFormComponent implements OnInit {
ngOnInit(): void {
this.formId = this.formService.getUniqueId('resource-policy-form');
this.formModel = this.buildResourcePolicyForm();
if (!this.canSetGrant()) {
this.subs.push(observableCombineLatest([this.resourcePolicy.eperson, this.resourcePolicy.group])
.subscribe(([epersonRD, groupRD]: [RemoteData<DSpaceObject>, RemoteData<DSpaceObject>]) => {
this.resourcePolicyGrant = epersonRD.payload || groupRD.payload;
})
)
}
}
/**
@@ -111,7 +128,7 @@ export class ResourcePolicyFormComponent implements OnInit {
*/
isFormValid(): Observable<boolean> {
return this.formService.isValid(this.formId).pipe(
map((isValid: boolean) => isValid && isNotEmpty(this.resourcePolicyTarget))
map((isValid: boolean) => isValid && isNotEmpty(this.resourcePolicyGrant))
)
}
@@ -171,21 +188,31 @@ export class ResourcePolicyFormComponent implements OnInit {
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.resourcePolicyTarget) ? this.dsoNameService.getName(this.resourcePolicyTarget) : '';
console.log(this.resourcePolicy);
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.resourcePolicyTarget = object;
this.resourcePolicyTargetType = isEPerson ? 'eperson' : 'group';
this.resourcePolicyGrant = object;
this.resourcePolicyGrantType = isEPerson ? 'eperson' : 'group';
}
/**
@@ -204,17 +231,40 @@ export class ResourcePolicyFormComponent implements OnInit {
this.formService.getFormData(this.formId)
.subscribe((data) => {
const eventPayload: ResourcePolicyEvent = Object.create({});
const resourcePolicy = new ResourcePolicy();
resourcePolicy.name = data.name;
resourcePolicy.description = data.description;
resourcePolicy.policyType = data.policyType;
resourcePolicy.action = data.action;
resourcePolicy.startDate = (data.date) ? data.date.start : undefined;
resourcePolicy.endDate = (data.date) ? data.date.end : undefined;
eventPayload.object = resourcePolicy;
eventPayload.target.type = this.resourcePolicyTargetType;
eventPayload.target.uuid = this.resourcePolicyTarget.id;
eventPayload.object = this.createResourcePolicyByFormData(data);
console.log('resourcePolicyTarget', this.resourcePolicyGrant.type.value);
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) ? data.date.start[0].value : null;
resourcePolicy.endDate = (data.date && data.date.end) ? data.date.end[0].value : null;
resourcePolicy.type = RESOURCE_POLICY;
return resourcePolicy;
}
/**
* Unsubscribe from all subscriptions
*/
ngOnDestroy(): void {
this.subs
.filter((subscription) => hasValue(subscription))
.forEach((subscription) => subscription.unsubscribe())
}
}

View File

@@ -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),
);
}
}

View File

@@ -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),
);
}
}

View File

@@ -4,9 +4,9 @@
<tr>
<th colspan="7">
<p class="d-flex justify-content-between align-items-center m-0">
{{ 'resource-policies.table.headers.title.for.' + resourceKey | translate }} {{resourceUUID}}
{{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} {{resourceUUID}}
<button class="btn btn-outline-primary float-right" (click)="createResourcePolicy()">
{{'resource-policies.add.for.' + resourceKey | translate}}
{{'resource-policies.add.for.' + resourceType | translate}}
</button>
</p>
</th>
@@ -14,6 +14,7 @@
<tr class="text-center">
<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>
@@ -30,6 +31,7 @@
</button>
</th>
<td>{{policy.name}}</td>
<td>{{policy.policyType}}</td>
<td>{{policy.action}}</td>
<td *ngIf="(hasEPerson(policy) | async)">
{{getEPersonName(policy) | async}}

View File

@@ -1,12 +1,16 @@
import { ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { filter, map, startWith, take } from 'rxjs/operators';
import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
import { PaginatedList } from '../../core/data/paginated-list';
import { getFirstSucceededRemoteDataPayload, getSucceededRemoteData } from '../../core/shared/operators';
import {
getFirstSucceededRemoteDataPayload,
getFirstSucceededRemoteDataWithNotEmptyPayload,
getSucceededRemoteData
} from '../../core/shared/operators';
import { RemoteData } from '../../core/data/remote-data';
import { ResourcePolicy } from '../../core/resource-policy/models/resource-policy.model';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
@@ -15,6 +19,8 @@ import { GroupDataService } from '../../core/eperson/group-data.service';
import { hasValue, isNotEmpty } from '../empty.util';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { EPersonDataService } from '../../core/eperson/eperson-data.service';
import { followLink } from '../utils/follow-link-config.model';
import { RequestService } from '../../core/data/request.service';
@Component({
selector: 'ds-resource-policies',
@@ -36,13 +42,20 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* The resource type (e.g. 'item', 'bundle' etc) used as key to build automatically translation label
* @type {string}
*/
@Input() public resourceKey: string;
@Input() public resourceType: string;
/**
* A boolean representing if component is active
* @type {boolean}
*/
private isActive: boolean;
/**
* The list of policies for given resource
* @type {Observable<RemoteData<PaginatedList<ResourcePolicy>>>}
*/
private resourcePolicies$: Observable<RemoteData<PaginatedList<ResourcePolicy>>>;
private resourcePolicies$: BehaviorSubject<RemoteData<PaginatedList<ResourcePolicy>>> =
new BehaviorSubject<RemoteData<PaginatedList<ResourcePolicy>>>({} as any);
/**
* Array to track all subscriptions and unsubscribe them onDestroy
@@ -57,6 +70,7 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* @param {DSONameService} dsoNameService
* @param {EPersonDataService} ePersonService
* @param {GroupDataService} groupService
* @param {RequestService} requestService
* @param {ResourcePolicyService} resourcePolicyService
* @param {ActivatedRoute} route
* @param {Router} router
@@ -66,6 +80,7 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
private dsoNameService: DSONameService,
private ePersonService: EPersonDataService,
private groupService: GroupDataService,
private requestService: RequestService,
private resourcePolicyService: ResourcePolicyService,
private route: ActivatedRoute,
private router: Router
@@ -76,9 +91,16 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* Initialize the component, setting up the resource's policies
*/
ngOnInit(): void {
this.resourcePolicies$ = this.resourcePolicyService.searchByResource(this.resourceUUID).pipe(
getSucceededRemoteData()
);
this.isActive = true;
this.requestService.removeByHrefSubstring(this.resourceUUID);
this.resourcePolicyService.searchByResource(this.resourceUUID, null,
followLink('eperson'), followLink('group')).pipe(
filter(() => this.isActive),
getSucceededRemoteData(),
take(1)
).subscribe((result) => {
this.resourcePolicies$.next(result);
});
}
@@ -86,7 +108,13 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* Redirect to resource policy creation page
*/
createResourcePolicy(): void {
this.router.navigate([`../${this.resourceUUID}/create`], { relativeTo: this.route })
this.router.navigate([`../create`], {
relativeTo: this.route,
queryParams: {
policyTargetId: this.resourceUUID,
targetType: this.resourceType
}
})
}
/**
@@ -95,7 +123,12 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* @param policy The resource policy
*/
editResourcePolicy(policy: ResourcePolicy): void {
this.router.navigate([`../${this.resourceUUID}/${policy.id}/edit`], { relativeTo: this.route })
this.router.navigate([`../edit`], {
relativeTo: this.route,
queryParams: {
policyId: policy.id
}
})
}
/**
@@ -104,9 +137,11 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* @param policy The resource policy
*/
getEPersonName(policy: ResourcePolicy): Observable<string> {
return this.ePersonService.findByHref(policy._links.eperson.href).pipe(
getFirstSucceededRemoteDataPayload(),
map((eperson: EPerson) => this.dsoNameService.getName(eperson))
return policy.eperson.pipe(
filter(() => this.isActive),
getFirstSucceededRemoteDataWithNotEmptyPayload(),
map((eperson: EPerson) => this.dsoNameService.getName(eperson)),
startWith('')
)
}
@@ -116,9 +151,11 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* @param policy The resource policy
*/
getGroupName(policy: ResourcePolicy): Observable<string> {
return this.groupService.findByHref(policy._links.group.href).pipe(
getFirstSucceededRemoteDataPayload(),
map((group: Group) => this.dsoNameService.getName(group))
return policy.group.pipe(
filter(() => this.isActive),
getFirstSucceededRemoteDataWithNotEmptyPayload(),
map((group: Group) => this.dsoNameService.getName(group)),
startWith('')
)
}
@@ -128,7 +165,7 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* @return an observable that emits all resource's policies
*/
getResourcePolicies(): Observable<RemoteData<PaginatedList<ResourcePolicy>>> {
return this.resourcePolicies$;
return this.resourcePolicies$.asObservable();
}
/**
@@ -138,7 +175,8 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* @return an observable that emits true when the policy is linked to a ePerson, false otherwise
*/
hasEPerson(policy): Observable<boolean> {
return this.ePersonService.findByHref(policy._links.eperson.href).pipe(
return policy.eperson.pipe(
filter(() => this.isActive),
getFirstSucceededRemoteDataPayload(),
map((eperson: EPerson) => isNotEmpty(eperson))
)
@@ -151,7 +189,8 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* @return an observable that emits true when the policy is linked to a group, false otherwise
*/
hasGroup(policy): Observable<boolean> {
return this.groupService.findByHref(policy._links.group.href).pipe(
return policy.group.pipe(
filter(() => this.isActive),
getFirstSucceededRemoteDataPayload(),
map((group: Group) => isNotEmpty(group))
)
@@ -164,7 +203,8 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
*/
redirectToGroupEditPage(policy: ResourcePolicy): void {
this.subs.push(
this.groupService.findByHref(policy._links.group.href).pipe(
policy.group.pipe(
filter(() => this.isActive),
getFirstSucceededRemoteDataPayload(),
map((group: Group) => group.id)
).subscribe((groupUUID) => this.router.navigate(['groups', groupUUID, 'edit']))
@@ -175,6 +215,7 @@ export class ResourcePoliciesComponent implements OnInit, OnDestroy {
* Unsubscribe from all subscriptions
*/
ngOnDestroy(): void {
this.isActive = false;
this.subs
.filter((subscription) => hasValue(subscription))
.forEach((subscription) => subscription.unsubscribe())

View File

@@ -189,9 +189,9 @@ import { ItemVersionsNoticeComponent } from './item/item-versions/notice/item-ve
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';
import { ResourcePolicyCreateComponent } from './resource-policies/create/resource-policy-create.component';
import { ResourcePolicyEditComponent } from './resource-policies/edit/resource-policy-edit.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';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -366,8 +366,6 @@ const COMPONENTS = [
ItemVersionsNoticeComponent,
ResourcePoliciesComponent,
ResourcePolicyFormComponent,
ResourcePolicyCreateComponent,
ResourcePolicyEditComponent,
EpersonGroupListComponent
];
@@ -435,9 +433,7 @@ const ENTRY_COMPONENTS = [
LogInPasswordComponent,
LogInShibbolethComponent,
ItemVersionsComponent,
ItemVersionsNoticeComponent,
ResourcePolicyCreateComponent,
ResourcePolicyEditComponent
ItemVersionsNoticeComponent
];
const SHARED_ITEM_PAGE_COMPONENTS = [
@@ -452,7 +448,9 @@ const PROVIDERS = [
{
provide: DYNAMIC_FORM_CONTROL_MAP_FN,
useValue: dsDynamicFormControlMapFn
}
},
ResourcePolicyResolver,
ResourcePolicyTargetResolver
];
const DIRECTIVES = [