55946: Intermediate commit

This commit is contained in:
Kristof De Langhe
2018-10-09 14:09:56 +02:00
parent 3a809cc671
commit 6bc4d061f1
10 changed files with 208 additions and 9 deletions

View File

@@ -124,6 +124,27 @@
"curate": { "curate": {
"head": "Curate" "head": "Curate"
} }
},
"item-mapper": {
"head": "Item Mapper - Map Item to Collections",
"item": "Item: \"<b>{{name}}</b>\"",
"description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.",
"confirm": "Map item to selected collections",
"tabs": {
"browse": "Browse",
"map": "Map"
},
"notifications": {
"success": {
"head": "Mapping completed",
"content": "Successfully mapped item to {{amount}} collections."
},
"error": {
"head": "Mapping errors",
"content": "Errors occurred for mapping of item to {{amount}} collections."
}
},
"return": "Return"
} }
} }
}, },

View File

@@ -5,11 +5,13 @@ import { EditItemPageRoutingModule } from './edit-item-page.routing.module';
import { EditItemPageComponent } from './edit-item-page.component'; import { EditItemPageComponent } from './edit-item-page.component';
import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component'; import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component';
import { ItemStatusComponent } from './item-status/item-status.component'; import { ItemStatusComponent } from './item-status/item-status.component';
import { SearchPageModule } from '../../+search-page/search-page.module';
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
SharedModule, SharedModule,
SearchPageModule,
EditItemPageRoutingModule EditItemPageRoutingModule
], ],
declarations: [ declarations: [

View File

@@ -15,7 +15,7 @@ import { ItemCollectionMapperComponent } from './item-collection-mapper/item-col
} }
}, },
{ {
path: 'map', path: 'mapper',
component: ItemCollectionMapperComponent, component: ItemCollectionMapperComponent,
resolve: { resolve: {
item: ItemPageResolver item: ItemPageResolver

View File

@@ -1,7 +1,38 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<p>It works!</p> <h2>{{'item.edit.item-mapper.head' | translate}}</h2>
<p [innerHTML]="'item.edit.item-mapper.item' | translate:{ name: (itemRD$ | async)?.payload?.name }" id="item-name"></p>
<p>{{'item.edit.item-mapper.description' | translate}}</p>
<div class="row">
<div class="col-12 col-lg-6">
<ds-search-form id="search-form"
[query]="(searchOptions$ | async)?.query"
[scope]="(searchOptions$ | async)?.scope"
[currentUrl]="getCurrentUrl()">
</ds-search-form>
</div>
</div>
<ngb-tabset (tabChange)="tabChange($event)">
<ngb-tab title="{{'item.edit.item-mapper.tabs.browse' | translate}}">
<ng-template ngbTabContent>
<div class="mt-2">
<div *ngFor="let col of (itemCollectionsRD$ | async)?.payload">{{col.name}}</div>
</div>
</ng-template>
</ngb-tab>
<ngb-tab title="{{'item.edit.item-mapper.tabs.map' | translate}}">
<ng-template ngbTabContent>
<div class="mt-2">
</div>
</ng-template>
</ngb-tab>
</ngb-tabset>
<button [routerLink]="['/items/', (itemRD$ | async)?.payload?.id]" class="btn btn-outline-secondary">{{'item.edit.item-mapper.return' | translate}}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,20 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
import { Observable } from 'rxjs/Observable';
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { Collection } from '../../../core/shared/collection.model';
import { Item } from '../../../core/shared/item.model';
import { getSucceededRemoteData } from '../../../core/shared/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { SearchService } from '../../../+search-page/search-service/search.service';
import { SearchConfigurationService } from '../../../+search-page/search-service/search-configuration.service';
import { map, switchMap } from 'rxjs/operators';
import { CollectionDataService } from '../../../core/data/collection-data.service';
import { ItemDataService } from '../../../core/data/item-data.service';
@Component({ @Component({
selector: 'ds-item-collection-mapper', selector: 'ds-item-collection-mapper',
@@ -14,6 +29,81 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
/** /**
* Component for mapping collections to an item * Component for mapping collections to an item
*/ */
export class ItemCollectionMapperComponent { export class ItemCollectionMapperComponent implements OnInit {
/**
* The item to map to collections
*/
itemRD$: Observable<RemoteData<Item>>;
/**
* Search options
*/
searchOptions$: Observable<PaginatedSearchOptions>;
/**
* List of collections to show under the "Browse" tab
* Collections that are mapped to the item
*/
itemCollectionsRD$: Observable<RemoteData<Collection[]>>;
/**
* List of collections to show under the "Map" tab
* Collections that are not mapped to the item
*/
mappingCollectionsRD$: Observable<RemoteData<PaginatedList<Collection>>>;
/**
* Sort on title ASC by default
* @type {SortOptions}
*/
defaultSortOptions: SortOptions = new SortOptions('dc.title', SortDirection.ASC);
constructor(private route: ActivatedRoute,
private router: Router,
private searchConfigService: SearchConfigurationService,
private searchService: SearchService,
private collectionDataService: CollectionDataService,
private itemDataService: ItemDataService) {
}
ngOnInit(): void {
this.itemRD$ = this.route.data.map((data) => data.item).pipe(getSucceededRemoteData()) as Observable<RemoteData<Item>>;
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
this.loadCollectionLists();
}
/**
* Load itemCollectionsRD$ with a fixed scope to only obtain the collections that own this item
* Load mappingCollectionsRD$ to only obtain collections that don't own this item
* TODO: When the API support it, fetch collections excluding the item's scope (currently fetches all collections)
*/
loadCollectionLists() {
this.itemCollectionsRD$ = this.itemRD$.pipe(
map((itemRD: RemoteData<Item>) => itemRD.payload),
switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id))
);
this.mappingCollectionsRD$ = this.collectionDataService.findAll();
}
/**
* Clear url parameters on tab change (temporary fix until pagination is improved)
* @param event
*/
tabChange(event) {
// TODO: Fix tabs to maintain their own pagination options (once the current pagination system is improved)
// Temporary solution: Clear url params when changing tabs
this.router.navigateByUrl(this.getCurrentUrl());
}
/**
* Get current url without parameters
* @returns {string}
*/
getCurrentUrl(): string {
if (this.router.url.indexOf('?') > -1) {
return this.router.url.substring(0, this.router.url.indexOf('?'));
}
return this.router.url;
}
} }

View File

@@ -54,7 +54,7 @@ export class ItemStatusComponent implements OnInit {
this.statusDataKeys = Object.keys(this.statusData); this.statusDataKeys = Object.keys(this.statusData);
this.actions = Object.assign({ this.actions = Object.assign({
mappedCollections: this.getCurrentUrl() + '/map' mappedCollections: this.getCurrentUrl() + '/mapper'
}); });
this.actionsKeys = Object.keys(this.actions); this.actionsKeys = Object.keys(this.actions);
} }

View File

@@ -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 { MappingCollectionsReponseParsingService } from './data/mapping-collections-reponse-parsing.service';
const IMPORTS = [ const IMPORTS = [
CommonModule, CommonModule,
@@ -111,6 +112,7 @@ const PROVIDERS = [
RegistryMetadataschemasResponseParsingService, RegistryMetadataschemasResponseParsingService,
RegistryMetadatafieldsResponseParsingService, RegistryMetadatafieldsResponseParsingService,
RegistryBitstreamformatsResponseParsingService, RegistryBitstreamformatsResponseParsingService,
MappingCollectionsReponseParsingService,
MetadataschemaParsingService, MetadataschemaParsingService,
DebugResponseParsingService, DebugResponseParsingService,
SearchResponseParsingService, SearchResponseParsingService,

View File

@@ -3,7 +3,7 @@ import { Inject, Injectable } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { isEmpty, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { ensureArrayHasValue, isEmpty, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { BrowseService } from '../browse/browse.service'; import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedItem } from '../cache/models/normalized-item.model'; import { NormalizedItem } from '../cache/models/normalized-item.model';
@@ -15,11 +15,21 @@ import { URLCombiner } from '../url-combiner/url-combiner';
import { DataService } from './data.service'; import { DataService } from './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 { FindAllOptions, PostRequest, RestRequest } from './request.models'; import { FindAllOptions, GetRequest, MappingCollectionsRequest, PostRequest, RestRequest } from './request.models';
import { distinctUntilChanged, map } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { configureRequest, getResponseFromSelflink } from '../shared/operators'; import {
configureRequest,
filterSuccessfulResponses,
getRequestFromSelflink,
getResponseFromSelflink
} from '../shared/operators';
import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { RestResponse } from '../cache/response-cache.models'; import { DSOSuccessResponse, GenericSuccessResponse, RestResponse } from '../cache/response-cache.models';
import { BrowseDefinition } from '../shared/browse-definition.model';
import { Collection } from '../shared/collection.model';
import { NormalizedCollection } from '../cache/models/normalized-collection.model';
import { RemoteData } from './remote-data';
import { PaginatedList } from './paginated-list';
@Injectable() @Injectable()
export class ItemDataService extends DataService<NormalizedItem, Item> { export class ItemDataService extends DataService<NormalizedItem, Item> {
@@ -71,4 +81,16 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
); );
} }
public getMappedCollections(itemId: string): Observable<RemoteData<Collection[]>> {
const request$ = this.getMappingCollectionsEndpoint(itemId).pipe(
isNotEmptyOperator(),
distinctUntilChanged(),
map((endpointURL: string) => new MappingCollectionsRequest(this.requestService.generateRequestId(), endpointURL)),
configureRequest(this.requestService)
);
// TODO: Create a remotedata object
return undefined;
}
} }

View File

@@ -0,0 +1,24 @@
import { 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';
@Injectable()
export class MappingCollectionsReponseParsingService implements ResponseParsingService {
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const payload = data.payload;
if (payload._embedded && payload._embedded.mappingCollections) {
const mappingCollections = payload._embedded.mappingCollections;
return new GenericSuccessResponse(mappingCollections, data.statusCode);
} else {
return new ErrorResponse(
Object.assign(
new Error('Unexpected response from mappingCollections endpoint'),
{ statusText: data.statusCode }
)
);
}
}
}

View File

@@ -13,6 +13,7 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { HttpHeaders } from '@angular/common/http'; import { HttpHeaders } from '@angular/common/http';
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service'; import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service'; import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
import { MappingCollectionsReponseParsingService } from './mapping-collections-reponse-parsing.service';
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
@@ -191,6 +192,12 @@ export class BrowseItemsRequest extends GetRequest {
} }
} }
export class MappingCollectionsRequest extends GetRequest {
getResponseParser(): GenericConstructor<ResponseParsingService> {
return MappingCollectionsReponseParsingService;
}
}
export class ConfigRequest extends GetRequest { export class ConfigRequest extends GetRequest {
constructor(uuid: string, href: string) { constructor(uuid: string, href: string) {
super(uuid, href); super(uuid, href);