mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-16 06:23:03 +00:00
Merge branch 'w2p-55693_Item-mapping-to-collections' into w2p-55946_Item-mapping-on-item-level
Conflicts: src/app/core/data/item-data.service.ts
This commit is contained in:
@@ -19,18 +19,31 @@
|
|||||||
"collection": "Collection: \"<b>{{name}}</b>\"",
|
"collection": "Collection: \"<b>{{name}}</b>\"",
|
||||||
"description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.",
|
"description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.",
|
||||||
"confirm": "Map selected items",
|
"confirm": "Map selected items",
|
||||||
|
"remove": "Remove selected item mappings",
|
||||||
"tabs": {
|
"tabs": {
|
||||||
"browse": "Browse",
|
"browse": "Browse",
|
||||||
"map": "Map"
|
"map": "Map"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"success": {
|
"map": {
|
||||||
"head": "Mapping completed",
|
"success": {
|
||||||
"content": "Successfully mapped {{amount}} items."
|
"head": "Mapping completed",
|
||||||
|
"content": "Successfully mapped {{amount}} items."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"head": "Mapping errors",
|
||||||
|
"content": "Errors occurred for mapping of {{amount}} items."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"unmap": {
|
||||||
"head": "Mapping errors",
|
"success": {
|
||||||
"content": "Errors occurred for mapping of {{amount}} items."
|
"head": "Remove mapping completed",
|
||||||
|
"content": "Successfully removed the mappings of {{amount}} items."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"head": "Remove mapping errors",
|
||||||
|
"content": "Errors occurred for removing the mappings of {{amount}} items."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"return": "Return"
|
"return": "Return"
|
||||||
|
@@ -19,12 +19,12 @@
|
|||||||
<ngb-tab title="{{'collection.item-mapper.tabs.browse' | translate}}">
|
<ngb-tab title="{{'collection.item-mapper.tabs.browse' | translate}}">
|
||||||
<ng-template ngbTabContent>
|
<ng-template ngbTabContent>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<ds-viewable-collection
|
<ds-item-select class="mt-2"
|
||||||
[config]="(searchOptions$ | async)?.pagination"
|
[dsoRD$]="collectionItemsRD$"
|
||||||
[sortConfig]="(searchOptions$ | async)?.sort"
|
[paginationOptions]="(searchOptions$ | async)?.pagination"
|
||||||
[objects]="collectionItemsRD$ | async"
|
[confirmButton]="'collection.item-mapper.remove'"
|
||||||
[hideGear]="true">
|
[hideCollection]="true"
|
||||||
</ds-viewable-collection>
|
(confirm)="mapItems($event, true)"></ds-item-select>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-tab>
|
</ngb-tab>
|
||||||
|
@@ -17,6 +17,8 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import { ItemDataService } from '../../core/data/item-data.service';
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
import { RestResponse } from '../../core/cache/response-cache.models';
|
import { RestResponse } from '../../core/cache/response-cache.models';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-item-mapper',
|
selector: 'ds-collection-item-mapper',
|
||||||
@@ -67,6 +69,7 @@ export class CollectionItemMapperComponent implements OnInit {
|
|||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
private itemDataService: ItemDataService,
|
private itemDataService: ItemDataService,
|
||||||
|
private collectionDataService: CollectionDataService,
|
||||||
private translateService: TranslateService) {
|
private translateService: TranslateService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,17 +91,16 @@ export class CollectionItemMapperComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
this.collectionItemsRD$ = collectionAndOptions$.pipe(
|
this.collectionItemsRD$ = collectionAndOptions$.pipe(
|
||||||
switchMap(([collectionRD, options]) => {
|
switchMap(([collectionRD, options]) => {
|
||||||
return this.searchService.search(Object.assign(options, {
|
return this.collectionDataService.getMappedItems(collectionRD.payload.id, Object.assign(options, {
|
||||||
scope: collectionRD.payload.id,
|
|
||||||
dsoType: DSpaceObjectType.ITEM,
|
|
||||||
sort: this.defaultSortOptions
|
sort: this.defaultSortOptions
|
||||||
}));
|
}))
|
||||||
}),
|
})
|
||||||
toDSpaceObjectListRD()
|
|
||||||
);
|
);
|
||||||
this.mappingItemsRD$ = this.searchOptions$.pipe(
|
this.mappingItemsRD$ = collectionAndOptions$.pipe(
|
||||||
flatMap((options: PaginatedSearchOptions) => {
|
switchMap(([collectionRD, options]) => {
|
||||||
return this.searchService.search(Object.assign(options, {
|
return this.searchService.search(Object.assign(options, {
|
||||||
|
// TODO: Exclude items already mapped to collection without overwriting search query
|
||||||
|
// query: `-location.coll:\"${collectionRD.payload.id}\"`,
|
||||||
scope: undefined,
|
scope: undefined,
|
||||||
dsoType: DSpaceObjectType.ITEM,
|
dsoType: DSpaceObjectType.ITEM,
|
||||||
sort: this.defaultSortOptions
|
sort: this.defaultSortOptions
|
||||||
@@ -109,23 +111,30 @@ export class CollectionItemMapperComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map the selected items to the collection and display notifications
|
* Map/Unmap the selected items to the collection and display notifications
|
||||||
* @param {string[]} ids The list of item UUID's to map to the collection
|
* @param ids The list of item UUID's to map/unmap to the collection
|
||||||
|
* @param remove Whether or not it's supposed to remove mappings
|
||||||
*/
|
*/
|
||||||
mapItems(ids: string[]) {
|
mapItems(ids: string[], remove?: boolean) {
|
||||||
const responses$ = this.collectionRD$.pipe(
|
const responses$ = this.collectionRD$.pipe(
|
||||||
getSucceededRemoteData(),
|
getSucceededRemoteData(),
|
||||||
map((collectionRD: RemoteData<Collection>) => collectionRD.payload.id),
|
map((collectionRD: RemoteData<Collection>) => collectionRD.payload.id),
|
||||||
switchMap((collectionId: string) => Observable.combineLatest(ids.map((id: string) => this.itemDataService.mapToCollection(id, collectionId))))
|
switchMap((collectionId: string) =>
|
||||||
|
Observable.combineLatest(ids.map((id: string) =>
|
||||||
|
remove ? this.itemDataService.removeMappingFromCollection(id, collectionId) : this.itemDataService.mapToCollection(id, collectionId)
|
||||||
|
))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const messageInsertion = remove ? 'unmap' : 'map';
|
||||||
|
|
||||||
responses$.subscribe((responses: RestResponse[]) => {
|
responses$.subscribe((responses: RestResponse[]) => {
|
||||||
const successful = responses.filter((response: RestResponse) => response.isSuccessful);
|
const successful = responses.filter((response: RestResponse) => response.isSuccessful);
|
||||||
const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful);
|
const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful);
|
||||||
if (successful.length > 0) {
|
if (successful.length > 0) {
|
||||||
const successMessages = Observable.combineLatest(
|
const successMessages = Observable.combineLatest(
|
||||||
this.translateService.get('collection.item-mapper.notifications.success.head'),
|
this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.head`),
|
||||||
this.translateService.get('collection.item-mapper.notifications.success.content', { amount: successful.length })
|
this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.content`, { amount: successful.length })
|
||||||
);
|
);
|
||||||
|
|
||||||
successMessages.subscribe(([head, content]) => {
|
successMessages.subscribe(([head, content]) => {
|
||||||
@@ -134,8 +143,8 @@ export class CollectionItemMapperComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
if (unsuccessful.length > 0) {
|
if (unsuccessful.length > 0) {
|
||||||
const unsuccessMessages = Observable.combineLatest(
|
const unsuccessMessages = Observable.combineLatest(
|
||||||
this.translateService.get('collection.item-mapper.notifications.error.head'),
|
this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.head`),
|
||||||
this.translateService.get('collection.item-mapper.notifications.error.content', { amount: unsuccessful.length })
|
this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.content`, { amount: unsuccessful.length })
|
||||||
);
|
);
|
||||||
|
|
||||||
unsuccessMessages.subscribe(([head, content]) => {
|
unsuccessMessages.subscribe(([head, content]) => {
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { NormalizedCollection } from '../cache/models/normalized-collection.model';
|
import { NormalizedCollection } from '../cache/models/normalized-collection.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
@@ -11,6 +10,20 @@ import { ComColDataService } from './comcol-data.service';
|
|||||||
import { CommunityDataService } from './community-data.service';
|
import { CommunityDataService } from './community-data.service';
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { RemoteData } from './remote-data';
|
||||||
|
import { PaginatedList } from './paginated-list';
|
||||||
|
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
|
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
|
import { GetRequest } from './request.models';
|
||||||
|
import {
|
||||||
|
configureRequest
|
||||||
|
} from '../shared/operators';
|
||||||
|
import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model';
|
||||||
|
import { GenericConstructor } from '../shared/generic-constructor';
|
||||||
|
import { ResponseParsingService } from './parsing.service';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
|
import { DSOResponseParsingService } from './dso-response-parsing.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
|
export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
|
||||||
@@ -27,4 +40,34 @@ export class CollectionDataService extends ComColDataService<NormalizedCollectio
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getMappingItemsEndpoint(collectionId): Observable<string> {
|
||||||
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
|
map((endpoint: string) => this.getFindByIDHref(endpoint, collectionId)),
|
||||||
|
map((endpoint: string) => `${endpoint}/mappingItems`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMappedItems(collectionId: string, searchOptions?: PaginatedSearchOptions): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
|
||||||
|
const href$ = this.getMappingItemsEndpoint(collectionId).pipe(
|
||||||
|
isNotEmptyOperator(),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint)
|
||||||
|
);
|
||||||
|
|
||||||
|
href$.pipe(
|
||||||
|
map((endpoint: string) => {
|
||||||
|
const request = new GetRequest(this.requestService.generateRequestId(), endpoint);
|
||||||
|
return Object.assign(request, {
|
||||||
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
|
return DSOResponseParsingService;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
configureRequest(this.requestService)
|
||||||
|
).subscribe();
|
||||||
|
|
||||||
|
return this.rdbService.buildList(href$);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -75,6 +75,7 @@ export class ObjectCollectionComponent implements OnChanges, OnInit {
|
|||||||
this.currentMode = params.view;
|
this.currentMode = params.view;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.log(this.objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th scope="col">{{'item.select.table.collection' | translate}}</th>
|
<th *ngIf="!hideCollection" scope="col">{{'item.select.table.collection' | translate}}</th>
|
||||||
<th scope="col">{{'item.select.table.author' | translate}}</th>
|
<th scope="col">{{'item.select.table.author' | translate}}</th>
|
||||||
<th scope="col">{{'item.select.table.title' | translate}}</th>
|
<th scope="col">{{'item.select.table.title' | translate}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let item of itemsRD?.payload?.page">
|
<tr *ngFor="let item of itemsRD?.payload?.page">
|
||||||
<td><input class="item-checkbox" [ngModel]="getSelected(item.id) | async" (change)="switch(item.id)" type="checkbox" name="{{item.id}}"></td>
|
<td><input class="item-checkbox" [ngModel]="getSelected(item.id) | async" (change)="switch(item.id)" type="checkbox" name="{{item.id}}"></td>
|
||||||
<td><a [routerLink]="['/items', item.id]">{{(item.owningCollection | async)?.payload?.name}}</a></td>
|
<td *ngIf="!hideCollection"><a [routerLink]="['/items', item.id]">{{(item.owningCollection | async)?.payload?.name}}</a></td>
|
||||||
<td><a *ngIf="item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0" [routerLink]="['/items', item.id]">{{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}}</a></td>
|
<td><a *ngIf="item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*']).length > 0" [routerLink]="['/items', item.id]">{{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}}</a></td>
|
||||||
<td><a [routerLink]="['/items', item.id]">{{item.findMetadata("dc.title")}}</a></td>
|
<td><a [routerLink]="['/items', item.id]">{{item.findMetadata("dc.title")}}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@@ -30,6 +30,9 @@ export abstract class ObjectSelectComponent<TDomain> implements OnInit, OnDestro
|
|||||||
@Input()
|
@Input()
|
||||||
confirmButton: string;
|
confirmButton: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
hideCollection = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EventEmitter to return the selected UUIDs when the confirm button is pressed
|
* EventEmitter to return the selected UUIDs when the confirm button is pressed
|
||||||
* @type {EventEmitter<string[]>}
|
* @type {EventEmitter<string[]>}
|
||||||
|
Reference in New Issue
Block a user