diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 7664b8967a..4fee874bb3 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -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", diff --git a/src/app/+item-page/edit-item-page/edit-item-page.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.module.ts index 2b1248e61f..13507c31ce 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.module.ts @@ -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 { diff --git a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts index c9bb14b1a9..aa42d8ed24 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts @@ -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 { diff --git a/src/app/+item-page/edit-item-page/item-authorizations/item-authorizations.component.html b/src/app/+item-page/edit-item-page/item-authorizations/item-authorizations.component.html index cb22d93868..0cf61579f1 100644 --- a/src/app/+item-page/edit-item-page/item-authorizations/item-authorizations.component.html +++ b/src/app/+item-page/edit-item-page/item-authorizations/item-authorizations.component.html @@ -1,11 +1,11 @@
- + - - diff --git a/src/app/core/resource-policy/resource-policy.service.spec.ts b/src/app/core/resource-policy/resource-policy.service.spec.ts index 6c26973e02..78566d61a3 100644 --- a/src/app/core/resource-policy/resource-policy.service.spec.ts +++ b/src/app/core/resource-policy/resource-policy.service.spec.ts @@ -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)); diff --git a/src/app/core/resource-policy/resource-policy.service.ts b/src/app/core/resource-policy/resource-policy.service.ts index 44938ec6a3..291920c35a 100644 --- a/src/app/core/resource-policy/resource-policy.service.ts +++ b/src/app/core/resource-policy/resource-policy.service.ts @@ -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> { + 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} diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 14d101a448..715e59df1a 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -67,6 +67,10 @@ export const getSucceededRemoteData = () => (source: Observable>): Observable> => source.pipe(find((rd: RemoteData) => rd.hasSucceeded)); +export const getSucceededRemoteWithNotEmptyData = () => + (source: Observable>): Observable> => + source.pipe(find((rd: RemoteData) => 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 = () => + (source: Observable>): Observable => + source.pipe( + getSucceededRemoteWithNotEmptyData(), + getRemoteDataPayload() + ); + /** * Get the all successful remotely retrieved objects * diff --git a/src/app/shared/resource-policies/create/resource-policy-create.component.html b/src/app/shared/resource-policies/create/resource-policy-create.component.html index 7990b8eb43..c4eb42bb18 100644 --- a/src/app/shared/resource-policies/create/resource-policy-create.component.html +++ b/src/app/shared/resource-policies/create/resource-policy-create.component.html @@ -1,6 +1,6 @@
-

{{'resource-policies.create.modal.head' | translate}}

+

{{'resource-policies.edit.page.heading' | translate}} {{targetResourceName}}

-
diff --git a/src/app/shared/resource-policies/create/resource-policy-create.component.ts b/src/app/shared/resource-policies/create/resource-policy-create.component.ts index e92dab14da..375966509b 100644 --- a/src/app/shared/resource-policies/create/resource-policy-create.component.ts +++ b/src/app/shared/resource-policies/create/resource-policy-create.component.ts @@ -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).payload.id; + this.targetResourceName = this.dsoNameService.getName((data.resourcePolicyTarget as RemoteData).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) => !response.isResponsePending) + ).subscribe((responseRD: RemoteData) => { + 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); - }) - } } diff --git a/src/app/shared/resource-policies/edit/resource-policy-edit.component.html b/src/app/shared/resource-policies/edit/resource-policy-edit.component.html index d463e7fc1a..ede5519c74 100644 --- a/src/app/shared/resource-policies/edit/resource-policy-edit.component.html +++ b/src/app/shared/resource-policies/edit/resource-policy-edit.component.html @@ -1,5 +1,7 @@
-

{{'resource-policies.edit.modal.head' | translate}}

+

{{'resource-policies.edit.page.heading' | translate}} {{resourcePolicy.id}}

- +
diff --git a/src/app/shared/resource-policies/edit/resource-policy-edit.component.ts b/src/app/shared/resource-policies/edit/resource-policy-edit.component.ts index fa55979d35..844ac5b4e4 100644 --- a/src/app/shared/resource-policies/edit/resource-policy-edit.component.ts +++ b/src/app/shared/resource-policies/edit/resource-policy-edit.component.ts @@ -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).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) => !response.isResponsePending) + ).subscribe((responseRD: RemoteData) => { + 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'); + } + }) + } } diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts index ed2f420438..2b4572cba5 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts +++ b/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts @@ -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); diff --git a/src/app/shared/resource-policies/form/resource-policy-form.html b/src/app/shared/resource-policies/form/resource-policy-form.html index 62b7ead932..999e7cf66c 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.html +++ b/src/app/shared/resource-policies/form/resource-policy-form.html @@ -7,7 +7,7 @@
- + diff --git a/src/app/shared/resource-policies/form/resource-policy-form.ts b/src/app/shared/resource-policies/form/resource-policy-form.ts index 9c08e8dcb3..e5b29cdaf1 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.ts +++ b/src/app/shared/resource-policies/form/resource-policy-form.ts @@ -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, RemoteData]) => { + this.resourcePolicyGrant = epersonRD.payload || groupRD.payload; + }) + ) + } } /** @@ -111,7 +128,7 @@ export class ResourcePolicyFormComponent implements OnInit { */ isFormValid(): Observable { 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()) + } } diff --git a/src/app/shared/resource-policies/resolvers/resource-policy-target.resolver.ts b/src/app/shared/resource-policies/resolvers/resource-policy-target.resolver.ts new file mode 100644 index 0000000000..3580e86080 --- /dev/null +++ b/src/app/shared/resource-policies/resolvers/resource-policy-target.resolver.ts @@ -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> { + + /** + * The data service used to make request. + */ + private dataService: DataService; + + 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<> 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> { + 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), + ); + } +} diff --git a/src/app/shared/resource-policies/resolvers/resource-policy.resolver.ts b/src/app/shared/resource-policies/resolvers/resource-policy.resolver.ts new file mode 100644 index 0000000000..2e38aca5b6 --- /dev/null +++ b/src/app/shared/resource-policies/resolvers/resource-policy.resolver.ts @@ -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> { + + 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<> 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> { + 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), + ); + } +} diff --git a/src/app/shared/resource-policies/resource-policies.component.html b/src/app/shared/resource-policies/resource-policies.component.html index ecf810fbf8..19303b67ed 100644 --- a/src/app/shared/resource-policies/resource-policies.component.html +++ b/src/app/shared/resource-policies/resource-policies.component.html @@ -4,9 +4,9 @@

- {{ 'resource-policies.table.headers.title.for.' + resourceKey | translate }} {{resourceUUID}} + {{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} {{resourceUUID}}

@@ -14,6 +14,7 @@ {{'resource-policies.table.headers.id' | translate}} {{'resource-policies.table.headers.name' | translate}} + {{'resource-policies.table.headers.policyType' | translate}} {{'resource-policies.table.headers.action' | translate}} {{'resource-policies.table.headers.eperson' | translate}} {{'resource-policies.table.headers.group' | translate}} @@ -30,6 +31,7 @@ {{policy.name}} + {{policy.policyType}} {{policy.action}} {{getEPersonName(policy) | async}} diff --git a/src/app/shared/resource-policies/resource-policies.component.ts b/src/app/shared/resource-policies/resource-policies.component.ts index 1f33437bfd..257657afdd 100644 --- a/src/app/shared/resource-policies/resource-policies.component.ts +++ b/src/app/shared/resource-policies/resource-policies.component.ts @@ -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>>} */ - private resourcePolicies$: Observable>>; + private resourcePolicies$: BehaviorSubject>> = + new BehaviorSubject>>({} 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 { - 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 { - 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>> { - 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 { - 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 { - 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()) diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 35eb5de6b5..b7b3a9bbff 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -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 = [