mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 02:24:11 +00:00
Merge pull request #533 from atmire/Virtual-metadata-on-item-delete
Virtual metadata on item delete
This commit is contained in:
@@ -853,7 +853,7 @@
|
|||||||
|
|
||||||
"item.edit.tabs.relationships.head": "Item Relationships",
|
"item.edit.tabs.relationships.head": "Item Relationships",
|
||||||
|
|
||||||
"item.edit.tabs.relationships.title": "Item Edit - Relationships",
|
"item.edit.tabs.relationships.title": "Item Edit - Relationships",
|
||||||
|
|
||||||
"item.edit.tabs.status.buttons.authorizations.button": "Authorizations...",
|
"item.edit.tabs.status.buttons.authorizations.button": "Authorizations...",
|
||||||
|
|
||||||
@@ -1974,12 +1974,16 @@
|
|||||||
|
|
||||||
"uploader.drag-message": "Drag & Drop your files here",
|
"uploader.drag-message": "Drag & Drop your files here",
|
||||||
|
|
||||||
"uploader.or": ", or ",
|
"uploader.or": ", or",
|
||||||
|
|
||||||
"uploader.processing": "Processing",
|
"uploader.processing": "Processing",
|
||||||
|
|
||||||
"uploader.queue-length": "Queue length",
|
"uploader.queue-length": "Queue length",
|
||||||
|
|
||||||
|
"virtual-metadata.delete-item.info": "Select the types for which you want to save the virtual metadata as real metadata",
|
||||||
|
|
||||||
|
"virtual-metadata.delete-item.modal-head": "The virtual metadata of this relation",
|
||||||
|
|
||||||
"virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata",
|
"virtual-metadata.delete-relationship.modal-head": "Select the items for which you want to save the virtual metadata as real metadata",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,98 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
|
||||||
|
<h2>{{headerMessage | translate: {id: item.handle} }}</h2>
|
||||||
|
<p>{{descriptionMessage | translate}}</p>
|
||||||
|
<ds-modify-item-overview [item]="item"></ds-modify-item-overview>
|
||||||
|
|
||||||
|
<ng-container *ngVar="(types$ | async) as types">
|
||||||
|
|
||||||
|
<div *ngIf="types && types.length > 0" class="mb-4">
|
||||||
|
|
||||||
|
{{'virtual-metadata.delete-item.info' | translate}}
|
||||||
|
|
||||||
|
<div *ngFor="let type of types" class="mb-4">
|
||||||
|
|
||||||
|
<div *ngVar="(isSelected(type) | async) as selected"
|
||||||
|
class="d-flex flex-row">
|
||||||
|
|
||||||
|
<div class="m-2" (click)="setSelected(type, !selected)">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" [checked]="selected">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-column flex-grow-1">
|
||||||
|
<h5 (click)="setSelected(type, !selected)">
|
||||||
|
{{getRelationshipMessageKey(getLabel(type) | async) | translate}}
|
||||||
|
</h5>
|
||||||
|
<div *ngFor="let relationship of (getRelationships(type) | async)"
|
||||||
|
class="d-flex flex-row">
|
||||||
|
<ng-container *ngVar="(getRelatedItem(relationship) | async) as relatedItem">
|
||||||
|
|
||||||
|
<ds-listable-object-component-loader
|
||||||
|
*ngIf="relatedItem"
|
||||||
|
[object]="relatedItem"
|
||||||
|
[viewMode]="viewMode">
|
||||||
|
</ds-listable-object-component-loader>
|
||||||
|
<div class="ml-auto">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-outline-info btn-sm"
|
||||||
|
(click)="openVirtualMetadataModal(virtualMetadataModal)">
|
||||||
|
<i class="fas fa-info fa-fw"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #virtualMetadataModal>
|
||||||
|
<div>
|
||||||
|
<div class="modal-header">
|
||||||
|
{{'virtual-metadata.delete-item.modal-head' | translate}}
|
||||||
|
<button type="button" class="close"
|
||||||
|
(click)="closeVirtualMetadataModal()" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<ds-listable-object-component-loader
|
||||||
|
*ngIf="relatedItem"
|
||||||
|
[object]="relatedItem"
|
||||||
|
[viewMode]="viewMode">
|
||||||
|
</ds-listable-object-component-loader>
|
||||||
|
<div *ngFor="let metadata of (getVirtualMetadata(relationship) | async)">
|
||||||
|
<div>
|
||||||
|
<div class="font-weight-bold">
|
||||||
|
{{metadata.metadataField}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{metadata.metadataValue.value}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<button (click)="performAction()"
|
||||||
|
class="btn btn-outline-secondary perform-action">{{confirmMessage | translate}}
|
||||||
|
</button>
|
||||||
|
<button [routerLink]="['/items/', item.id, 'edit']" class="btn btn-outline-secondary cancel">
|
||||||
|
{{cancelMessage| translate}}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -1,44 +1,132 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
|
||||||
import { RouterStub } from '../../../shared/testing/router-stub';
|
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||||
import { of as observableOf } from 'rxjs';
|
import {Item} from '../../../core/shared/item.model';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import {RouterStub} from '../../../shared/testing/router-stub';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
import {of as observableOf} from 'rxjs';
|
||||||
import { CommonModule } from '@angular/common';
|
import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub';
|
||||||
import { FormsModule } from '@angular/forms';
|
import {CommonModule} from '@angular/common';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import {FormsModule} from '@angular/forms';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import {RouterTestingModule} from '@angular/router/testing';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import {TranslateModule} from '@ngx-translate/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import {ItemDataService} from '../../../core/data/item-data.service';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
import { By } from '@angular/platform-browser';
|
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||||
import { ItemDeleteComponent } from './item-delete.component';
|
import {By} from '@angular/platform-browser';
|
||||||
import { getItemEditPath } from '../../item-page-routing.module';
|
import {ItemDeleteComponent} from './item-delete.component';
|
||||||
import { RestResponse } from '../../../core/cache/response.models';
|
import {getItemEditPath} from '../../item-page-routing.module';
|
||||||
import { createSuccessfulRemoteDataObject } from '../../../shared/testing/utils';
|
import {createSuccessfulRemoteDataObject} from '../../../shared/testing/utils';
|
||||||
|
import {VarDirective} from '../../../shared/utils/var.directive';
|
||||||
|
import {ObjectUpdatesService} from '../../../core/data/object-updates/object-updates.service';
|
||||||
|
import {RelationshipService} from '../../../core/data/relationship.service';
|
||||||
|
import {RelationshipType} from '../../../core/shared/item-relationships/relationship-type.model';
|
||||||
|
import {RemoteData} from '../../../core/data/remote-data';
|
||||||
|
import {PaginatedList} from '../../../core/data/paginated-list';
|
||||||
|
import {PageInfo} from '../../../core/shared/page-info.model';
|
||||||
|
import {EntityTypeService} from '../../../core/data/entity-type.service';
|
||||||
|
|
||||||
let comp: ItemDeleteComponent;
|
let comp: ItemDeleteComponent;
|
||||||
let fixture: ComponentFixture<ItemDeleteComponent>;
|
let fixture: ComponentFixture<ItemDeleteComponent>;
|
||||||
|
|
||||||
let mockItem;
|
let mockItem;
|
||||||
|
let itemType;
|
||||||
|
let type1;
|
||||||
|
let type2;
|
||||||
|
let types;
|
||||||
|
let relationships;
|
||||||
let itemPageUrl;
|
let itemPageUrl;
|
||||||
let routerStub;
|
let routerStub;
|
||||||
let mockItemDataService: ItemDataService;
|
let mockItemDataService: ItemDataService;
|
||||||
let routeStub;
|
let routeStub;
|
||||||
|
let objectUpdatesServiceStub;
|
||||||
|
let relationshipService;
|
||||||
|
let entityTypeService;
|
||||||
let notificationsServiceStub;
|
let notificationsServiceStub;
|
||||||
|
let typesSelection;
|
||||||
|
|
||||||
describe('ItemDeleteComponent', () => {
|
describe('ItemDeleteComponent', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
|
||||||
mockItem = Object.assign(new Item(), {
|
mockItem = Object.assign(new Item(), {
|
||||||
id: 'fake-id',
|
id: 'fake-id',
|
||||||
|
uuid: 'fake-uuid',
|
||||||
handle: 'fake/handle',
|
handle: 'fake/handle',
|
||||||
lastModified: '2018',
|
lastModified: '2018',
|
||||||
isWithdrawn: true
|
isWithdrawn: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
itemType = Object.assign(new ItemType(), {
|
||||||
|
id: 'itemType',
|
||||||
|
uuid: 'itemType',
|
||||||
|
});
|
||||||
|
|
||||||
|
type1 = Object.assign(new RelationshipType(), {
|
||||||
|
id: '1',
|
||||||
|
uuid: 'type-1',
|
||||||
|
});
|
||||||
|
|
||||||
|
type2 = Object.assign(new RelationshipType(), {
|
||||||
|
id: '2',
|
||||||
|
uuid: 'type-2',
|
||||||
|
});
|
||||||
|
|
||||||
|
types = [type1, type2];
|
||||||
|
|
||||||
|
relationships = [
|
||||||
|
Object.assign(new Relationship(), {
|
||||||
|
id: '1',
|
||||||
|
uuid: 'relationship-1',
|
||||||
|
relationshipType: observableOf(new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
type1
|
||||||
|
)),
|
||||||
|
leftItem: observableOf(new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
mockItem,
|
||||||
|
)),
|
||||||
|
rightItem: observableOf(new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
Object.assign(new Item(), {})
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
Object.assign(new Relationship(), {
|
||||||
|
id: '2',
|
||||||
|
uuid: 'relationship-2',
|
||||||
|
relationshipType: observableOf(new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
type2
|
||||||
|
)),
|
||||||
|
leftItem: observableOf(new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
mockItem,
|
||||||
|
)),
|
||||||
|
rightItem: observableOf(new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
Object.assign(new Item(), {})
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
itemPageUrl = `fake-url/${mockItem.id}`;
|
itemPageUrl = `fake-url/${mockItem.id}`;
|
||||||
routerStub = Object.assign(new RouterStub(), {
|
routerStub = Object.assign(new RouterStub(), {
|
||||||
url: `${itemPageUrl}/edit`
|
url: `${itemPageUrl}/edit`
|
||||||
@@ -54,16 +142,56 @@ describe('ItemDeleteComponent', () => {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typesSelection = {
|
||||||
|
type1: false,
|
||||||
|
type2: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
entityTypeService = jasmine.createSpyObj('entityTypeService',
|
||||||
|
{
|
||||||
|
getEntityTypeByLabel: observableOf(new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
itemType,
|
||||||
|
)),
|
||||||
|
getEntityTypeRelationships: observableOf(new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
null,
|
||||||
|
new PaginatedList(new PageInfo(), types),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
objectUpdatesServiceStub = {
|
||||||
|
initialize: () => {
|
||||||
|
// do nothing
|
||||||
|
},
|
||||||
|
isSelectedVirtualMetadata: (type) => observableOf(typesSelection[type]),
|
||||||
|
};
|
||||||
|
|
||||||
|
relationshipService = jasmine.createSpyObj('relationshipService',
|
||||||
|
{
|
||||||
|
getItemRelationshipsArray: observableOf(relationships),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
notificationsServiceStub = new NotificationsServiceStub();
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
|
||||||
declarations: [ItemDeleteComponent],
|
declarations: [ItemDeleteComponent, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: routeStub },
|
{ provide: ActivatedRoute, useValue: routeStub },
|
||||||
{ provide: Router, useValue: routerStub },
|
{ provide: Router, useValue: routerStub },
|
||||||
{ provide: ItemDataService, useValue: mockItemDataService },
|
{ provide: ItemDataService, useValue: mockItemDataService },
|
||||||
{ provide: NotificationsService, useValue: notificationsServiceStub },
|
{ provide: NotificationsService, useValue: notificationsServiceStub },
|
||||||
|
{ provide: ObjectUpdatesService, useValue: objectUpdatesServiceStub },
|
||||||
|
{ provide: RelationshipService, useValue: relationshipService },
|
||||||
|
{ provide: EntityTypeService, useValue: entityTypeService },
|
||||||
], schemas: [
|
], schemas: [
|
||||||
CUSTOM_ELEMENTS_SCHEMA
|
CUSTOM_ELEMENTS_SCHEMA
|
||||||
]
|
]
|
||||||
@@ -91,7 +219,8 @@ describe('ItemDeleteComponent', () => {
|
|||||||
it('should call delete function from the ItemDataService', () => {
|
it('should call delete function from the ItemDataService', () => {
|
||||||
spyOn(comp, 'notify');
|
spyOn(comp, 'notify');
|
||||||
comp.performAction();
|
comp.performAction();
|
||||||
expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem);
|
expect(mockItemDataService.delete)
|
||||||
|
.toHaveBeenCalledWith(mockItem, types.filter((type) => typesSelection[type]).map((type) => type.id));
|
||||||
expect(comp.notify).toHaveBeenCalled();
|
expect(comp.notify).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,29 +1,323 @@
|
|||||||
import { Component } from '@angular/core';
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
import { first } from 'rxjs/operators';
|
import {filter, first, map, switchMap, take} from 'rxjs/operators';
|
||||||
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
|
import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component';
|
||||||
import { getItemEditPath } from '../../item-page-routing.module';
|
import {getItemEditPath} from '../../item-page-routing.module';
|
||||||
import { RestResponse } from '../../../core/cache/response.models';
|
import {NgbModal, NgbModalRef} from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import {combineLatest as observableCombineLatest, combineLatest, Observable} from 'rxjs';
|
||||||
|
import {RelationshipType} from '../../../core/shared/item-relationships/relationship-type.model';
|
||||||
|
import {VirtualMetadata} from '../virtual-metadata/virtual-metadata.component';
|
||||||
|
import {Relationship} from '../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import {getRemoteDataPayload, getSucceededRemoteData} from '../../../core/shared/operators';
|
||||||
|
import {hasValue, isNotEmpty} from '../../../shared/empty.util';
|
||||||
|
import {Item} from '../../../core/shared/item.model';
|
||||||
|
import {MetadataValue} from '../../../core/shared/metadata.models';
|
||||||
|
import {ViewMode} from '../../../core/shared/view-mode.model';
|
||||||
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
|
import {NotificationsService} from '../../../shared/notifications/notifications.service';
|
||||||
|
import {ItemDataService} from '../../../core/data/item-data.service';
|
||||||
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
|
import {ObjectUpdatesService} from '../../../core/data/object-updates/object-updates.service';
|
||||||
|
import {RelationshipService} from '../../../core/data/relationship.service';
|
||||||
|
import {EntityTypeService} from '../../../core/data/entity-type.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-delete',
|
selector: 'ds-item-delete',
|
||||||
templateUrl: '../simple-item-action/abstract-simple-item-action.component.html'
|
templateUrl: '../item-delete/item-delete.component.html'
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Component responsible for rendering the item delete page
|
* Component responsible for rendering the item delete page
|
||||||
*/
|
*/
|
||||||
export class ItemDeleteComponent extends AbstractSimpleItemActionComponent {
|
export class ItemDeleteComponent
|
||||||
|
extends AbstractSimpleItemActionComponent
|
||||||
|
implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current url of this page
|
||||||
|
*/
|
||||||
|
@Input() url: string;
|
||||||
|
|
||||||
protected messageKey = 'delete';
|
protected messageKey = 'delete';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform the delete action to the item
|
* The view-mode we're currently on
|
||||||
|
*/
|
||||||
|
viewMode = ViewMode.ListElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of the relationship types for which this item has relations as an observable.
|
||||||
|
* The list doesn't contain duplicates.
|
||||||
|
*/
|
||||||
|
types$: Observable<RelationshipType[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map which stores the relationships of this item for each type as observable lists
|
||||||
|
*/
|
||||||
|
relationships$: Map<RelationshipType, Observable<Relationship[]>>
|
||||||
|
= new Map<RelationshipType, Observable<Relationship[]>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map which stores the related item of each relationship of this item as an observable
|
||||||
|
*/
|
||||||
|
relatedItems$: Map<Relationship, Observable<Item>> = new Map<Relationship, Observable<Item>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map which stores the virtual metadata (of the related) item corresponding to each relationship of this item
|
||||||
|
* as an observable list
|
||||||
|
*/
|
||||||
|
virtualMetadata$: Map<Relationship, Observable<VirtualMetadata[]>> = new Map<Relationship, Observable<VirtualMetadata[]>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to NgbModal
|
||||||
|
*/
|
||||||
|
public modalRef: NgbModalRef;
|
||||||
|
|
||||||
|
constructor(protected route: ActivatedRoute,
|
||||||
|
protected router: Router,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected itemDataService: ItemDataService,
|
||||||
|
protected translateService: TranslateService,
|
||||||
|
protected modalService: NgbModal,
|
||||||
|
protected objectUpdatesService: ObjectUpdatesService,
|
||||||
|
protected relationshipService: RelationshipService,
|
||||||
|
protected entityTypeService: EntityTypeService,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
route,
|
||||||
|
router,
|
||||||
|
notificationsService,
|
||||||
|
itemDataService,
|
||||||
|
translateService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up and initialize all fields
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
|
||||||
|
super.ngOnInit();
|
||||||
|
this.url = this.router.url;
|
||||||
|
|
||||||
|
this.types$ = this.entityTypeService.getEntityTypeByLabel(
|
||||||
|
this.item.firstMetadataValue('relationship.type')
|
||||||
|
).pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
switchMap((entityType) => this.entityTypeService.getEntityTypeRelationships(entityType.id)),
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((relationshipTypes) => relationshipTypes.page),
|
||||||
|
switchMap((types) =>
|
||||||
|
combineLatest(types.map((type) => this.getRelationships(type))).pipe(
|
||||||
|
map((relationships) =>
|
||||||
|
types.reduce<RelationshipType[]>((includedTypes, type, index) => {
|
||||||
|
if (!includedTypes.some((includedType) => includedType.id === type.id)
|
||||||
|
&& !(relationships[index].length === 0)) {
|
||||||
|
return [...includedTypes, type];
|
||||||
|
} else {
|
||||||
|
return includedTypes;
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.types$.pipe(
|
||||||
|
take(1),
|
||||||
|
).subscribe((types) =>
|
||||||
|
this.objectUpdatesService.initialize(this.url, types, this.item.lastModified)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the modal which lists the virtual metadata of a relation
|
||||||
|
* @param content the html content of the modal
|
||||||
|
*/
|
||||||
|
openVirtualMetadataModal(content: any) {
|
||||||
|
this.modalRef = this.modalService.open(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the modal which lists the virtual metadata of a relation
|
||||||
|
*/
|
||||||
|
closeVirtualMetadataModal() {
|
||||||
|
this.modalRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the i18n message key for a relationship
|
||||||
|
* @param label The relationship type's label
|
||||||
|
*/
|
||||||
|
getRelationshipMessageKey(label: string): string {
|
||||||
|
if (hasValue(label) && label.indexOf('Of') > -1) {
|
||||||
|
return `relationships.${label.substring(0, label.indexOf('Of') + 2)}`
|
||||||
|
} else {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the relationship type label relevant for this item as an observable
|
||||||
|
* @param relationshipType the relationship type to get the label for
|
||||||
|
*/
|
||||||
|
getLabel(relationshipType: RelationshipType): Observable<string> {
|
||||||
|
|
||||||
|
return this.getRelationships(relationshipType).pipe(
|
||||||
|
switchMap((relationships) =>
|
||||||
|
this.isLeftItem(relationships[0]).pipe(
|
||||||
|
map((isLeftItem) => isLeftItem ? relationshipType.leftwardType : relationshipType.rightwardType),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the relationships of this item with a given type as an observable
|
||||||
|
* @param relationshipType the relationship type to filter the item's relationships on
|
||||||
|
*/
|
||||||
|
getRelationships(relationshipType: RelationshipType): Observable<Relationship[]> {
|
||||||
|
|
||||||
|
if (!this.relationships$.has(relationshipType)) {
|
||||||
|
this.relationships$.set(
|
||||||
|
relationshipType,
|
||||||
|
this.relationshipService.getItemRelationshipsArray(this.item).pipe(
|
||||||
|
// filter on type
|
||||||
|
switchMap((relationships) =>
|
||||||
|
observableCombineLatest(
|
||||||
|
relationships.map((relationship) => this.getRelationshipType(relationship))
|
||||||
|
).pipe(
|
||||||
|
map((types) => relationships.filter(
|
||||||
|
(relationship, index) => relationshipType.id === types[index].id
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.relationships$.get(relationshipType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of a given relationship as an observable
|
||||||
|
* @param relationship the relationship to get the type for
|
||||||
|
*/
|
||||||
|
private getRelationshipType(relationship: Relationship): Observable<RelationshipType> {
|
||||||
|
|
||||||
|
return relationship.relationshipType.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
filter((relationshipType: RelationshipType) => hasValue(relationshipType) && isNotEmpty(relationshipType.uuid))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the item this item is related to through a given relationship as an observable
|
||||||
|
* @param relationship the relationship to get the other item for
|
||||||
|
*/
|
||||||
|
getRelatedItem(relationship: Relationship): Observable<Item> {
|
||||||
|
|
||||||
|
if (!this.relatedItems$.has(relationship)) {
|
||||||
|
|
||||||
|
this.relatedItems$.set(
|
||||||
|
relationship,
|
||||||
|
this.isLeftItem(relationship).pipe(
|
||||||
|
switchMap((isLeftItem) => isLeftItem ? relationship.rightItem : relationship.leftItem),
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.relatedItems$.get(relationship);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the virtual metadata for a given relationship of the related item.
|
||||||
|
* @param relationship the relationship to get the virtual metadata for
|
||||||
|
*/
|
||||||
|
getVirtualMetadata(relationship: Relationship): Observable<VirtualMetadata[]> {
|
||||||
|
|
||||||
|
if (!this.virtualMetadata$.has(relationship)) {
|
||||||
|
|
||||||
|
this.virtualMetadata$.set(
|
||||||
|
relationship,
|
||||||
|
this.getRelatedItem(relationship).pipe(
|
||||||
|
map((relatedItem) =>
|
||||||
|
Object.entries(relatedItem.metadata)
|
||||||
|
.map(([key, value]) => value
|
||||||
|
.filter((metadata: MetadataValue) =>
|
||||||
|
metadata.authority && metadata.authority.endsWith(relationship.id))
|
||||||
|
.map((metadata: MetadataValue) => {
|
||||||
|
return {
|
||||||
|
metadataField: key,
|
||||||
|
metadataValue: metadata,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.reduce((previous, current) => previous.concat(current))
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.virtualMetadata$.get(relationship);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether this item is the left item of a given relationship, as an observable boolean
|
||||||
|
* @param relationship the relationship for which to check whether this item is the left item
|
||||||
|
*/
|
||||||
|
private isLeftItem(relationship: Relationship): Observable<boolean> {
|
||||||
|
|
||||||
|
return relationship.leftItem.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid)),
|
||||||
|
map((leftItem) => leftItem.uuid === this.item.uuid)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a given relationship type is selected to save the corresponding virtual metadata
|
||||||
|
* @param type the relationship type for which to check whether it is selected
|
||||||
|
*/
|
||||||
|
isSelected(type: RelationshipType): Observable<boolean> {
|
||||||
|
return this.objectUpdatesService.isSelectedVirtualMetadata(this.url, this.item.uuid, type.uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select/deselect a given relationship type to save the corresponding virtual metadata
|
||||||
|
* @param type the relationship type to select/deselect
|
||||||
|
* @param selected whether the type should be selected
|
||||||
|
*/
|
||||||
|
setSelected(type: RelationshipType, selected: boolean): void {
|
||||||
|
this.objectUpdatesService.setSelectedVirtualMetadata(this.url, this.item.uuid, type.uuid, selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the delete operation
|
||||||
*/
|
*/
|
||||||
performAction() {
|
performAction() {
|
||||||
this.itemDataService.delete(this.item).pipe(first()).subscribe(
|
|
||||||
(succeeded: boolean) => {
|
this.types$.pipe(
|
||||||
this.notify(succeeded);
|
switchMap((types) =>
|
||||||
}
|
combineLatest(
|
||||||
);
|
types.map((type) => this.isSelected(type))
|
||||||
|
).pipe(
|
||||||
|
map((selection) => types.filter(
|
||||||
|
(type, index) => selection[index]
|
||||||
|
)),
|
||||||
|
map((selectedTypes) => selectedTypes.map((type) => type.id)),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
).subscribe((types) => {
|
||||||
|
this.itemDataService.delete(this.item, types).pipe(first()).subscribe(
|
||||||
|
(succeeded: boolean) => {
|
||||||
|
this.notify(succeeded);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -318,9 +318,11 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
/**
|
/**
|
||||||
* Delete an existing DSpace Object on the server
|
* Delete an existing DSpace Object on the server
|
||||||
* @param dso The DSpace Object to be removed
|
* @param dso The DSpace Object to be removed
|
||||||
* Return an observable that emits true when the deletion was successful, false when it failed
|
* @param copyVirtualMetadata (optional parameter) the identifiers of the relationship types for which the virtual
|
||||||
|
* metadata should be saved as real metadata
|
||||||
|
* @return an observable that emits true when the deletion was successful, false when it failed
|
||||||
*/
|
*/
|
||||||
delete(dso: T): Observable<boolean> {
|
delete(dso: T, copyVirtualMetadata?: string[]): Observable<boolean> {
|
||||||
const requestId = this.requestService.generateRequestId();
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
|
||||||
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
|
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
@@ -329,6 +331,13 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
hrefObs.pipe(
|
hrefObs.pipe(
|
||||||
find((href: string) => hasValue(href)),
|
find((href: string) => hasValue(href)),
|
||||||
map((href: string) => {
|
map((href: string) => {
|
||||||
|
if (copyVirtualMetadata) {
|
||||||
|
copyVirtualMetadata.forEach((id) =>
|
||||||
|
href += (href.includes('?') ? '&' : '?')
|
||||||
|
+ 'copyVirtualMetadata='
|
||||||
|
+ id
|
||||||
|
);
|
||||||
|
}
|
||||||
const request = new DeleteByIDRequest(requestId, href, dso.uuid);
|
const request = new DeleteByIDRequest(requestId, href, dso.uuid);
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
})
|
})
|
||||||
|
@@ -1,35 +1,47 @@
|
|||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
|
||||||
import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
|
|
||||||
import { compareArraysUsingIds, paginatedRelationsToItems, relationsToItems } from '../../+item-page/simple/item-types/shared/item-relationships-utils';
|
|
||||||
import { AppState, keySelector } from '../../app.reducer';
|
|
||||||
import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
|
||||||
import { ReorderableRelationship } from '../../shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
|
import { ReorderableRelationship } from '../../shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
|
||||||
import { RemoveNameVariantAction, SetNameVariantAction } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
|
import { RequestService } from './request.service';
|
||||||
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
|
||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { configureRequest, getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators';
|
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
|
||||||
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
|
|
||||||
import { RestResponse } from '../cache/response.models';
|
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
|
import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
configureRequest,
|
||||||
|
getRemoteDataPayload,
|
||||||
|
getResponseFromEntry,
|
||||||
|
getSucceededRemoteData
|
||||||
|
} from '../shared/operators';
|
||||||
|
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RestResponse } from '../cache/response.models';
|
||||||
|
import { Item } from '../shared/item.model';
|
||||||
|
import { Relationship } from '../shared/item-relationships/relationship.model';
|
||||||
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
||||||
import { RemoteData, RemoteDataState } from './remote-data';
|
import { RemoteData, RemoteDataState } from './remote-data';
|
||||||
|
import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
import { ItemDataService } from './item-data.service';
|
import { ItemDataService } from './item-data.service';
|
||||||
import { Relationship } from '../shared/item-relationships/relationship.model';
|
import {
|
||||||
import { Item } from '../shared/item.model';
|
compareArraysUsingIds,
|
||||||
|
paginatedRelationsToItems,
|
||||||
|
relationsToItems
|
||||||
|
} from '../../+item-page/simple/item-types/shared/item-relationships-utils';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
|
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
import { RequestService } from './request.service';
|
import { SearchParam } from '../cache/models/search-param.model';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
|
import { AppState, keySelector } from '../../app.reducer';
|
||||||
|
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
||||||
|
import {
|
||||||
|
RemoveNameVariantAction,
|
||||||
|
SetNameVariantAction
|
||||||
|
} from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
|
||||||
|
|
||||||
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
|
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user