diff --git a/resources/i18n/en.json b/resources/i18n/en.json
index 69c4a4174b..f1e6c2b9ed 100644
--- a/resources/i18n/en.json
+++ b/resources/i18n/en.json
@@ -124,6 +124,27 @@
"curate": {
"head": "Curate"
}
+ },
+ "item-mapper": {
+ "head": "Item Mapper - Map Item to Collections",
+ "item": "Item: \"{{name}}\"",
+ "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"
}
}
},
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 70f6fc7d3b..d11eb7ec7d 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
@@ -5,11 +5,13 @@ import { EditItemPageRoutingModule } from './edit-item-page.routing.module';
import { EditItemPageComponent } from './edit-item-page.component';
import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component';
import { ItemStatusComponent } from './item-status/item-status.component';
+import { SearchPageModule } from '../../+search-page/search-page.module';
@NgModule({
imports: [
CommonModule,
SharedModule,
+ SearchPageModule,
EditItemPageRoutingModule
],
declarations: [
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 f2209cddcc..dc3f975468 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,7 +15,7 @@ import { ItemCollectionMapperComponent } from './item-collection-mapper/item-col
}
},
{
- path: 'map',
+ path: 'mapper',
component: ItemCollectionMapperComponent,
resolve: {
item: ItemPageResolver
diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html
index 3fb829fe8b..21149eafaf 100644
--- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html
+++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html
@@ -1,7 +1,38 @@
-
It works!
+
{{'item.edit.item-mapper.head' | translate}}
+
+
{{'item.edit.item-mapper.description' | translate}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts
index 592e3bd26c..eeb602292d 100644
--- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts
+++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts
@@ -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 { 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({
selector: 'ds-item-collection-mapper',
@@ -14,6 +29,81 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
/**
* Component for mapping collections to an item
*/
-export class ItemCollectionMapperComponent {
+export class ItemCollectionMapperComponent implements OnInit {
+ /**
+ * The item to map to collections
+ */
+ itemRD$: Observable>;
+
+ /**
+ * Search options
+ */
+ searchOptions$: Observable;
+
+ /**
+ * List of collections to show under the "Browse" tab
+ * Collections that are mapped to the item
+ */
+ itemCollectionsRD$: Observable>;
+
+ /**
+ * List of collections to show under the "Map" tab
+ * Collections that are not mapped to the item
+ */
+ mappingCollectionsRD$: Observable>>;
+
+ /**
+ * 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>;
+ 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- ) => 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;
+ }
}
diff --git a/src/app/+item-page/edit-item-page/item-status/item-status.component.ts b/src/app/+item-page/edit-item-page/item-status/item-status.component.ts
index 715614c1d9..545ca17b6e 100644
--- a/src/app/+item-page/edit-item-page/item-status/item-status.component.ts
+++ b/src/app/+item-page/edit-item-page/item-status/item-status.component.ts
@@ -54,7 +54,7 @@ export class ItemStatusComponent implements OnInit {
this.statusDataKeys = Object.keys(this.statusData);
this.actions = Object.assign({
- mappedCollections: this.getCurrentUrl() + '/map'
+ mappedCollections: this.getCurrentUrl() + '/mapper'
});
this.actionsKeys = Object.keys(this.actions);
}
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 31b9b31244..4ea3aa8df7 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -65,6 +65,7 @@ import { UploaderService } from '../shared/uploader/uploader.service';
import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service';
import { DSpaceObjectDataService } from './data/dspace-object-data.service';
import { ItemSelectService } from '../shared/item-select/item-select.service';
+import { MappingCollectionsReponseParsingService } from './data/mapping-collections-reponse-parsing.service';
const IMPORTS = [
CommonModule,
@@ -111,6 +112,7 @@ const PROVIDERS = [
RegistryMetadataschemasResponseParsingService,
RegistryMetadatafieldsResponseParsingService,
RegistryBitstreamformatsResponseParsingService,
+ MappingCollectionsReponseParsingService,
MetadataschemaParsingService,
DebugResponseParsingService,
SearchResponseParsingService,
diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts
index 7c2c4e572d..250d8c1303 100644
--- a/src/app/core/data/item-data.service.ts
+++ b/src/app/core/data/item-data.service.ts
@@ -3,7 +3,7 @@ import { Inject, Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
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 { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
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 { RequestService } from './request.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 { configureRequest, getResponseFromSelflink } from '../shared/operators';
+import {
+ configureRequest,
+ filterSuccessfulResponses,
+ getRequestFromSelflink,
+ getResponseFromSelflink
+} from '../shared/operators';
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()
export class ItemDataService extends DataService {
@@ -71,4 +81,16 @@ export class ItemDataService extends DataService {
);
}
+ public getMappedCollections(itemId: string): Observable> {
+ 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;
+ }
+
}
diff --git a/src/app/core/data/mapping-collections-reponse-parsing.service.ts b/src/app/core/data/mapping-collections-reponse-parsing.service.ts
new file mode 100644
index 0000000000..1b1ff3368f
--- /dev/null
+++ b/src/app/core/data/mapping-collections-reponse-parsing.service.ts
@@ -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 }
+ )
+ );
+ }
+ }
+}
diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts
index b87f9cefc8..001c416f69 100644
--- a/src/app/core/data/request.models.ts
+++ b/src/app/core/data/request.models.ts
@@ -13,6 +13,7 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { HttpHeaders } from '@angular/common/http';
import { IntegrationResponseParsingService } from '../integration/integration-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 */
@@ -191,6 +192,12 @@ export class BrowseItemsRequest extends GetRequest {
}
}
+export class MappingCollectionsRequest extends GetRequest {
+ getResponseParser(): GenericConstructor {
+ return MappingCollectionsReponseParsingService;
+ }
+}
+
export class ConfigRequest extends GetRequest {
constructor(uuid: string, href: string) {
super(uuid, href);