mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
55693: Mapped items using new REST endpoint + item-select component
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
"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"
|
||||||
|
@@ -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"
|
[itemsRD$]="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)="unmapItems($event)"></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,13 +91,10 @@ 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$ = this.searchOptions$.pipe(
|
||||||
flatMap((options: PaginatedSearchOptions) => {
|
flatMap((options: PaginatedSearchOptions) => {
|
||||||
@@ -145,6 +145,14 @@ export class CollectionItemMapperComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the mapping for the selected items to the collection and display notifications
|
||||||
|
* @param {string[]} ids The list of item UUID's to remove the mapping to the collection
|
||||||
|
*/
|
||||||
|
unmapItems(ids: string[]) {
|
||||||
|
// TODO: Functionality for unmapping items
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear url parameters on tab change (temporary fix until pagination is improved)
|
* Clear url parameters on tab change (temporary fix until pagination is improved)
|
||||||
* @param event
|
* @param event
|
||||||
|
@@ -65,6 +65,7 @@ import { UploaderService } from '../shared/uploader/uploader.service';
|
|||||||
import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service';
|
import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service';
|
||||||
import { DSpaceObjectDataService } from './data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from './data/dspace-object-data.service';
|
||||||
import { ItemSelectService } from '../shared/item-select/item-select.service';
|
import { ItemSelectService } from '../shared/item-select/item-select.service';
|
||||||
|
import { MappingItemsResponseParsingService } from './data/mapping-items-response-parsing.service';
|
||||||
|
|
||||||
const IMPORTS = [
|
const IMPORTS = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -130,6 +131,7 @@ const PROVIDERS = [
|
|||||||
UUIDService,
|
UUIDService,
|
||||||
DSpaceObjectDataService,
|
DSpaceObjectDataService,
|
||||||
ItemSelectService,
|
ItemSelectService,
|
||||||
|
MappingItemsResponseParsingService,
|
||||||
// register AuthInterceptor as HttpInterceptor
|
// register AuthInterceptor as HttpInterceptor
|
||||||
{
|
{
|
||||||
provide: HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
@@ -11,6 +11,26 @@ 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 { Item } from '../shared/item.model';
|
||||||
|
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
|
import { ensureArrayHasValue, hasValue, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
|
import { GetRequest, RestRequest } from './request.models';
|
||||||
|
import {
|
||||||
|
configureRequest,
|
||||||
|
filterSuccessfulResponses,
|
||||||
|
getRequestFromSelflink,
|
||||||
|
getResponseFromSelflink
|
||||||
|
} from '../shared/operators';
|
||||||
|
import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model';
|
||||||
|
import { GenericConstructor } from '../shared/generic-constructor';
|
||||||
|
import { ResponseParsingService } from './parsing.service';
|
||||||
|
import { MappingItemsResponseParsingService } from './mapping-items-response-parsing.service';
|
||||||
|
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||||
|
import { GenericSuccessResponse } from '../cache/response-cache.models';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
|
export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
|
||||||
@@ -27,4 +47,43 @@ 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 MappingItemsResponseParsingService;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
configureRequest(this.requestService)
|
||||||
|
).subscribe();
|
||||||
|
|
||||||
|
const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService));
|
||||||
|
const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache));
|
||||||
|
|
||||||
|
const payload$ = responseCache$.pipe(
|
||||||
|
filterSuccessfulResponses(),
|
||||||
|
map((entry: ResponseCacheEntry) => entry.response),
|
||||||
|
map((response: GenericSuccessResponse<DSpaceObject[]>) => new PaginatedList(response.pageInfo, response.payload))
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
44
src/app/core/data/mapping-items-response-parsing.service.ts
Normal file
44
src/app/core/data/mapping-items-response-parsing.service.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Inject, Injectable } from '@angular/core';
|
||||||
|
import { ResponseParsingService } from './parsing.service';
|
||||||
|
import { RestRequest } from './request.models';
|
||||||
|
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||||
|
import { ErrorResponse, GenericSuccessResponse, RestResponse } from '../cache/response-cache.models';
|
||||||
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||||
|
import { NormalizedItem } from '../cache/models/normalized-item.model';
|
||||||
|
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { BaseResponseParsingService } from './base-response-parsing.service';
|
||||||
|
import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
|
import { Item } from '../shared/item.model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MappingItemsResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||||
|
|
||||||
|
protected objectFactory = NormalizedObjectFactory;
|
||||||
|
protected toCache = true;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
) { super();
|
||||||
|
}
|
||||||
|
|
||||||
|
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||||
|
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._embedded)
|
||||||
|
&& Array.isArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]])) {
|
||||||
|
const serializer = new DSpaceRESTv2Serializer(DSpaceObject);
|
||||||
|
const items = serializer.deserializeArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]]);
|
||||||
|
return new GenericSuccessResponse(items, data.statusCode, this.processPageInfo(data.payload));
|
||||||
|
} else {
|
||||||
|
return new ErrorResponse(
|
||||||
|
Object.assign(
|
||||||
|
new Error('Unexpected response from mappingItems endpoint'),
|
||||||
|
{ statusText: data.statusCode }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -10,7 +10,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>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let item of (itemsRD$ | async)?.payload?.page">
|
<tr *ngFor="let item of (itemsRD$ | async)?.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>
|
||||||
|
@@ -38,6 +38,9 @@ export class ItemSelectComponent implements OnInit {
|
|||||||
@Input()
|
@Input()
|
||||||
confirmButton = 'item.select.confirm';
|
confirmButton = 'item.select.confirm';
|
||||||
|
|
||||||
|
@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[]>}
|
||||||
|
@@ -75,6 +75,7 @@ export class ObjectCollectionComponent implements OnChanges, OnInit {
|
|||||||
this.currentMode = params.view;
|
this.currentMode = params.view;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.log(this.objects);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user