diff --git a/resources/i18n/en.json b/resources/i18n/en.json
index 50f67c6cb8..bbe77f3e2f 100644
--- a/resources/i18n/en.json
+++ b/resources/i18n/en.json
@@ -238,6 +238,17 @@
"item.edit.modify.overview.field": "Field",
"item.edit.modify.overview.language": "Language",
"item.edit.modify.overview.value": "Value",
+ "item.edit.move.cancel": "Cancel",
+ "item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.",
+ "item.edit.move.error": "An error occured when attempting to move the item",
+ "item.edit.move.head": "Move item: {{id}}",
+ "item.edit.move.inheritpolicies.checkbox": "Inherit policies",
+ "item.edit.move.inheritpolicies.description": "Inherit the default policies of the destination collection",
+ "item.edit.move.move": "Move",
+ "item.edit.move.processing": "Moving...",
+ "item.edit.move.search.placeholder": "Enter a search query to look for collections",
+ "item.edit.move.success": "The item has been moved succesfully",
+ "item.edit.move.title": "Move item",
"item.edit.private.cancel": "Cancel",
"item.edit.private.confirm": "Make it Private",
"item.edit.private.description": "Are you sure this item should be made private in the archive?",
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 1542d12ce5..236388109e 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
@@ -18,6 +18,7 @@ import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.compo
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
import { EditRelationshipComponent } from './item-relationships/edit-relationship/edit-relationship.component';
import { EditRelationshipListComponent } from './item-relationships/edit-relationship-list/edit-relationship-list.component';
+import { ItemMoveComponent } from './item-move/item-move.component';
/**
* Module that contains all components related to the Edit Item page administrator functionality
@@ -44,7 +45,8 @@ import { EditRelationshipListComponent } from './item-relationships/edit-relatio
ItemBitstreamsComponent,
EditInPlaceFieldComponent,
EditRelationshipComponent,
- EditRelationshipListComponent
+ EditRelationshipListComponent,
+ ItemMoveComponent,
]
})
export class EditItemPageModule {
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 f298f7df39..65e2a36fd1 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
@@ -10,6 +10,7 @@ import { ItemDeleteComponent } from './item-delete/item-delete.component';
import { ItemStatusComponent } from './item-status/item-status.component';
import { ItemMetadataComponent } from './item-metadata/item-metadata.component';
import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component';
+import { ItemMoveComponent } from './item-move/item-move.component';
import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component';
const ITEM_EDIT_WITHDRAW_PATH = 'withdraw';
@@ -17,6 +18,7 @@ const ITEM_EDIT_REINSTATE_PATH = 'reinstate';
const ITEM_EDIT_PRIVATE_PATH = 'private';
const ITEM_EDIT_PUBLIC_PATH = 'public';
const ITEM_EDIT_DELETE_PATH = 'delete';
+const ITEM_EDIT_MOVE_PATH = 'move';
/**
* Routing module that handles the routing for the Edit Item page administrator functionality
@@ -104,6 +106,14 @@ const ITEM_EDIT_DELETE_PATH = 'delete';
resolve: {
item: ItemPageResolver
}
+ },
+ {
+ path: ITEM_EDIT_MOVE_PATH,
+ component: ItemMoveComponent,
+ data: { title: 'item.edit.move.title' },
+ resolve: {
+ item: ItemPageResolver
+ }
}])
],
providers: [
diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html
index e9c5de95ca..e8ffc28920 100644
--- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html
+++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html
@@ -4,7 +4,7 @@
{{metadata?.key?.split('.').join('.')}}
-
+ >
{{"item.edit.metadata.metadatafield.invalid" | translate}}
diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts
index b8d122d4f6..e07df15651 100644
--- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts
@@ -10,7 +10,6 @@ import { By } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { SharedModule } from '../../../../shared/shared.module';
import { getTestScheduler } from 'jasmine-marbles';
-import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
import { TestScheduler } from 'rxjs/testing';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { TranslateModule } from '@ngx-translate/core';
@@ -18,6 +17,7 @@ import { MetadatumViewModel } from '../../../../core/shared/metadata.models';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
+import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
let comp: EditInPlaceFieldComponent;
let fixture: ComponentFixture;
diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts
index 1722cde8bc..7dce025a73 100644
--- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts
+++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts
@@ -4,13 +4,13 @@ import { RegistryService } from '../../../../core/registry/registry.service';
import { cloneDeep } from 'lodash';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { map, take } from 'rxjs/operators';
-import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { NgModel } from '@angular/forms';
import { MetadatumViewModel } from '../../../../core/shared/metadata.models';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
+import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
@Component({
// tslint:disable-next-line:component-selector
diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.html b/src/app/+item-page/edit-item-page/item-move/item-move.component.html
new file mode 100644
index 0000000000..cf5ada77cf
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.html
@@ -0,0 +1,48 @@
+
+
+
+
{{'item.edit.move.head' | translate: {id: (itemRD$ | async)?.payload?.handle} }}
+
{{'item.edit.move.description' | translate}}
+
+
+
+
+
+
+
+
+ {{'item.edit.move.inheritpolicies.description' | translate}}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts
new file mode 100644
index 0000000000..e73b4b6f9a
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts
@@ -0,0 +1,172 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { Item } from '../../../core/shared/item.model';
+import { RouterStub } from '../../../shared/testing/router-stub';
+import { CommonModule } from '@angular/common';
+import { RouterTestingModule } from '@angular/router/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { ActivatedRoute, Router } from '@angular/router';
+import { ItemMoveComponent } from './item-move.component';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { SearchService } from '../../../+search-page/search-service/search.service';
+import { of as observableOf } from 'rxjs';
+import { FormsModule } from '@angular/forms';
+import { ItemDataService } from '../../../core/data/item-data.service';
+import { RemoteData } from '../../../core/data/remote-data';
+import { PaginatedList } from '../../../core/data/paginated-list';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RestResponse } from '../../../core/cache/response.models';
+import { Collection } from '../../../core/shared/collection.model';
+
+describe('ItemMoveComponent', () => {
+ let comp: ItemMoveComponent;
+ let fixture: ComponentFixture;
+
+ const mockItem = Object.assign(new Item(), {
+ id: 'fake-id',
+ handle: 'fake/handle',
+ lastModified: '2018'
+ });
+
+ const itemPageUrl = `fake-url/${mockItem.id}`;
+ const routerStub = Object.assign(new RouterStub(), {
+ url: `${itemPageUrl}/edit`
+ });
+
+ const mockItemDataService = jasmine.createSpyObj({
+ moveToCollection: observableOf(new RestResponse(true, 200, 'Success'))
+ });
+
+ const mockItemDataServiceFail = jasmine.createSpyObj({
+ moveToCollection: observableOf(new RestResponse(false, 500, 'Internal server error'))
+ });
+
+ const routeStub = {
+ data: observableOf({
+ item: new RemoteData(false, false, true, null, {
+ id: 'item1'
+ })
+ })
+ };
+
+ const collection1 = Object.assign(new Collection(),{
+ uuid: 'collection-uuid-1',
+ name: 'Test collection 1',
+ self: 'self-link-1',
+ });
+
+ const collection2 = Object.assign(new Collection(),{
+ uuid: 'collection-uuid-2',
+ name: 'Test collection 2',
+ self: 'self-link-2',
+ });
+
+ const mockSearchService = {
+ search: () => {
+ return observableOf(new RemoteData(false, false, true, null,
+ new PaginatedList(null, [
+ {
+ indexableObject: collection1,
+ hitHighlights: {}
+ }, {
+ indexableObject: collection2,
+ hitHighlights: {}
+ }
+ ])));
+ }
+ };
+
+ const notificationsServiceStub = new NotificationsServiceStub();
+
+ describe('ItemMoveComponent success', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [ItemMoveComponent],
+ providers: [
+ {provide: ActivatedRoute, useValue: routeStub},
+ {provide: Router, useValue: routerStub},
+ {provide: ItemDataService, useValue: mockItemDataService},
+ {provide: NotificationsService, useValue: notificationsServiceStub},
+ {provide: SearchService, useValue: mockSearchService},
+ ], schemas: [
+ CUSTOM_ELEMENTS_SCHEMA
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ItemMoveComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+ it('should load suggestions', () => {
+ const expected = [
+ collection1,
+ collection2
+ ];
+
+ comp.collectionSearchResults.subscribe((value) => {
+ expect(value).toEqual(expected);
+ }
+ );
+ });
+ it('should get current url ', () => {
+ expect(comp.getCurrentUrl()).toEqual('fake-url/fake-id/edit');
+ });
+ it('should on click select the correct collection name and id', () => {
+ const data = collection1;
+
+ comp.onClick(data);
+
+ expect(comp.selectedCollectionName).toEqual('Test collection 1');
+ expect(comp.selectedCollection).toEqual(collection1);
+ });
+ describe('moveCollection', () => {
+ it('should call itemDataService.moveToCollection', () => {
+ comp.itemId = 'item-id';
+ comp.selectedCollectionName = 'selected-collection-id';
+ comp.selectedCollection = collection1;
+ comp.moveCollection();
+
+ expect(mockItemDataService.moveToCollection).toHaveBeenCalledWith('item-id', collection1);
+ });
+ it('should call notificationsService success message on success', () => {
+ comp.moveCollection();
+
+ expect(notificationsServiceStub.success).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('ItemMoveComponent fail', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [ItemMoveComponent],
+ providers: [
+ {provide: ActivatedRoute, useValue: routeStub},
+ {provide: Router, useValue: routerStub},
+ {provide: ItemDataService, useValue: mockItemDataServiceFail},
+ {provide: NotificationsService, useValue: notificationsServiceStub},
+ {provide: SearchService, useValue: mockSearchService},
+ ], schemas: [
+ CUSTOM_ELEMENTS_SCHEMA
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ItemMoveComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should call notificationsService error message on fail', () => {
+ comp.moveCollection();
+
+ expect(notificationsServiceStub.error).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts
new file mode 100644
index 0000000000..113ee97b3f
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts
@@ -0,0 +1,139 @@
+import { Component, OnInit } from '@angular/core';
+import { SearchService } from '../../../+search-page/search-service/search.service';
+import { first, map } from 'rxjs/operators';
+import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
+import { SearchOptions } from '../../../+search-page/search-options.model';
+import { RemoteData } from '../../../core/data/remote-data';
+import { DSpaceObject } from '../../../core/shared/dspace-object.model';
+import { PaginatedList } from '../../../core/data/paginated-list';
+import { SearchResult } from '../../../+search-page/search-result.model';
+import { Item } from '../../../core/shared/item.model';
+import { ActivatedRoute, Router } from '@angular/router';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { TranslateService } from '@ngx-translate/core';
+import { getSucceededRemoteData } from '../../../core/shared/operators';
+import { ItemDataService } from '../../../core/data/item-data.service';
+import { getItemEditPath } from '../../item-page-routing.module';
+import { Observable, of as observableOf } from 'rxjs';
+import { RestResponse } from '../../../core/cache/response.models';
+import { Collection } from '../../../core/shared/collection.model';
+import { tap } from 'rxjs/internal/operators/tap';
+import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
+import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
+
+@Component({
+ selector: 'ds-item-move',
+ templateUrl: './item-move.component.html'
+})
+/**
+ * Component that handles the moving of an item to a different collection
+ */
+export class ItemMoveComponent implements OnInit {
+ /**
+ * TODO: There is currently no backend support to change the owningCollection and inherit policies,
+ * TODO: when this is added, the inherit policies option should be used.
+ */
+
+ selectorType = DSpaceObjectType.COLLECTION;
+
+ inheritPolicies = false;
+ itemRD$: Observable>;
+ collectionSearchResults: Observable = observableOf([]);
+ selectedCollectionName: string;
+ selectedCollection: Collection;
+ canSubmit = false;
+
+ itemId: string;
+ processing = false;
+
+ pagination = new PaginationComponentOptions();
+
+ constructor(private route: ActivatedRoute,
+ private router: Router,
+ private notificationsService: NotificationsService,
+ private itemDataService: ItemDataService,
+ private searchService: SearchService,
+ private translateService: TranslateService) {
+ }
+
+ ngOnInit(): void {
+ this.itemRD$ = this.route.data.pipe(map((data) => data.item), getSucceededRemoteData()) as Observable>;
+ this.itemRD$.subscribe((rd) => {
+ this.itemId = rd.payload.id;
+ }
+ );
+ this.pagination.pageSize = 5;
+ this.loadSuggestions('');
+ }
+
+ /**
+ * Find suggestions based on entered query
+ * @param query - Search query
+ */
+ findSuggestions(query): void {
+ this.loadSuggestions(query);
+ }
+
+ /**
+ * Load all available collections to move the item to.
+ * TODO: When the API support it, only fetch collections where user has ADD rights to.
+ */
+ loadSuggestions(query): void {
+ this.collectionSearchResults = this.searchService.search(new PaginatedSearchOptions({
+ pagination: this.pagination,
+ dsoType: DSpaceObjectType.COLLECTION,
+ query: query
+ })).pipe(
+ first(),
+ map((rd: RemoteData>>) => {
+ return rd.payload.page.map((searchResult) => {
+ return searchResult.indexableObject
+ })
+ }) ,
+ );
+
+ }
+
+ /**
+ * Set the collection name and id based on the selected value
+ * @param data - obtained from the ds-input-suggestions component
+ */
+ onClick(data: any): void {
+ this.selectedCollection = data;
+ this.selectedCollectionName = data.name;
+ this.canSubmit = true;
+ }
+
+ /**
+ * @returns {string} the current URL
+ */
+ getCurrentUrl() {
+ return this.router.url;
+ }
+
+ /**
+ * Moves the item to a new collection based on the selected collection
+ */
+ moveCollection() {
+ this.processing = true;
+ this.itemDataService.moveToCollection(this.itemId, this.selectedCollection).pipe(first()).subscribe(
+ (response: RestResponse) => {
+ this.router.navigate([getItemEditPath(this.itemId)]);
+ if (response.isSuccessful) {
+ this.notificationsService.success(this.translateService.get('item.edit.move.success'));
+ } else {
+ this.notificationsService.error(this.translateService.get('item.edit.move.error'));
+ }
+ this.processing = false;
+ }
+ );
+ }
+
+ /**
+ * Resets the can submit when the user changes the content of the input field
+ * @param data
+ */
+ resetCollection(data: any) {
+ this.canSubmit = false;
+ }
+}
diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html
index 4623195437..3a52fd0d12 100644
--- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html
+++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html
@@ -4,7 +4,7 @@
diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts
index 1901bf5fb4..7122dbaf42 100644
--- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts
@@ -1,8 +1,9 @@
-import {ItemOperation} from './itemOperation.model';
-import {async, TestBed} from '@angular/core/testing';
-import {ItemOperationComponent} from './item-operation.component';
-import {TranslateModule} from '@ngx-translate/core';
-import {By} from '@angular/platform-browser';
+import { ItemOperation } from './itemOperation.model';
+import { async, TestBed } from '@angular/core/testing';
+import { ItemOperationComponent } from './item-operation.component';
+import { TranslateModule } from '@ngx-translate/core';
+import { By } from '@angular/platform-browser';
+import { RouterTestingModule } from '@angular/router/testing';
describe('ItemOperationComponent', () => {
let itemOperation: ItemOperation;
@@ -12,7 +13,7 @@ describe('ItemOperationComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [TranslateModule.forRoot()],
+ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
declarations: [ItemOperationComponent]
}).compileComponents();
}));
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 c7e3a023d1..d293188aa6 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
@@ -79,6 +79,7 @@ export class ItemStatusComponent implements OnInit {
this.operations.push(new ItemOperation('public', this.getCurrentUrl(item) + '/public'));
}
this.operations.push(new ItemOperation('delete', this.getCurrentUrl(item) + '/delete'));
+ this.operations.push(new ItemOperation('move', this.getCurrentUrl(item) + '/move'));
});
}
diff --git a/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html
index 63d034c6ea..5e6bcfaf8b 100644
--- a/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html
+++ b/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html
@@ -15,7 +15,7 @@
| translate}}
-
+ ngDefaultControl>
diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts
index ccbda54f89..cb25aba44e 100644
--- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts
+++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts
@@ -21,9 +21,9 @@ import { SearchService } from '../../../search-service/search.service';
import { FILTER_CONFIG, IN_PLACE_SEARCH, SearchFilterService } from '../search-filter.service';
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
import { getSucceededRemoteData } from '../../../../core/shared/operators';
-import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
import { SearchOptions } from '../../../search-options.model';
import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component';
+import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
@Component({
selector: 'ds-search-facet-filter',
diff --git a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html
index 996fd7f751..06b60b5ecd 100644
--- a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html
+++ b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html
@@ -15,7 +15,7 @@
| translate}}
-
+ >
diff --git a/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.html
index db6a566758..43134014e1 100644
--- a/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.html
+++ b/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.html
@@ -15,7 +15,7 @@
| translate}}
-
+ ngDefaultControl>
diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts
index f6adbb23c2..07d8ed8405 100644
--- a/src/app/core/data/item-data.service.ts
+++ b/src/app/core/data/item-data.service.ts
@@ -1,8 +1,8 @@
-import { distinctUntilChanged, filter, map } from 'rxjs/operators';
+import { distinctUntilChanged, filter, find, map } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
-import { isNotEmpty } from '../../shared/empty.util';
+import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers';
@@ -12,14 +12,17 @@ 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, PatchRequest, RestRequest } from './request.models';
+import { FindAllOptions, PatchRequest, PutRequest, RestRequest } from './request.models';
import { ObjectCacheService } from '../cache/object-cache.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
-import { HttpClient } from '@angular/common/http';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { configureRequest, getRequestFromRequestHref } from '../shared/operators';
import { RequestEntry } from './request.reducer';
+import { RestResponse } from '../cache/response.models';
+import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
+import { Collection } from '../shared/collection.model';
@Injectable()
export class ItemDataService extends DataService- {
@@ -118,4 +121,43 @@ export class ItemDataService extends DataService
- {
map((requestEntry: RequestEntry) => requestEntry.response)
);
}
+
+ /**
+ * Get the endpoint to move the item
+ * @param itemId
+ */
+ public getMoveItemEndpoint(itemId: string): Observable {
+ return this.halService.getEndpoint(this.linkPath).pipe(
+ map((endpoint: string) => this.getIDHref(endpoint, itemId)),
+ map((endpoint: string) => `${endpoint}/owningCollection`)
+ );
+ }
+
+ /**
+ * Move the item to a different owning collection
+ * @param itemId
+ * @param collection
+ */
+ public moveToCollection(itemId: string, collection: Collection): Observable {
+ const options: HttpOptions = Object.create({});
+ let headers = new HttpHeaders();
+ headers = headers.append('Content-Type', 'text/uri-list');
+ options.headers = headers;
+
+ const requestId = this.requestService.generateRequestId();
+ const hrefObs = this.getMoveItemEndpoint(itemId);
+
+ hrefObs.pipe(
+ find((href: string) => hasValue(href)),
+ map((href: string) => {
+ const request = new PutRequest(requestId, href, collection.self, options);
+ this.requestService.configure(request);
+ })
+ ).subscribe();
+
+ return this.requestService.getByUUID(requestId).pipe(
+ find((request: RequestEntry) => request.completed),
+ map((request: RequestEntry) => request.response)
+ );
+ }
}
diff --git a/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html
new file mode 100644
index 0000000000..2b605ccdc6
--- /dev/null
+++ b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html
@@ -0,0 +1,25 @@
+
+
diff --git a/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts
new file mode 100644
index 0000000000..4229060e86
--- /dev/null
+++ b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts
@@ -0,0 +1,71 @@
+import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
+
+import { TranslateModule } from '@ngx-translate/core';
+import { By } from '@angular/platform-browser';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule } from '@angular/forms';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { RouterTestingModule } from '@angular/router/testing';
+import { DsoInputSuggestionsComponent } from './dso-input-suggestions.component';
+import { DSpaceObject } from '../../../core/shared/dspace-object.model';
+
+describe('DsoInputSuggestionsComponent', () => {
+
+ let comp: DsoInputSuggestionsComponent;
+ let fixture: ComponentFixture;
+ let de: DebugElement;
+ let el: HTMLElement;
+
+ const dso1 = {
+ uuid: 'test-uuid-1',
+ name: 'test-name-1'
+ } as DSpaceObject;
+
+ const dso2 = {
+ uuid: 'test-uuid-2',
+ name: 'test-name-2'
+ } as DSpaceObject;
+
+ const dso3 = {
+ uuid: 'test-uuid-3',
+ name: 'test-name-3'
+ } as DSpaceObject;
+
+ const suggestions = [dso1, dso2, dso3];
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NoopAnimationsModule, FormsModule],
+ declarations: [DsoInputSuggestionsComponent],
+ providers: [],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(DsoInputSuggestionsComponent, {
+ set: {changeDetection: ChangeDetectionStrategy.Default}
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DsoInputSuggestionsComponent);
+
+ comp = fixture.componentInstance; // LoadingComponent test instance
+ comp.suggestions = suggestions;
+ // query for the message