diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 82e372ea56..0ad08652b5 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -128,8 +128,28 @@ "collection.delete.notification.fail": "Collection could not be deleted", "collection.delete.notification.success": "Successfully deleted collection", "collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"", + "collection.edit.delete": "Delete this collection", "collection.edit.head": "Edit Collection", + + "collection.edit.item-mapper.cancel": "Cancel", + "collection.edit.item-mapper.collection": "Collection: \"{{name}}\"", + "collection.edit.item-mapper.confirm": "Map selected items", + "collection.edit.item-mapper.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.", + "collection.edit.item-mapper.head": "Item Mapper - Map Items from Other Collections", + "collection.edit.item-mapper.no-search": "Please enter a query to search", + "collection.edit.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.", + "collection.edit.item-mapper.notifications.map.error.head": "Mapping errors", + "collection.edit.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.", + "collection.edit.item-mapper.notifications.map.success.head": "Mapping completed", + "collection.edit.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.", + "collection.edit.item-mapper.notifications.unmap.error.head": "Remove mapping errors", + "collection.edit.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.", + "collection.edit.item-mapper.notifications.unmap.success.head": "Remove mapping completed", + "collection.edit.item-mapper.remove": "Remove selected item mappings", + "collection.edit.item-mapper.tabs.browse": "Browse mapped items", + "collection.edit.item-mapper.tabs.map": "Map new items", + "collection.form.abstract": "Short Description", "collection.form.description": "Introductory text (HTML)", "collection.form.errors.title.required": "Please enter a collection name", @@ -139,29 +159,13 @@ "collection.form.tableofcontents": "News (HTML)", "collection.form.title": "Name", - "collection.item-mapper.cancel": "Cancel", - "collection.item-mapper.collection": "Collection: \"{{name}}\"", - "collection.item-mapper.confirm": "Map selected items", - "collection.item-mapper.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.", - "collection.item-mapper.head": "Item Mapper - Map Items from Other Collections", - "collection.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.", - "collection.item-mapper.notifications.map.error.head": "Mapping errors", - "collection.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.", - "collection.item-mapper.notifications.map.success.head": "Mapping completed", - "collection.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.", - "collection.item-mapper.notifications.unmap.error.head": "Remove mapping errors", - "collection.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.", - "collection.item-mapper.notifications.unmap.success.head": "Remove mapping completed", - "collection.item-mapper.remove": "Remove selected item mappings", - "collection.item-mapper.tabs.browse": "Browse", - "collection.item-mapper.tabs.map": "Map", - "collection.page.browse.recent.head": "Recent Submissions", "collection.page.browse.recent.empty": "No items to show", "collection.page.license": "License", "collection.page.news": "News", "collection.select.confirm": "Confirm selected", + "collection.select.empty": "No collections to show", "collection.select.table.title": "Title", "community.create.head": "Create a Community", @@ -258,6 +262,7 @@ "item.edit.item-mapper.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.", "item.edit.item-mapper.head": "Item Mapper - Map Item to Collections", "item.edit.item-mapper.item": "Item: \"{{name}}\"", + "item.edit.item-mapper.no-search": "Please enter a query to search", "item.edit.item-mapper.notifications.add.error.content": "Errors occurred for mapping of item to {{amount}} collections.", "item.edit.item-mapper.notifications.add.error.head": "Mapping errors", "item.edit.item-mapper.notifications.add.success.content": "Successfully mapped item to {{amount}} collections.", @@ -266,8 +271,8 @@ "item.edit.item-mapper.notifications.remove.error.head": "Removal of mapping errors", "item.edit.item-mapper.notifications.remove.success.content": "Successfully removed mapping of item to {{amount}} collections.", "item.edit.item-mapper.notifications.remove.success.head": "Removal of mapping completed", - "item.edit.item-mapper.tabs.browse": "Browse", - "item.edit.item-mapper.tabs.map": "Map", + "item.edit.item-mapper.tabs.browse": "Browse mapped collections", + "item.edit.item-mapper.tabs.map": "Map new collections", "item.edit.metadata.add-button": "Add", "item.edit.metadata.discard-button": "Discard", @@ -401,6 +406,7 @@ "item.page.uri": "URI", "item.select.confirm": "Confirm selected", + "item.select.empty": "No items to show", "item.select.table.author": "Author", "item.select.table.collection": "Collection", "item.select.table.title": "Title", diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html index 23e23b5c25..af4153220f 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html @@ -1,20 +1,20 @@
-

{{'collection.item-mapper.head' | translate}}

-

-

{{'collection.item-mapper.description' | translate}}

+

{{'collection.edit.item-mapper.head' | translate}}

+

+

{{'collection.edit.item-mapper.description' | translate}}

- +
- +
@@ -30,21 +30,25 @@ [query]="(searchOptions$ | async)?.query" [scope]="(searchOptions$ | async)?.scope" [currentUrl]="'./'" - [inPlaceSearch]="true"> + [inPlaceSearch]="true" + (submitSearch)="performedSearch = true">
-
+
+ diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts index 8f46a82077..4781e63c95 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts @@ -84,6 +84,12 @@ export class CollectionItemMapperComponent implements OnInit { */ shouldUpdate$: BehaviorSubject; + /** + * Track whether at least one search has been performed or not + * As soon as at least one search has been performed, we display the search results + */ + performedSearch = false; + constructor(private route: ActivatedRoute, private router: Router, @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService, @@ -128,7 +134,7 @@ export class CollectionItemMapperComponent implements OnInit { scope: undefined, dsoType: DSpaceObjectType.ITEM, sort: this.defaultSortOptions - })).pipe( + }), 1000).pipe( toDSpaceObjectListRD(), startWith(undefined) ); @@ -154,7 +160,6 @@ export class CollectionItemMapperComponent implements OnInit { ); this.showNotifications(responses$, remove); - this.clearRequestCache(); } /** @@ -170,8 +175,8 @@ export class CollectionItemMapperComponent implements OnInit { const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); if (successful.length > 0) { const successMessages = observableCombineLatest( - this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.head`), - this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.content`, { amount: successful.length }) + this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.head`), + this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.content`, { amount: successful.length }) ); successMessages.subscribe(([head, content]) => { @@ -180,8 +185,8 @@ export class CollectionItemMapperComponent implements OnInit { } if (unsuccessful.length > 0) { const unsuccessMessages = observableCombineLatest( - this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.head`), - this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.content`, { amount: unsuccessful.length }) + this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.error.head`), + this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.error.content`, { amount: unsuccessful.length }) ); unsuccessMessages.subscribe(([head, content]) => { @@ -194,21 +199,12 @@ export class CollectionItemMapperComponent implements OnInit { }); } - /** - * Clear all previous requests from cache in preparation of refreshing all lists - */ - private clearRequestCache() { - this.collectionRD$.pipe(take(1)).subscribe((collectionRD: RemoteData) => { - this.collectionDataService.clearMappedItemsRequests(collectionRD.payload.id); - this.searchService.clearDiscoveryRequests(); - }); - } - /** * Clear url parameters on tab change (temporary fix until pagination is improved) * @param event */ tabChange(event) { + this.performedSearch = false; this.router.navigateByUrl(this.getCurrentUrl()); } diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index ae5d04714a..66c623657d 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -64,7 +64,7 @@ const COLLECTION_EDIT_PATH = ':id/edit'; } }, { - path: ':id/mapper', + path: ':id/edit/mapper', component: CollectionItemMapperComponent, pathMatch: 'full', resolve: { 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 d4433cd4a2..43bf7ecd02 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 @@ -28,12 +28,13 @@ + [inPlaceSearch]="true" + (submitSearch)="performedSearch = true">
-
+
+ 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 a7a090f691..4ee8498bb7 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 @@ -69,6 +69,12 @@ export class ItemCollectionMapperComponent implements OnInit { */ shouldUpdate$: BehaviorSubject; + /** + * Track whether at least one search has been performed or not + * As soon as at least one search has been performed, we display the search results + */ + performedSearch = false; + constructor(private route: ActivatedRoute, private router: Router, private searchConfigService: SearchConfigurationService, @@ -112,7 +118,7 @@ export class ItemCollectionMapperComponent implements OnInit { return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), { query: this.buildQuery([...itemCollectionsRD.payload.page, owningCollectionRD.payload], searchOptions.query), dsoType: DSpaceObjectType.COLLECTION - })).pipe( + }), 1000).pipe( toDSpaceObjectListRD(), startWith(undefined) ); @@ -146,7 +152,6 @@ export class ItemCollectionMapperComponent implements OnInit { ); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add'); - this.clearRequestCache(); } /** @@ -161,7 +166,6 @@ export class ItemCollectionMapperComponent implements OnInit { ); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove'); - this.clearRequestCache(); } /** @@ -209,21 +213,12 @@ export class ItemCollectionMapperComponent implements OnInit { }); } - /** - * Clear all previous requests from cache in preparation of refreshing all lists - */ - private clearRequestCache() { - this.itemRD$.pipe(take(1)).subscribe((itemRD: RemoteData) => { - this.itemDataService.clearMappedCollectionsRequests(itemRD.payload.id); - this.searchService.clearDiscoveryRequests(); - }); - } - /** * Clear url parameters on tab change (temporary fix until pagination is improved) * @param event */ tabChange(event) { + this.performedSearch = false; this.router.navigateByUrl(this.getCurrentUrl()); } diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 8374813bc7..bedae84eaa 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -100,9 +100,10 @@ export class SearchService implements OnDestroy { /** * Method to retrieve a paginated list of search results from the server * @param {PaginatedSearchOptions} searchOptions The configuration necessary to perform this search + * @param responseMsToLive The amount of milliseconds for the response to live in cache * @returns {Observable>>>} Emits a paginated list with all search results found */ - search(searchOptions?: PaginatedSearchOptions): Observable>>> { + search(searchOptions?: PaginatedSearchOptions, responseMsToLive?: number): Observable>>> { const hrefObs = this.halService.getEndpoint(this.searchLinkPath).pipe( map((url: string) => { if (hasValue(searchOptions)) { @@ -122,6 +123,7 @@ export class SearchService implements OnDestroy { }; return Object.assign(request, { + responseMsToLive: hasValue(responseMsToLive) ? responseMsToLive : request.responseMsToLive, getResponseParser: getResponseParserFn }); }), @@ -373,15 +375,6 @@ export class SearchService implements OnDestroy { return '/search'; } - /** - * Clear all request cache related to discovery objects - */ - clearDiscoveryRequests() { - this.halService.getEndpoint(this.searchLinkPath).pipe(take(1)).subscribe((href: string) => { - this.requestService.removeByHrefSubstring(href); - }); - } - /** * Unsubscribe from the subscription */ diff --git a/src/app/core/data/collection-data.service.spec.ts b/src/app/core/data/collection-data.service.spec.ts index b0b3889c9c..5cb7fed5e4 100644 --- a/src/app/core/data/collection-data.service.spec.ts +++ b/src/app/core/data/collection-data.service.spec.ts @@ -41,14 +41,4 @@ describe('CollectionDataService', () => { }); }); - describe('clearMappedItemsRequests', () => { - beforeEach(() => { - service.clearMappedItemsRequests('collection-id'); - }); - - it('should remote request cache', () => { - expect(requestService.removeByHrefSubstring).toHaveBeenCalled(); - }); - }); - }); diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index cd59e7ac65..e49267d1f2 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -125,6 +125,7 @@ export class CollectionDataService extends ComColDataService { map((endpoint: string) => { const request = new GetRequest(requestUuid, endpoint); return Object.assign(request, { + responseMsToLive: 0, getResponseParser(): GenericConstructor { return DSOResponseParsingService; } @@ -136,14 +137,4 @@ export class CollectionDataService extends ComColDataService { return this.rdbService.buildList(href$); } - /** - * Clears all requests (from cache) connected to the mappedItems endpoint - * @param collectionId - */ - clearMappedItemsRequests(collectionId: string) { - this.getMappedItemsEndpoint(collectionId).pipe(take(1)).subscribe((href: string) => { - this.requestService.removeByHrefSubstring(href); - }); - } - } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index cec60f3305..de05dad0c1 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -148,16 +148,6 @@ export class ItemDataService extends DataService { return this.rdbService.toRemoteDataObservable(requestEntry$, payload$); } - /** - * Clears all requests (from cache) connected to the mappedCollections endpoint - * @param itemId - */ - public clearMappedCollectionsRequests(itemId: string) { - this.getMappedCollectionsEndpoint(itemId).pipe(take(1)).subscribe((href: string) => { - this.requestService.removeByHrefSubstring(href); - }); - } - /** * Get the endpoint for item withdrawal and reinstatement * @param itemId diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index b327306fcb..b0efc0ce71 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -190,6 +190,8 @@ export class BrowseItemsRequest extends GetRequest { * Request to fetch the mapped collections of an item */ export class MappedCollectionsRequest extends GetRequest { + public responseMsToLive = 0; + getResponseParser(): GenericConstructor { return MappedCollectionsReponseParsingService; } diff --git a/src/app/shared/object-select/collection-select/collection-select.component.html b/src/app/shared/object-select/collection-select/collection-select.component.html index 44307859ad..c8a0c4b879 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.html +++ b/src/app/shared/object-select/collection-select/collection-select.component.html @@ -7,7 +7,7 @@ [collectionSize]="collectionsRD?.payload?.totalElements" [hidePagerWhenSinglePage]="true" [hideGear]="true"> -
+
@@ -24,11 +24,18 @@
+ -
+
- - +
diff --git a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts index c9f79f6af5..af7c01a3c5 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts +++ b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts @@ -16,7 +16,7 @@ import { CollectionSelectComponent } from './collection-select.component'; import { Collection } from '../../../core/shared/collection.model'; import { of } from 'rxjs/internal/observable/of'; -describe('ItemSelectComponent', () => { +describe('CollectionSelectComponent', () => { let comp: CollectionSelectComponent; let fixture: ComponentFixture; let objectSelectService: ObjectSelectService; @@ -43,7 +43,7 @@ describe('ItemSelectComponent', () => { imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], declarations: [], providers: [ - { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, + { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockCollectionList[1].id]) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/object-select/item-select/item-select.component.html b/src/app/shared/object-select/item-select/item-select.component.html index 6691be3584..da31dbac65 100644 --- a/src/app/shared/object-select/item-select/item-select.component.html +++ b/src/app/shared/object-select/item-select/item-select.component.html @@ -7,7 +7,7 @@ [collectionSize]="itemsRD?.payload?.totalElements" [hidePagerWhenSinglePage]="true" [hideGear]="true"> -
+ + -
+
- - +
diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index 33fa4dcd7e..059e44064e 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -65,7 +65,7 @@ describe('ItemSelectComponent', () => { imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], declarations: [], providers: [ - { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, + { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/object-select/object-select.service.ts b/src/app/shared/object-select/object-select.service.ts index 03ddc0078c..8e30bca24d 100644 --- a/src/app/shared/object-select/object-select.service.ts +++ b/src/app/shared/object-select/object-select.service.ts @@ -51,7 +51,13 @@ export class ObjectSelectService { */ getAllSelected(key: string): Observable { return this.appStore.select(objectSelectionListStateSelector).pipe( - map((state: ObjectSelectionListState) => Object.keys(state[key]).filter((id) => state[key][id].checked)) + map((state: ObjectSelectionListState) => { + if (hasValue(state[key])) { + return Object.keys(state[key]).filter((id) => state[key][id].checked); + } else { + return []; + } + }) ); } diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index 7414dd70e6..6b81b103ca 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { Router } from '@angular/router'; import { hasValue, isNotEmpty } from '../empty.util'; @@ -56,6 +56,11 @@ export class SearchFormComponent { */ @Input() brandColor = 'primary'; + /** + * Output the search data on submit + */ + @Output() submitSearch = new EventEmitter(); + constructor(private router: Router, private searchService: SearchService) { } @@ -65,6 +70,7 @@ export class SearchFormComponent { */ onSubmit(data: any) { this.updateSearch(data); + this.submitSearch.emit(data); } /**