From 6b986c8c9172549b8a90c84401213c76b33d1c74 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 26 Sep 2018 09:44:01 +0200 Subject: [PATCH 001/142] 55693: Item mapper page + ItemSelectComponent --- resources/i18n/en.json | 13 ++++ .../collection-item-mapper.component.html | 45 ++++++++++++ .../collection-item-mapper.component.scss | 5 ++ .../collection-item-mapper.component.spec.ts | 0 .../collection-item-mapper.component.ts | 69 +++++++++++++++++++ .../collection-page-routing.module.ts | 9 +++ .../collection-page.module.ts | 2 + .../item-select/item-select.component.html | 20 ++++++ .../item-select/item-select.component.scss | 0 .../item-select/item-select.component.spec.ts | 0 .../item-select/item-select.component.ts | 28 ++++++++ src/app/shared/shared.module.ts | 4 +- 12 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html create mode 100644 src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss create mode 100644 src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts create mode 100644 src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts create mode 100644 src/app/shared/item-select/item-select.component.html create mode 100644 src/app/shared/item-select/item-select.component.scss create mode 100644 src/app/shared/item-select/item-select.component.spec.ts create mode 100644 src/app/shared/item-select/item-select.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index c7cb9a5ba7..e75441c477 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -13,6 +13,12 @@ "head": "Recent Submissions" } } + }, + "item-mapper": { + "head": "Item Mapper - Map Items from Other Collections", + "collection": "Collection: \"{{name}}\"", + "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.", + "return": "Return" } }, "community": { @@ -43,6 +49,13 @@ "simple": "Simple item page", "full": "Full item page" } + }, + "select": { + "table": { + "collection": "Collection", + "author": "Author", + "title": "Title" + } } }, "nav": { 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 new file mode 100644 index 0000000000..078eabd1c7 --- /dev/null +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html @@ -0,0 +1,45 @@ +
+
+
+

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

+

+

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

+ +
+
+ + +
+
+ + +
+ +
+ + +
+ + +
+ +
+
+ + +
+
+
diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss new file mode 100644 index 0000000000..4414c21645 --- /dev/null +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss @@ -0,0 +1,5 @@ +@import '../../../styles/variables.scss'; + +.tab:hover { + cursor: pointer; +} diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..4456d3138e --- /dev/null +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts @@ -0,0 +1,69 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { fadeIn, fadeInOut } from '../../shared/animations/fade'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { ActivatedRoute, PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router'; +import { RemoteData } from '../../core/data/remote-data'; +import { Observable } from 'rxjs/Observable'; +import { Collection } from '../../core/shared/collection.model'; +import { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service'; +import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; +import { PaginatedList } from '../../core/data/paginated-list'; +import { Item } from '../../core/shared/item.model'; +import { combineLatest, flatMap, map, tap } from 'rxjs/operators'; +import { getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; +import { SearchService } from '../../+search-page/search-service/search.service'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; + +@Component({ + selector: 'ds-collection-item-mapper', + styleUrls: ['./collection-item-mapper.component.scss'], + templateUrl: './collection-item-mapper.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeIn, + fadeInOut + ] +}) +export class CollectionItemMapperComponent implements OnInit { + + collectionRD$: Observable>; + searchOptions$: Observable; + collectionItemsRD$: Observable>>; + mappingItemsRD$: Observable>>; + + activeTab = 0; + + constructor(private collectionDataService: CollectionDataService, + private route: ActivatedRoute, + private router: Router, + private searchConfigService: SearchConfigurationService, + private searchService: SearchService) { + } + + ngOnInit(): void { + this.collectionRD$ = this.route.data.map((data) => data.collection); + this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; + this.collectionItemsRD$ = this.collectionRD$.pipe( + getSucceededRemoteData(), + combineLatest(this.searchOptions$), + flatMap(([collectionRD, options]) => { + return this.searchService.search(Object.assign(options, { + scope: collectionRD.payload.id + })); + }), + toDSpaceObjectListRD() + ); + this.mappingItemsRD$ = this.searchOptions$.pipe( + flatMap((options: PaginatedSearchOptions) => this.searchService.search(options)), + toDSpaceObjectListRD() + ); + } + + getCurrentUrl(): string { + const urlTree = this.router.parseUrl(this.router.url); + const g: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; + return '/' + g.toString(); + } + +} diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index ca56bca2cd..c85d102437 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -3,6 +3,7 @@ import { RouterModule } from '@angular/router'; import { CollectionPageComponent } from './collection-page.component'; import { CollectionPageResolver } from './collection-page.resolver'; +import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component'; @NgModule({ imports: [ @@ -14,6 +15,14 @@ import { CollectionPageResolver } from './collection-page.resolver'; resolve: { collection: CollectionPageResolver } + }, + { + path: ':id/mapper', + component: CollectionItemMapperComponent, + pathMatch: 'full', + resolve: { + collection: CollectionPageResolver + } } ]) ], diff --git a/src/app/+collection-page/collection-page.module.ts b/src/app/+collection-page/collection-page.module.ts index 85462e67a3..79efea46c0 100644 --- a/src/app/+collection-page/collection-page.module.ts +++ b/src/app/+collection-page/collection-page.module.ts @@ -6,6 +6,7 @@ import { SharedModule } from '../shared/shared.module'; import { CollectionPageComponent } from './collection-page.component'; import { CollectionPageRoutingModule } from './collection-page-routing.module'; import { SearchPageModule } from '../+search-page/search-page.module'; +import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component'; @NgModule({ imports: [ @@ -16,6 +17,7 @@ import { SearchPageModule } from '../+search-page/search-page.module'; ], declarations: [ CollectionPageComponent, + CollectionItemMapperComponent ] }) export class CollectionPageModule { diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/item-select/item-select.component.html new file mode 100644 index 0000000000..c2d02b4529 --- /dev/null +++ b/src/app/shared/item-select/item-select.component.html @@ -0,0 +1,20 @@ +
+ + + + + + + + + + + + + + + + + +
{{'item.select.table.collection' | translate}}{{'item.select.table.author' | translate}}{{'item.select.table.title' | translate}}
{{(item.owningCollection | async)?.payload?.name}}{{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}}{{item.findMetadata("dc.title")}}
+
diff --git a/src/app/shared/item-select/item-select.component.scss b/src/app/shared/item-select/item-select.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/item-select/item-select.component.spec.ts b/src/app/shared/item-select/item-select.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/item-select/item-select.component.ts new file mode 100644 index 0000000000..7159d94962 --- /dev/null +++ b/src/app/shared/item-select/item-select.component.ts @@ -0,0 +1,28 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { PaginatedList } from '../../core/data/paginated-list'; +import { RemoteData } from '../../core/data/remote-data'; +import { Observable } from 'rxjs/Observable'; +import { Item } from '../../core/shared/item.model'; + +@Component({ + selector: 'ds-item-select', + styleUrls: ['./item-select.component.scss'], + templateUrl: './item-select.component.html' +}) + +export class ItemSelectComponent implements OnInit { + + @Input() + items$: Observable>>; + + checked: boolean[] = []; + + constructor(private itemDataService: ItemDataService) { + } + + ngOnInit(): void { + this.items$ = this.itemDataService.findAll({}); + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index b6122dc70a..c5c6cad09b 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -83,6 +83,7 @@ import { InputSuggestionsComponent } from './input-suggestions/input-suggestions import { CapitalizePipe } from './utils/capitalize.pipe'; import { MomentModule } from 'angular2-moment'; import { ObjectKeysPipe } from './utils/object-keys-pipe'; +import { ItemSelectComponent } from './item-select/item-select.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -156,7 +157,8 @@ const COMPONENTS = [ TruncatableComponent, TruncatablePartComponent, BrowseByComponent, - InputSuggestionsComponent + InputSuggestionsComponent, + ItemSelectComponent ]; const ENTRY_COMPONENTS = [ From 315b6a9690fa7f3c7f0ed72a57c0ea2e37d60b22 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 26 Sep 2018 11:02:11 +0200 Subject: [PATCH 002/142] 55693: Intermediate commit --- .../collection-item-mapper.component.html | 2 +- .../collection-item-mapper.component.ts | 9 +++- .../item-select/item-select.component.html | 47 +++++++++++-------- .../item-select/item-select.component.ts | 10 ++-- 4 files changed, 42 insertions(+), 26 deletions(-) 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 078eabd1c7..1f7dd29396 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 @@ -35,7 +35,7 @@
- +
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 4456d3138e..81b964de36 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 @@ -49,13 +49,18 @@ export class CollectionItemMapperComponent implements OnInit { combineLatest(this.searchOptions$), flatMap(([collectionRD, options]) => { return this.searchService.search(Object.assign(options, { - scope: collectionRD.payload.id + scope: collectionRD.payload.id, + dsoType: DSpaceObjectType.ITEM })); }), toDSpaceObjectListRD() ); this.mappingItemsRD$ = this.searchOptions$.pipe( - flatMap((options: PaginatedSearchOptions) => this.searchService.search(options)), + flatMap((options: PaginatedSearchOptions) => { + return this.searchService.search(Object.assign(options, { + dsoType: DSpaceObjectType.ITEM + })); + }), toDSpaceObjectListRD() ); } diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/item-select/item-select.component.html index c2d02b4529..1705495a20 100644 --- a/src/app/shared/item-select/item-select.component.html +++ b/src/app/shared/item-select/item-select.component.html @@ -1,20 +1,27 @@ -
- - - - - - - - - - - - - - - - - -
{{'item.select.table.collection' | translate}}{{'item.select.table.author' | translate}}{{'item.select.table.title' | translate}}
{{(item.owningCollection | async)?.payload?.name}}{{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}}{{item.findMetadata("dc.title")}}
-
+ +
+ + + + + + + + + + + + + + + + + +
{{'item.select.table.collection' | translate}}{{'item.select.table.author' | translate}}{{'item.select.table.title' | translate}}
{{(item.owningCollection | async)?.payload?.name}}{{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}}{{item.findMetadata("dc.title")}}
+
+
diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/item-select/item-select.component.ts index 7159d94962..e8ac0eb6f4 100644 --- a/src/app/shared/item-select/item-select.component.ts +++ b/src/app/shared/item-select/item-select.component.ts @@ -1,9 +1,10 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { ItemDataService } from '../../core/data/item-data.service'; import { PaginatedList } from '../../core/data/paginated-list'; import { RemoteData } from '../../core/data/remote-data'; import { Observable } from 'rxjs/Observable'; import { Item } from '../../core/shared/item.model'; +import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; @Component({ selector: 'ds-item-select', @@ -14,7 +15,10 @@ import { Item } from '../../core/shared/item.model'; export class ItemSelectComponent implements OnInit { @Input() - items$: Observable>>; + itemsRD$: Observable>>; + + @Input() + paginationOptions: PaginationComponentOptions; checked: boolean[] = []; @@ -22,7 +26,7 @@ export class ItemSelectComponent implements OnInit { } ngOnInit(): void { - this.items$ = this.itemDataService.findAll({}); + this.itemsRD$.subscribe((value) => console.log(value)); } } From 3dc09062d043ac27b133a203dce1fe34820e40dc Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 26 Sep 2018 11:45:10 +0200 Subject: [PATCH 003/142] 55693: Tabset --- resources/i18n/en.json | 4 ++ .../collection-item-mapper.component.html | 39 ++++++++----------- .../collection-item-mapper.component.scss | 4 -- .../collection-item-mapper.component.ts | 2 - 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index e75441c477..63ee598199 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -18,6 +18,10 @@ "head": "Item Mapper - Map Items from Other Collections", "collection": "Collection: \"{{name}}\"", "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.", + "tabs": { + "browse": "Browse", + "map": "Map" + }, "return": "Return" } }, 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 1f7dd29396..3fdbdb4e7e 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 @@ -14,30 +14,23 @@ - -
- -
- - -
- - -
- -
-
+ + + + + + + + + + + + + diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss index 4414c21645..50be6f5ad0 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss @@ -1,5 +1 @@ @import '../../../styles/variables.scss'; - -.tab:hover { - cursor: pointer; -} 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 81b964de36..af0735acf7 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 @@ -32,8 +32,6 @@ export class CollectionItemMapperComponent implements OnInit { collectionItemsRD$: Observable>>; mappingItemsRD$: Observable>>; - activeTab = 0; - constructor(private collectionDataService: CollectionDataService, private route: ActivatedRoute, private router: Router, From 9440401ca34e49a7cfa89aed69b221bae760a56f Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 26 Sep 2018 13:16:56 +0200 Subject: [PATCH 004/142] 55693: Fixed scope being passed between tabs --- .../collection-item-mapper.component.html | 16 ++++++++++------ .../collection-item-mapper.component.ts | 11 +++++++++-- .../item-select/item-select.component.html | 5 +++-- 3 files changed, 22 insertions(+), 10 deletions(-) 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 3fdbdb4e7e..f8d770423b 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 @@ -18,16 +18,20 @@ - - +
+ + +
- +
+ +
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 af0735acf7..622bd14547 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 @@ -14,6 +14,7 @@ import { getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/ import { SearchService } from '../../+search-page/search-service/search.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; @Component({ selector: 'ds-collection-item-mapper', @@ -32,6 +33,8 @@ export class CollectionItemMapperComponent implements OnInit { collectionItemsRD$: Observable>>; mappingItemsRD$: Observable>>; + defaultSortOptions: SortOptions = new SortOptions('dc.title', SortDirection.ASC); + constructor(private collectionDataService: CollectionDataService, private route: ActivatedRoute, private router: Router, @@ -48,15 +51,19 @@ export class CollectionItemMapperComponent implements OnInit { flatMap(([collectionRD, options]) => { return this.searchService.search(Object.assign(options, { scope: collectionRD.payload.id, - dsoType: DSpaceObjectType.ITEM + dsoType: DSpaceObjectType.ITEM, + sort: this.defaultSortOptions })); }), toDSpaceObjectListRD() ); this.mappingItemsRD$ = this.searchOptions$.pipe( flatMap((options: PaginatedSearchOptions) => { + options.sort.field = 'dc.title'; return this.searchService.search(Object.assign(options, { - dsoType: DSpaceObjectType.ITEM + scope: undefined, + dsoType: DSpaceObjectType.ITEM, + sort: this.defaultSortOptions })); }), toDSpaceObjectListRD() diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/item-select/item-select.component.html index 1705495a20..d92d87e156 100644 --- a/src/app/shared/item-select/item-select.component.html +++ b/src/app/shared/item-select/item-select.component.html @@ -3,7 +3,8 @@ [paginationOptions]="paginationOptions" [pageInfoState]="(itemsRD$ | async)?.payload" [collectionSize]="(itemsRD$ | async)?.payload?.totalElements" - [hidePagerWhenSinglePage]="true"> + [hidePagerWhenSinglePage]="true" + [hideGear]="true">
@@ -18,7 +19,7 @@ - + From 5040d230fb46568bceb5ca85a12ae7f3dce7d9eb Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 26 Sep 2018 17:30:24 +0200 Subject: [PATCH 005/142] 55693: (incomplete) store interraction for selecting items --- src/app/app.reducer.ts | 7 +- src/app/core/core.module.ts | 2 + .../shared/item-select/item-select.actions.ts | 75 +++++++++++++ .../item-select/item-select.component.html | 4 +- .../item-select/item-select.component.ts | 11 +- .../shared/item-select/item-select.reducer.ts | 84 +++++++++++++++ .../shared/item-select/item-select.service.ts | 101 ++++++++++++++++++ 7 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 src/app/shared/item-select/item-select.actions.ts create mode 100644 src/app/shared/item-select/item-select.reducer.ts create mode 100644 src/app/shared/item-select/item-select.service.ts diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index 8dc82dfb6f..ba882b50b8 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -14,6 +14,7 @@ import { } from './+search-page/search-filters/search-filter/search-filter.reducer'; import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers'; import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer'; +import { itemSelectionReducer, ItemSelectionsState } from './shared/item-select/item-select.reducer'; export interface AppState { router: fromRouter.RouterReducerState; @@ -23,7 +24,8 @@ export interface AppState { notifications: NotificationsState; searchSidebar: SearchSidebarState; searchFilter: SearchFiltersState; - truncatable: TruncatablesState; + truncatable: TruncatablesState, + itemSelection: ItemSelectionsState } export const appReducers: ActionReducerMap = { @@ -34,7 +36,8 @@ export const appReducers: ActionReducerMap = { notifications: notificationsReducer, searchSidebar: sidebarReducer, searchFilter: filterReducer, - truncatable: truncatableReducer + truncatable: truncatableReducer, + itemSelection: itemSelectionReducer }; export const routerStateSelector = (state: AppState) => state.router; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 73e97c7933..31b9b31244 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -64,6 +64,7 @@ import { NotificationsService } from '../shared/notifications/notifications.serv 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'; const IMPORTS = [ CommonModule, @@ -128,6 +129,7 @@ const PROVIDERS = [ UploaderService, UUIDService, DSpaceObjectDataService, + ItemSelectService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/shared/item-select/item-select.actions.ts b/src/app/shared/item-select/item-select.actions.ts new file mode 100644 index 0000000000..0f17575a28 --- /dev/null +++ b/src/app/shared/item-select/item-select.actions.ts @@ -0,0 +1,75 @@ +import { type } from '../ngrx/type'; +import { Action } from '@ngrx/store'; + +export const ItemSelectionActionTypes = { + INITIAL_DESELECT: type('dspace/item-select/INITIAL_DESELECT'), + INITIAL_SELECT: type('dspace/item-select/INITIAL_SELECT'), + SELECT: type('dspace/item-select/SELECT'), + DESELECT: type('dspace/item-select/DESELECT'), + SWITCH: type('dspace/item-select/SWITCH'), + RESET: type('dspace/item-select/RESET') +}; + +export class ItemSelectionAction implements Action { + /** + * UUID of the item a select action can be performed on + */ + id: string; + + /** + * Type of action that will be performed + */ + type; + + /** + * Initialize with the item's UUID + * @param {string} id of the item + */ + constructor(id: string) { + this.id = id; + } +} + +/* tslint:disable:max-classes-per-file */ +/** + * Used to set the initial state to deselected + */ +export class ItemSelectionInitialDeselectAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.INITIAL_DESELECT; +} + +/** + * Used to set the initial state to selected + */ +export class ItemSelectionInitialSelectAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.INITIAL_SELECT; +} + +/** + * Used to select an item + */ +export class ItemSelectionSelectAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.SELECT; +} + +/** + * Used to deselect an item + */ +export class ItemSelectionDeselectAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.DESELECT; +} + +/** + * Used to switch an item between selected and deselected + */ +export class ItemSelectionSwitchAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.SWITCH; +} + +/** + * Used to reset all item's selected to be deselected + */ +export class ItemSelectionResetAction extends ItemSelectionAction { + type = ItemSelectionActionTypes.RESET; +} +/* tslint:enable:max-classes-per-file */ diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/item-select/item-select.component.html index d92d87e156..c20e2cd6c1 100644 --- a/src/app/shared/item-select/item-select.component.html +++ b/src/app/shared/item-select/item-select.component.html @@ -16,8 +16,8 @@ - - + + diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/item-select/item-select.component.ts index e8ac0eb6f4..e24ce1c4fa 100644 --- a/src/app/shared/item-select/item-select.component.ts +++ b/src/app/shared/item-select/item-select.component.ts @@ -5,6 +5,7 @@ import { RemoteData } from '../../core/data/remote-data'; import { Observable } from 'rxjs/Observable'; import { Item } from '../../core/shared/item.model'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { ItemSelectService } from './item-select.service'; @Component({ selector: 'ds-item-select', @@ -22,11 +23,19 @@ export class ItemSelectComponent implements OnInit { checked: boolean[] = []; - constructor(private itemDataService: ItemDataService) { + constructor(private itemSelectService: ItemSelectService) { } ngOnInit(): void { this.itemsRD$.subscribe((value) => console.log(value)); } + switch(id: string) { + this.itemSelectService.switch(id); + } + + getSelected(id: string): Observable { + return this.itemSelectService.getSelected(id); + } + } diff --git a/src/app/shared/item-select/item-select.reducer.ts b/src/app/shared/item-select/item-select.reducer.ts new file mode 100644 index 0000000000..1ea443850b --- /dev/null +++ b/src/app/shared/item-select/item-select.reducer.ts @@ -0,0 +1,84 @@ +import { isEmpty } from '../empty.util'; +import { ItemSelectionAction, ItemSelectionActionTypes } from './item-select.actions'; + +/** + * Interface that represents the state for a single filters + */ +export interface ItemSelectionState { + checked: boolean; +} + +/** + * Interface that represents the state for all available filters + */ +export interface ItemSelectionsState { + [id: string]: ItemSelectionState +} + +const initialState: ItemSelectionsState = Object.create(null); + +/** + * Performs a search filter action on the current state + * @param {SearchFiltersState} state The state before the action is performed + * @param {SearchFilterAction} action The action that should be performed + * @returns {SearchFiltersState} The state after the action is performed + */ +export function itemSelectionReducer(state = initialState, action: ItemSelectionAction): ItemSelectionsState { + + switch (action.type) { + + case ItemSelectionActionTypes.INITIAL_SELECT: { + if (isEmpty(state) || isEmpty(state[action.id])) { + return Object.assign({}, state, { + [action.id]: { + checked: true + } + }); + } + return state; + } + + case ItemSelectionActionTypes.INITIAL_DESELECT: { + if (isEmpty(state) || isEmpty(state[action.id])) { + return Object.assign({}, state, { + [action.id]: { + checked: false + } + }); + } + return state; + } + + case ItemSelectionActionTypes.SELECT: { + return Object.assign({}, state, { + [action.id]: { + checked: true + } + }); + } + + case ItemSelectionActionTypes.DESELECT: { + return Object.assign({}, state, { + [action.id]: { + checked: false + } + }); + } + + case ItemSelectionActionTypes.SWITCH: { + return Object.assign({}, state, { + [action.id]: { + checked: !state.checked + } + }); + } + + case ItemSelectionActionTypes.RESET: { + return {}; + } + + default: { + return state; + } + } +} diff --git a/src/app/shared/item-select/item-select.service.ts b/src/app/shared/item-select/item-select.service.ts new file mode 100644 index 0000000000..3ba9f9a579 --- /dev/null +++ b/src/app/shared/item-select/item-select.service.ts @@ -0,0 +1,101 @@ +import { Injectable } from '@angular/core'; +import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; +import { ItemSelectionsState, ItemSelectionState } from './item-select.reducer'; +import { + ItemSelectionDeselectAction, + ItemSelectionInitialDeselectAction, + ItemSelectionInitialSelectAction, ItemSelectionResetAction, + ItemSelectionSelectAction, ItemSelectionSwitchAction +} from './item-select.actions'; +import { Observable } from 'rxjs/Observable'; +import { hasValue } from '../empty.util'; + +const selectionStateSelector = (state: ItemSelectionsState) => state.selectionItem; + +/** + * Service that takes care of selecting and deselecting items + */ +@Injectable() +export class ItemSelectService { + + constructor(private store: Store) { + } + + /** + * Request the current selection of a given item + * @param {string} id The UUID of the item + * @returns {Observable} Emits the current selection state of the given item, if it's unavailable, return false + */ + getSelected(id: string): Observable { + return this.store.select(selectionByIdSelector(id)) + .map((object: ItemSelectionState) => { + if (object) { + return object.checked; + } else { + return false; + } + }); + } + + /** + * Dispatches an initial select action to the store for a given item + * @param {string} id The UUID of the item to select + */ + public initialSelect(id: string): void { + this.store.dispatch(new ItemSelectionInitialSelectAction(id)); + } + + /** + * Dispatches an initial deselect action to the store for a given item + * @param {string} id The UUID of the item to deselect + */ + public initialDeselect(id: string): void { + this.store.dispatch(new ItemSelectionInitialDeselectAction(id)); + } + + /** + * Dispatches a select action to the store for a given item + * @param {string} id The UUID of the item to select + */ + public select(id: string): void { + this.store.dispatch(new ItemSelectionSelectAction(id)); + } + + /** + * Dispatches a deselect action to the store for a given item + * @param {string} id The UUID of the item to deselect + */ + public deselect(id: string): void { + this.store.dispatch(new ItemSelectionDeselectAction(id)); + } + + /** + * Dispatches a switch action to the store for a given item + * @param {string} id The UUID of the item to select + */ + public switch(id: string): void { + this.store.dispatch(new ItemSelectionSwitchAction(id)); + } + + /** + * Dispatches a reset action to the store for all items + */ + public reset(): void { + this.store.dispatch(new ItemSelectionResetAction(null)); + } + +} + +function selectionByIdSelector(id: string): MemoizedSelector { + return keySelector(id); +} + +export function keySelector(key: string): MemoizedSelector { + return createSelector(selectionStateSelector, (state: ItemSelectionState) => { + if (hasValue(state)) { + return state[key]; + } else { + return undefined; + } + }); +} From 09a84edb090893e3717df93988f8f537a8176656 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 27 Sep 2018 11:09:36 +0200 Subject: [PATCH 006/142] 55693: Working store interaction for selecting items --- resources/i18n/en.json | 3 +- .../collection-item-mapper.component.html | 2 +- .../collection-item-mapper.component.ts | 4 +++ .../item-select/item-select.component.html | 3 +- .../item-select/item-select.component.ts | 15 ++++++++-- .../shared/item-select/item-select.reducer.ts | 2 +- .../shared/item-select/item-select.service.ts | 28 +++++++++++++++---- 7 files changed, 45 insertions(+), 12 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 63ee598199..948f538e9a 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -59,7 +59,8 @@ "collection": "Collection", "author": "Author", "title": "Title" - } + }, + "confirm": "Confirm selected" } }, "nav": { 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 f8d770423b..d201ce0ad6 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 @@ -30,7 +30,7 @@
- +
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 622bd14547..f796abc54a 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 @@ -70,6 +70,10 @@ export class CollectionItemMapperComponent implements OnInit { ); } + mapItems(ids: string[]) { + console.log(ids); + } + getCurrentUrl(): string { const urlTree = this.router.parseUrl(this.router.url); const g: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/item-select/item-select.component.html index c20e2cd6c1..4047884bc4 100644 --- a/src/app/shared/item-select/item-select.component.html +++ b/src/app/shared/item-select/item-select.component.html @@ -17,7 +17,7 @@
- + @@ -26,3 +26,4 @@
{{(item.owningCollection | async)?.payload?.name}}{{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}}{{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}} {{item.findMetadata("dc.title")}}
{{(item.owningCollection | async)?.payload?.name}} {{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}} {{item.findMetadata("dc.title")}}
{{(item.owningCollection | async)?.payload?.name}} {{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}} {{item.findMetadata("dc.title")}}
+ diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/item-select/item-select.component.ts index e24ce1c4fa..93128aa1d5 100644 --- a/src/app/shared/item-select/item-select.component.ts +++ b/src/app/shared/item-select/item-select.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { ItemDataService } from '../../core/data/item-data.service'; import { PaginatedList } from '../../core/data/paginated-list'; import { RemoteData } from '../../core/data/remote-data'; @@ -21,13 +21,16 @@ export class ItemSelectComponent implements OnInit { @Input() paginationOptions: PaginationComponentOptions; - checked: boolean[] = []; + @Output() + confirm: EventEmitter = new EventEmitter(); + + selectedIds$: Observable; constructor(private itemSelectService: ItemSelectService) { } ngOnInit(): void { - this.itemsRD$.subscribe((value) => console.log(value)); + this.selectedIds$ = this.itemSelectService.getAllSelected(); } switch(id: string) { @@ -38,4 +41,10 @@ export class ItemSelectComponent implements OnInit { return this.itemSelectService.getSelected(id); } + confirmSelected() { + this.selectedIds$.subscribe((ids: string[]) => { + this.confirm.emit(ids); + }); + } + } diff --git a/src/app/shared/item-select/item-select.reducer.ts b/src/app/shared/item-select/item-select.reducer.ts index 1ea443850b..6306adf0c4 100644 --- a/src/app/shared/item-select/item-select.reducer.ts +++ b/src/app/shared/item-select/item-select.reducer.ts @@ -68,7 +68,7 @@ export function itemSelectionReducer(state = initialState, action: ItemSelection case ItemSelectionActionTypes.SWITCH: { return Object.assign({}, state, { [action.id]: { - checked: !state.checked + checked: (isEmpty(state) || isEmpty(state[action.id])) ? true : !state[action.id].checked } }); } diff --git a/src/app/shared/item-select/item-select.service.ts b/src/app/shared/item-select/item-select.service.ts index 3ba9f9a579..bf4c7ba239 100644 --- a/src/app/shared/item-select/item-select.service.ts +++ b/src/app/shared/item-select/item-select.service.ts @@ -9,8 +9,11 @@ import { } from './item-select.actions'; import { Observable } from 'rxjs/Observable'; import { hasValue } from '../empty.util'; +import { map } from 'rxjs/operators'; +import { AppState } from '../../app.reducer'; -const selectionStateSelector = (state: ItemSelectionsState) => state.selectionItem; +const selectionStateSelector = (state: ItemSelectionsState) => state.itemSelection; +const itemSelectionsStateSelector = (state: AppState) => state.itemSelection; /** * Service that takes care of selecting and deselecting items @@ -18,7 +21,10 @@ const selectionStateSelector = (state: ItemSelectionsState) => state.selectionIt @Injectable() export class ItemSelectService { - constructor(private store: Store) { + constructor( + private store: Store, + private appStore: Store + ) { } /** @@ -27,14 +33,26 @@ export class ItemSelectService { * @returns {Observable} Emits the current selection state of the given item, if it's unavailable, return false */ getSelected(id: string): Observable { - return this.store.select(selectionByIdSelector(id)) - .map((object: ItemSelectionState) => { + return this.store.select(selectionByIdSelector(id)).pipe( + map((object: ItemSelectionState) => { if (object) { return object.checked; } else { return false; } - }); + }) + ); + } + + /** + * Request the current selection of a given item + * @param {string} id The UUID of the item + * @returns {Observable} Emits the current selection state of the given item, if it's unavailable, return false + */ + getAllSelected(): Observable { + return this.appStore.select(itemSelectionsStateSelector).pipe( + map((state: ItemSelectionsState) => Object.keys(state).filter((key) => state[key].checked)) + ); } /** From ce134bbd107bb494f51146b9e456b7df2e42fc89 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 27 Sep 2018 11:46:26 +0200 Subject: [PATCH 007/142] 55693: emit only once and reset selected items + notification --- .../collection-item-mapper.component.ts | 11 +++++++---- src/app/shared/item-select/item-select.component.ts | 6 +++++- 2 files changed, 12 insertions(+), 5 deletions(-) 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 f796abc54a..b06f486072 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 @@ -15,6 +15,7 @@ import { SearchService } from '../../+search-page/search-service/search.service' import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; @Component({ selector: 'ds-collection-item-mapper', @@ -35,11 +36,11 @@ export class CollectionItemMapperComponent implements OnInit { defaultSortOptions: SortOptions = new SortOptions('dc.title', SortDirection.ASC); - constructor(private collectionDataService: CollectionDataService, - private route: ActivatedRoute, + constructor(private route: ActivatedRoute, private router: Router, private searchConfigService: SearchConfigurationService, - private searchService: SearchService) { + private searchService: SearchService, + private notificationsService: NotificationsService) { } ngOnInit(): void { @@ -71,7 +72,9 @@ export class CollectionItemMapperComponent implements OnInit { } mapItems(ids: string[]) { - console.log(ids); + this.collectionRD$.subscribe((collectionRD: RemoteData) => { + this.notificationsService.success('Mapping completed', `Successfully mapped ${ids.length} items to collection "${collectionRD.payload.name}".`); + }); } getCurrentUrl(): string { diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/item-select/item-select.component.ts index 93128aa1d5..01a4c2e6f1 100644 --- a/src/app/shared/item-select/item-select.component.ts +++ b/src/app/shared/item-select/item-select.component.ts @@ -6,6 +6,7 @@ import { Observable } from 'rxjs/Observable'; import { Item } from '../../core/shared/item.model'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; import { ItemSelectService } from './item-select.service'; +import { take } from 'rxjs/operators'; @Component({ selector: 'ds-item-select', @@ -42,8 +43,11 @@ export class ItemSelectComponent implements OnInit { } confirmSelected() { - this.selectedIds$.subscribe((ids: string[]) => { + this.selectedIds$.pipe( + take(1) + ).subscribe((ids: string[]) => { this.confirm.emit(ids); + this.itemSelectService.reset(); }); } From 4731da83cd31ce5aa36969f259e169ef56e33d35 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 27 Sep 2018 13:16:26 +0200 Subject: [PATCH 008/142] 55693: Intermediate Commit --- .../collection-item-mapper.component.ts | 12 +++++++- src/app/core/data/item-data.service.ts | 30 +++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) 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 b06f486072..04657fe4fd 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 @@ -16,6 +16,8 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { forkJoin } from 'rxjs/observable/forkJoin'; @Component({ selector: 'ds-collection-item-mapper', @@ -40,7 +42,8 @@ export class CollectionItemMapperComponent implements OnInit { private router: Router, private searchConfigService: SearchConfigurationService, private searchService: SearchService, - private notificationsService: NotificationsService) { + private notificationsService: NotificationsService, + private itemDataService: ItemDataService) { } ngOnInit(): void { @@ -72,6 +75,13 @@ export class CollectionItemMapperComponent implements OnInit { } mapItems(ids: string[]) { + const responses = this.collectionRD$.pipe( + map((collectionRD: RemoteData) => collectionRD.payload), + flatMap((collection: Collection) => forkJoin(ids.map((id: string) => this.itemDataService.mapToCollection(id, collection.id)))) + ); + + responses.subscribe((value) => console.log(value)); + this.collectionRD$.subscribe((collectionRD: RemoteData) => { this.notificationsService.success('Mapping completed', `Successfully mapped ${ids.length} items to collection "${collectionRD.payload.name}".`); }); diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index f984dceb12..5b26c8f252 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 } from '../../shared/empty.util'; +import { 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,7 +15,11 @@ 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 } from './request.models'; +import { FindAllOptions, PostRequest, RestRequest } from './request.models'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { configureRequest, getResponseFromSelflink } from '../shared/operators'; +import { ResponseCacheEntry } from '../cache/response-cache.reducer'; +import { RestResponse } from '../cache/response-cache.models'; @Injectable() export class ItemDataService extends DataService { @@ -48,4 +52,26 @@ export class ItemDataService extends DataService { .distinctUntilChanged(); } + public getMappingCollectionsEndpoint(itemId: string, collectionId?: string): Observable { + return this.halService.getEndpoint(this.linkPath).pipe( + map((endpoint: string) => this.getFindByIDHref(endpoint, itemId)), + map((endpoint: string) => `${endpoint}/mappingCollections${collectionId ? `/${collectionId}` : ''}`) + ); + } + + public mapToCollection(itemId: string, collectionId: string): Observable { + const request$ = this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( + isNotEmptyOperator(), + distinctUntilChanged(), + map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)), + configureRequest(this.requestService) + ); + + return request$.pipe( + map((request: RestRequest) => request.href), + getResponseFromSelflink(this.responseCache), + map((responseCacheEntry: ResponseCacheEntry) => responseCacheEntry.response) + ); + } + } From 7b74d9c07752df3f26e7f58ebd14be03af4818ec Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 27 Sep 2018 17:08:52 +0200 Subject: [PATCH 009/142] 55693: Notifications on success/failure and small refactoring --- .../collection-item-mapper.component.ts | 39 ++++++++++++------- src/app/core/data/item-data.service.ts | 7 +--- 2 files changed, 27 insertions(+), 19 deletions(-) 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 04657fe4fd..6d250d63eb 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 @@ -9,8 +9,8 @@ import { SearchConfigurationService } from '../../+search-page/search-service/se import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; import { PaginatedList } from '../../core/data/paginated-list'; import { Item } from '../../core/shared/item.model'; -import { combineLatest, flatMap, map, tap } from 'rxjs/operators'; -import { getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; +import { combineLatest, filter, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; +import { filterSuccessfulResponses, getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; import { SearchService } from '../../+search-page/search-service/search.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; @@ -18,6 +18,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ItemDataService } from '../../core/data/item-data.service'; import { forkJoin } from 'rxjs/observable/forkJoin'; +import { RestResponse } from '../../core/cache/response-cache.models'; @Component({ selector: 'ds-collection-item-mapper', @@ -47,12 +48,16 @@ export class CollectionItemMapperComponent implements OnInit { } ngOnInit(): void { - this.collectionRD$ = this.route.data.map((data) => data.collection); + this.collectionRD$ = this.route.data.map((data) => data.collection).pipe(getSucceededRemoteData()) as Observable>; this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; - this.collectionItemsRD$ = this.collectionRD$.pipe( - getSucceededRemoteData(), - combineLatest(this.searchOptions$), - flatMap(([collectionRD, options]) => { + + const collectionAndOptions$ = Observable.combineLatest( + this.collectionRD$, + this.searchOptions$ + ); + + this.collectionItemsRD$ = collectionAndOptions$.pipe( + switchMap(([collectionRD, options]) => { return this.searchService.search(Object.assign(options, { scope: collectionRD.payload.id, dsoType: DSpaceObjectType.ITEM, @@ -75,15 +80,21 @@ export class CollectionItemMapperComponent implements OnInit { } mapItems(ids: string[]) { - const responses = this.collectionRD$.pipe( - map((collectionRD: RemoteData) => collectionRD.payload), - flatMap((collection: Collection) => forkJoin(ids.map((id: string) => this.itemDataService.mapToCollection(id, collection.id)))) + const responses$ = this.collectionRD$.pipe( + getSucceededRemoteData(), + map((collectionRD: RemoteData) => collectionRD.payload.id), + switchMap((collectionId: string) => Observable.combineLatest(ids.map((id: string) => this.itemDataService.mapToCollection(id, collectionId)))) ); - responses.subscribe((value) => console.log(value)); - - this.collectionRD$.subscribe((collectionRD: RemoteData) => { - this.notificationsService.success('Mapping completed', `Successfully mapped ${ids.length} items to collection "${collectionRD.payload.name}".`); + responses$.subscribe((responses: RestResponse[]) => { + const successful = responses.filter((response: RestResponse) => response.isSuccessful); + const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); + if (successful.length > 0) { + this.notificationsService.success('Mapping completed', `Successfully mapped ${successful.length} items.`); + } + if (unsuccessful.length > 0) { + this.notificationsService.error('Mapping errors', `Errors occurred for mapping of ${unsuccessful.length} items.`); + } }); } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 5b26c8f252..7c2c4e572d 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -60,14 +60,11 @@ export class ItemDataService extends DataService { } public mapToCollection(itemId: string, collectionId: string): Observable { - const request$ = this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( + return this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( isNotEmptyOperator(), distinctUntilChanged(), map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)), - configureRequest(this.requestService) - ); - - return request$.pipe( + configureRequest(this.requestService), map((request: RestRequest) => request.href), getResponseFromSelflink(this.responseCache), map((responseCacheEntry: ResponseCacheEntry) => responseCacheEntry.response) From ba8be3f57d6e507e0e02519e5b7542606df935d0 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 28 Sep 2018 10:56:17 +0200 Subject: [PATCH 010/142] 55693: Temporary pagination fix (remove url params on tab change) --- .../collection-item-mapper.component.html | 5 +++-- .../collection-item-mapper.component.ts | 17 +++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) 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 d201ce0ad6..f18c76f04d 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 @@ -15,14 +15,15 @@ - +
+ [objects]="collectionItemsRD$ | async" + [hideGear]="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 6d250d63eb..eab9292e98 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 @@ -1,6 +1,5 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { fadeIn, fadeInOut } from '../../shared/animations/fade'; -import { CollectionDataService } from '../../core/data/collection-data.service'; import { ActivatedRoute, PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; import { Observable } from 'rxjs/Observable'; @@ -8,16 +7,14 @@ import { Collection } from '../../core/shared/collection.model'; import { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service'; import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; import { PaginatedList } from '../../core/data/paginated-list'; -import { Item } from '../../core/shared/item.model'; -import { combineLatest, filter, flatMap, map, switchMap, take, tap } from 'rxjs/operators'; -import { filterSuccessfulResponses, getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; +import { flatMap, map, switchMap } from 'rxjs/operators'; +import { getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; import { SearchService } from '../../+search-page/search-service/search.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ItemDataService } from '../../core/data/item-data.service'; -import { forkJoin } from 'rxjs/observable/forkJoin'; import { RestResponse } from '../../core/cache/response-cache.models'; @Component({ @@ -68,7 +65,6 @@ export class CollectionItemMapperComponent implements OnInit { ); this.mappingItemsRD$ = this.searchOptions$.pipe( flatMap((options: PaginatedSearchOptions) => { - options.sort.field = 'dc.title'; return this.searchService.search(Object.assign(options, { scope: undefined, dsoType: DSpaceObjectType.ITEM, @@ -98,6 +94,15 @@ export class CollectionItemMapperComponent implements OnInit { }); } + 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 + if (this.router.url.indexOf('?') > -1) { + const url: string = this.router.url.substring(0, this.router.url.indexOf('?')); + this.router.navigateByUrl(url); + } + } + getCurrentUrl(): string { const urlTree = this.router.parseUrl(this.router.url); const g: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; From 0a8a6bb7209ceca4c7131e395bade976182fee5a Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 28 Sep 2018 11:27:53 +0200 Subject: [PATCH 011/142] 55693: Messages improvement --- resources/i18n/en.json | 11 ++++++++ .../collection-item-mapper.component.html | 6 ++++- .../collection-item-mapper.component.ts | 26 ++++++++++++++++--- .../item-select/item-select.component.html | 2 +- .../item-select/item-select.component.ts | 3 +++ 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 948f538e9a..86ff55da66 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -18,10 +18,21 @@ "head": "Item Mapper - Map Items from Other Collections", "collection": "Collection: \"{{name}}\"", "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", "tabs": { "browse": "Browse", "map": "Map" }, + "notifications": { + "success": { + "head": "Mapping completed", + "content": "Successfully mapped {{amount}} items." + }, + "error": { + "head": "Mapping errors", + "content": "Errors occurred for mapping of {{amount}} items." + } + }, "return": "Return" } }, 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 f18c76f04d..88a39f3be9 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 @@ -31,7 +31,11 @@
- +
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 eab9292e98..806e535e5f 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 @@ -16,6 +16,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ItemDataService } from '../../core/data/item-data.service'; import { RestResponse } from '../../core/cache/response-cache.models'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'ds-collection-item-mapper', @@ -41,18 +42,21 @@ export class CollectionItemMapperComponent implements OnInit { private searchConfigService: SearchConfigurationService, private searchService: SearchService, private notificationsService: NotificationsService, - private itemDataService: ItemDataService) { + private itemDataService: ItemDataService, + private translateService: TranslateService) { } ngOnInit(): void { this.collectionRD$ = this.route.data.map((data) => data.collection).pipe(getSucceededRemoteData()) as Observable>; this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; + this.loadItemLists(); + } + loadItemLists() { const collectionAndOptions$ = Observable.combineLatest( this.collectionRD$, this.searchOptions$ ); - this.collectionItemsRD$ = collectionAndOptions$.pipe( switchMap(([collectionRD, options]) => { return this.searchService.search(Object.assign(options, { @@ -86,10 +90,24 @@ export class CollectionItemMapperComponent implements OnInit { const successful = responses.filter((response: RestResponse) => response.isSuccessful); const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); if (successful.length > 0) { - this.notificationsService.success('Mapping completed', `Successfully mapped ${successful.length} items.`); + const successMessages = Observable.combineLatest( + this.translateService.get('collection.item-mapper.notifications.success.head'), + this.translateService.get('collection.item-mapper.notifications.success.content', { amount: successful.length }) + ); + + successMessages.subscribe(([head, content]) => { + this.notificationsService.success(head, content); + }); } if (unsuccessful.length > 0) { - this.notificationsService.error('Mapping errors', `Errors occurred for mapping of ${unsuccessful.length} items.`); + const unsuccessMessages = Observable.combineLatest( + this.translateService.get('collection.item-mapper.notifications.error.head'), + this.translateService.get('collection.item-mapper.notifications.error.content', { amount: unsuccessful.length }) + ); + + unsuccessMessages.subscribe(([head, content]) => { + this.notificationsService.error(head, content); + }); } }); } diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/item-select/item-select.component.html index 4047884bc4..76fc118282 100644 --- a/src/app/shared/item-select/item-select.component.html +++ b/src/app/shared/item-select/item-select.component.html @@ -26,4 +26,4 @@ - + diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/item-select/item-select.component.ts index 01a4c2e6f1..00ca078bfa 100644 --- a/src/app/shared/item-select/item-select.component.ts +++ b/src/app/shared/item-select/item-select.component.ts @@ -22,6 +22,9 @@ export class ItemSelectComponent implements OnInit { @Input() paginationOptions: PaginationComponentOptions; + @Input() + confirmButton = 'item.select.confirm'; + @Output() confirm: EventEmitter = new EventEmitter(); From e1c734e1137d605dc81111ad9e65048f7ac09476 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 28 Sep 2018 12:43:38 +0200 Subject: [PATCH 012/142] 55693: CollectionItemMapperComponent test intermediate --- .../collection-item-mapper.component.spec.ts | 83 +++++++++++++++++++ src/app/shared/testing/active-router-stub.ts | 20 ++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index e69de29bb2..18558c6314 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -0,0 +1,83 @@ +import { CollectionItemMapperComponent } from './collection-item-mapper.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CommonModule } from '@angular/common'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { SearchFormComponent } from '../../shared/search-form/search-form.component'; +import { SearchPageModule } from '../../+search-page/search-page.module'; +import { ObjectCollectionComponent } from '../../shared/object-collection/object-collection.component'; +import { ItemSelectComponent } from '../../shared/item-select/item-select.component'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; +import { RouterStub } from '../../shared/testing/router-stub'; +import { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service'; +import { SearchService } from '../../+search-page/search-service/search.service'; +import { SearchServiceStub } from '../../shared/testing/search-service-stub'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { FormsModule } from '@angular/forms'; +import { SharedModule } from '../../shared/shared.module'; +import { Collection } from '../../core/shared/collection.model'; + +fdescribe('CollectionItemMapperComponent', () => { + let comp: CollectionItemMapperComponent; + let fixture: ComponentFixture; + + let route: ActivatedRoute; + let router: Router; + let searchConfigService: SearchConfigurationService; + let searchService: SearchService; + let notificationsService: NotificationsService; + let itemDataService: ItemDataService; + + const searchConfigServiceStub = { + + }; + const itemDataServiceStub = { + + }; + const mockCollection: Collection = Object.assign(new Collection(), { + metadata: [ + { + key: 'dc.title', + language: 'en_US', + value: 'Test title' + }] + }); + const activatedRouteStub = new ActivatedRouteStub({}, { collection: mockCollection }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CommonModule, FormsModule, SharedModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [CollectionItemMapperComponent], + providers: [ + { provide: ActivatedRoute, useValue: activatedRouteStub }, + { provide: Router, useValue: new RouterStub() }, + { provide: SearchConfigurationService, useValue: searchConfigServiceStub }, + { provide: SearchService, useValue: new SearchServiceStub() }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: ItemDataService, useValue: itemDataServiceStub }, + { provide: TranslateService, useValue: {} } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectionItemMapperComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + route = (comp as any).route; + router = (comp as any).router; + searchConfigService = (comp as any).searchConfigService; + searchService = (comp as any).searchService; + notificationsService = (comp as any).notificationsService; + itemDataService = (comp as any).itemDataService; + }); + + it('should test', () => { + + }); + +}); diff --git a/src/app/shared/testing/active-router-stub.ts b/src/app/shared/testing/active-router-stub.ts index 22c4060855..2ed3f367d3 100644 --- a/src/app/shared/testing/active-router-stub.ts +++ b/src/app/shared/testing/active-router-stub.ts @@ -5,19 +5,27 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject'; export class ActivatedRouteStub { private _testParams?: any; + private _testData?: any; // ActivatedRoute.params is Observable private subject?: BehaviorSubject = new BehaviorSubject(this.testParams); + private dataSubject?: BehaviorSubject = new BehaviorSubject(this.testData); params = this.subject.asObservable(); queryParams = this.subject.asObservable(); queryParamMap = this.subject.asObservable().map((params: Params) => convertToParamMap(params)); + data = this.dataSubject.asObservable(); - constructor(params?: Params) { + constructor(params?: Params, data?: any) { if (params) { this.testParams = params; } else { this.testParams = {}; } + if (data) { + this.testData = data; + } else { + this.testData = {}; + } } // Test parameters @@ -30,6 +38,16 @@ export class ActivatedRouteStub { this.subject.next(params); } + // Test data + get testData() { + return this._testParams; + } + + set testData(data: {}) { + this._testData = data; + this.dataSubject.next(data); + } + // ActivatedRoute.snapshot.params get snapshot() { return { From 95b46352918b391ff611288a1b0d998d3c622a0d Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 28 Sep 2018 16:02:39 +0200 Subject: [PATCH 013/142] 55693: CollectionItemMapperComponent tests --- .../collection-item-mapper.component.html | 2 +- .../collection-item-mapper.component.spec.ts | 84 +++++++++++++++---- .../collection-item-mapper.component.ts | 12 ++- 3 files changed, 75 insertions(+), 23 deletions(-) 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 88a39f3be9..a7a1416cb0 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 @@ -2,7 +2,7 @@

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

-

+

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

diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index 18558c6314..a08322dc1b 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -1,5 +1,5 @@ import { CollectionItemMapperComponent } from './collection-item-mapper.component'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { CommonModule } from '@angular/common'; @@ -20,6 +20,18 @@ import { ItemDataService } from '../../core/data/item-data.service'; import { FormsModule } from '@angular/forms'; import { SharedModule } from '../../shared/shared.module'; import { Collection } from '../../core/shared/collection.model'; +import { RemoteData } from '../../core/data/remote-data'; +import { Observable } from 'rxjs/Observable'; +import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { EventEmitter } from '@angular/core'; +import { HostWindowService } from '../../shared/host-window.service'; +import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub'; +import { By } from '@angular/platform-browser'; +import { RestResponse } from '../../core/cache/response-cache.models'; +import { PaginatedList } from '../../core/data/paginated-list'; +import { PageInfo } from '../../core/shared/page-info.model'; fdescribe('CollectionItemMapperComponent', () => { let comp: CollectionItemMapperComponent; @@ -32,21 +44,39 @@ fdescribe('CollectionItemMapperComponent', () => { let notificationsService: NotificationsService; let itemDataService: ItemDataService; + const mockCollection: Collection = Object.assign(new Collection(), { + id: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4', + name: 'test-collection' + }); + const mockCollectionRD: RemoteData = new RemoteData(false, false, true, null, mockCollection); + const mockSearchOptions = Observable.of(new PaginatedSearchOptions({ + pagination: Object.assign(new PaginationComponentOptions(), { + id: 'search-page-configuration', + pageSize: 10, + currentPage: 1 + }), + sort: new SortOptions('dc.title', SortDirection.ASC), + scope: mockCollection.id + })); + const routerStub = Object.assign(new RouterStub(), { + url: 'http://test.url' + }); const searchConfigServiceStub = { - + paginatedSearchOptions: mockSearchOptions }; const itemDataServiceStub = { - + mapToCollection: () => Observable.of(new RestResponse(true, '200')) }; - const mockCollection: Collection = Object.assign(new Collection(), { - metadata: [ - { - key: 'dc.title', - language: 'en_US', - value: 'Test title' - }] + const activatedRouteStub = new ActivatedRouteStub({}, { collection: mockCollectionRD }); + const translateServiceStub = { + get: () => Observable.of('test-message of collection ' + mockCollection.name), + onLangChange: new EventEmitter(), + onTranslationChange: new EventEmitter(), + onDefaultLangChange: new EventEmitter() + }; + const searchServiceStub = Object.assign(new SearchServiceStub(), { + search: () => Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))) }); - const activatedRouteStub = new ActivatedRouteStub({}, { collection: mockCollection }); beforeEach(async(() => { TestBed.configureTestingModule({ @@ -54,12 +84,13 @@ fdescribe('CollectionItemMapperComponent', () => { declarations: [CollectionItemMapperComponent], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, - { provide: Router, useValue: new RouterStub() }, + { provide: Router, useValue: routerStub }, { provide: SearchConfigurationService, useValue: searchConfigServiceStub }, - { provide: SearchService, useValue: new SearchServiceStub() }, + { provide: SearchService, useValue: searchServiceStub }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: ItemDataService, useValue: itemDataServiceStub }, - { provide: TranslateService, useValue: {} } + { provide: TranslateService, useValue: translateServiceStub }, + { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } ] }).compileComponents(); })); @@ -76,8 +107,31 @@ fdescribe('CollectionItemMapperComponent', () => { itemDataService = (comp as any).itemDataService; }); - it('should test', () => { + it('should display the correct collection name', () => { + const name: HTMLElement = fixture.debugElement.query(By.css('#collection-name')).nativeElement; + expect(name.innerHTML).toContain(mockCollection.name); + }); + describe('mapItems', () => { + const ids = ['id1', 'id2', 'id3', 'id4']; + + beforeEach(() => { + spyOn(notificationsService, 'success').and.callThrough(); + spyOn(notificationsService, 'error').and.callThrough(); + }); + + it('should display a success message if at least one mapping was successful', () => { + comp.mapItems(ids); + expect(notificationsService.success).toHaveBeenCalled(); + expect(notificationsService.error).not.toHaveBeenCalled(); + }); + + it('should display an error message if at least one mapping was unsuccessful', () => { + spyOn(itemDataService, 'mapToCollection').and.returnValue(Observable.of(new RestResponse(false, '404'))); + comp.mapItems(ids); + expect(notificationsService.success).not.toHaveBeenCalled(); + expect(notificationsService.error).toHaveBeenCalled(); + }); }); }); 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 806e535e5f..9367dbd5b5 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 @@ -115,16 +115,14 @@ export class CollectionItemMapperComponent implements OnInit { 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 - if (this.router.url.indexOf('?') > -1) { - const url: string = this.router.url.substring(0, this.router.url.indexOf('?')); - this.router.navigateByUrl(url); - } + this.router.navigateByUrl(this.getCurrentUrl()); } getCurrentUrl(): string { - const urlTree = this.router.parseUrl(this.router.url); - const g: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; - return '/' + g.toString(); + if (this.router.url.indexOf('?') > -1) { + return this.router.url.substring(0, this.router.url.indexOf('?')); + } + return this.router.url; } } From c3add84d860f0761ba334a25cf8303da58c6a365 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 28 Sep 2018 17:31:05 +0200 Subject: [PATCH 014/142] 55693: ItemSelectComponent tests intermediate + ItemSelectServiceStub --- .../collection-item-mapper.component.spec.ts | 2 +- .../item-select/item-select.component.spec.ts | 102 ++++++++++++++++++ .../testing/item-select-service-stub.ts | 37 +++++++ 3 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/app/shared/testing/item-select-service-stub.ts diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index a08322dc1b..d7ae41f023 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -33,7 +33,7 @@ import { RestResponse } from '../../core/cache/response-cache.models'; import { PaginatedList } from '../../core/data/paginated-list'; import { PageInfo } from '../../core/shared/page-info.model'; -fdescribe('CollectionItemMapperComponent', () => { +describe('CollectionItemMapperComponent', () => { let comp: CollectionItemMapperComponent; let fixture: ComponentFixture; diff --git a/src/app/shared/item-select/item-select.component.spec.ts b/src/app/shared/item-select/item-select.component.spec.ts index e69de29bb2..97132672e2 100644 --- a/src/app/shared/item-select/item-select.component.spec.ts +++ b/src/app/shared/item-select/item-select.component.spec.ts @@ -0,0 +1,102 @@ +import { ItemSelectComponent } from './item-select.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SharedModule } from '../shared.module'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { ItemSelectService } from './item-select.service'; +import { ItemSelectServiceStub } from '../testing/item-select-service-stub'; +import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../../core/data/remote-data'; +import { PaginatedList } from '../../core/data/paginated-list'; +import { PageInfo } from '../../core/shared/page-info.model'; +import { Item } from '../../core/shared/item.model'; +import { By } from '@angular/platform-browser'; +import { ActivatedRoute, Route, Router } from '@angular/router'; +import { ActivatedRouteStub } from '../testing/active-router-stub'; +import { RouterStub } from '../testing/router-stub'; +import { HostWindowService } from '../host-window.service'; +import { HostWindowServiceStub } from '../testing/host-window-service-stub'; +import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; +import { LocationStrategy } from '@angular/common'; +import { MockLocationStrategy } from '@angular/common/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +fdescribe('ItemSelectComponent', () => { + let comp: ItemSelectComponent; + let fixture: ComponentFixture; + let itemSelectService: ItemSelectService; + + const mockItemList = [ + Object.assign(new Item(), { + id: 'id1', + bitstreams: Observable.of({}), + metadata: [ + { + key: 'dc.title', + language: 'en_US', + value: 'This is just a title' + }, + { + key: 'dc.type', + language: null, + value: 'Article' + }] + }), + Object.assign(new Item(), { + id: 'id2', + bitstreams: Observable.of({}), + metadata: [ + { + key: 'dc.title', + language: 'en_US', + value: 'This is just another title' + }, + { + key: 'dc.type', + language: null, + value: 'Article' + }] + }) + ]; + const mockCheckedItems= [mockItemList[0].id]; + const mockItems = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), mockItemList))); + const mockPaginationOptions = Object.assign(new PaginationComponentOptions(), { + id: 'search-page-configuration', + pageSize: 10, + currentPage: 1 + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], + declarations: [], + providers: [ + { provide: ItemSelectService, useValue: new ItemSelectServiceStub(mockCheckedItems) }, + { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemSelectComponent); + comp = fixture.componentInstance; + comp.itemsRD$ = mockItems; + comp.paginationOptions = mockPaginationOptions; + fixture.detectChanges(); + itemSelectService = (comp as any).itemSelectService; + }); + + it(`should show a list of ${mockItemList.length} items`, () => { + const tbody: HTMLElement = fixture.debugElement.query(By.css('table#item-select tbody')).nativeElement; + expect(tbody.children.length).toBe(mockItemList.length); + }); + + it('should have the correct items selected', () => { + for (const item of mockItemList) { + const checked = (mockCheckedItems.indexOf(item.id) > -1); + const checkbox: HTMLElement = fixture.debugElement.query(By.css(`input[name=${item.id}]`)).nativeElement; + expect(checkbox.getAttribute('checked')).toBe(checked); + } + }); +}); diff --git a/src/app/shared/testing/item-select-service-stub.ts b/src/app/shared/testing/item-select-service-stub.ts new file mode 100644 index 0000000000..690d1e1435 --- /dev/null +++ b/src/app/shared/testing/item-select-service-stub.ts @@ -0,0 +1,37 @@ +import { Observable } from 'rxjs/Observable'; + +export class ItemSelectServiceStub { + + ids: string[] = []; + + constructor(ids?: string[]) { + if (ids) { + this.ids = ids; + } + } + + getSelected(id: string): Observable { + if (this.ids.indexOf(id) > -1) { + return Observable.of(true); + } else { + return Observable.of(false); + } + } + + getAllSelected(): Observable { + return Observable.of(this.ids); + } + + switch(id: string) { + const index = this.ids.indexOf(id); + if (index > -1) { + this.ids.splice(index, 1); + } else { + this.ids.push(id); + } + } + + reset() { + this.ids = []; + } +} From 8b99ea44b2f1edb66305e7af6b533bade4064aaf Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 1 Oct 2018 10:30:05 +0200 Subject: [PATCH 015/142] ItemSelectComponent tests --- .../item-select/item-select.component.html | 4 +- .../item-select/item-select.component.spec.ts | 47 +++++++++++++++---- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/item-select/item-select.component.html index 76fc118282..9c08cfae87 100644 --- a/src/app/shared/item-select/item-select.component.html +++ b/src/app/shared/item-select/item-select.component.html @@ -17,7 +17,7 @@ - + {{(item.owningCollection | async)?.payload?.name}} {{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}} {{item.findMetadata("dc.title")}} @@ -26,4 +26,4 @@
- + diff --git a/src/app/shared/item-select/item-select.component.spec.ts b/src/app/shared/item-select/item-select.component.spec.ts index 97132672e2..4ca4317eba 100644 --- a/src/app/shared/item-select/item-select.component.spec.ts +++ b/src/app/shared/item-select/item-select.component.spec.ts @@ -1,5 +1,5 @@ import { ItemSelectComponent } from './item-select.component'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { SharedModule } from '../shared.module'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TranslateModule } from '@ngx-translate/core'; @@ -58,7 +58,6 @@ fdescribe('ItemSelectComponent', () => { }] }) ]; - const mockCheckedItems= [mockItemList[0].id]; const mockItems = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), mockItemList))); const mockPaginationOptions = Object.assign(new PaginationComponentOptions(), { id: 'search-page-configuration', @@ -71,7 +70,7 @@ fdescribe('ItemSelectComponent', () => { imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], declarations: [], providers: [ - { provide: ItemSelectService, useValue: new ItemSelectServiceStub(mockCheckedItems) }, + { provide: ItemSelectService, useValue: new ItemSelectServiceStub() }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } ], schemas: [NO_ERRORS_SCHEMA] @@ -92,11 +91,41 @@ fdescribe('ItemSelectComponent', () => { expect(tbody.children.length).toBe(mockItemList.length); }); - it('should have the correct items selected', () => { - for (const item of mockItemList) { - const checked = (mockCheckedItems.indexOf(item.id) > -1); - const checkbox: HTMLElement = fixture.debugElement.query(By.css(`input[name=${item.id}]`)).nativeElement; - expect(checkbox.getAttribute('checked')).toBe(checked); - } + describe('checkboxes', () => { + let checkbox: HTMLInputElement; + + beforeEach(() => { + checkbox = fixture.debugElement.query(By.css('input.item-checkbox')).nativeElement; + }); + + it('should initially be unchecked',() => { + expect(checkbox.checked).toBeFalsy(); + }); + + it('should be checked when clicked', () => { + checkbox.click(); + fixture.detectChanges(); + expect(checkbox.checked).toBeTruthy(); + }); + + it('should switch the value through item-select-service', () => { + spyOn((comp as any).itemSelectService, 'switch').and.callThrough(); + checkbox.click(); + expect((comp as any).itemSelectService.switch).toHaveBeenCalled(); + }); + }); + + describe('when confirm is clicked', () => { + let confirmButton: HTMLButtonElement; + + beforeEach(() => { + confirmButton = fixture.debugElement.query(By.css('button.item-confirm')).nativeElement; + spyOn(comp.confirm, 'emit').and.callThrough(); + }); + + it('should emit the selected items',() => { + confirmButton.click(); + expect(comp.confirm.emit).toHaveBeenCalled(); + }); }); }); From 7934b87336ba9390f83bf7f20cd662c904c48871 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 1 Oct 2018 11:38:13 +0200 Subject: [PATCH 016/142] 55693: ItemSelectService tests --- .../item-select/item-select.component.spec.ts | 2 +- .../item-select/item-select.service.spec.ts | 96 +++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/app/shared/item-select/item-select.service.spec.ts diff --git a/src/app/shared/item-select/item-select.component.spec.ts b/src/app/shared/item-select/item-select.component.spec.ts index 4ca4317eba..0f3a9d5fae 100644 --- a/src/app/shared/item-select/item-select.component.spec.ts +++ b/src/app/shared/item-select/item-select.component.spec.ts @@ -21,7 +21,7 @@ import { LocationStrategy } from '@angular/common'; import { MockLocationStrategy } from '@angular/common/testing'; import { RouterTestingModule } from '@angular/router/testing'; -fdescribe('ItemSelectComponent', () => { +describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; let fixture: ComponentFixture; let itemSelectService: ItemSelectService; diff --git a/src/app/shared/item-select/item-select.service.spec.ts b/src/app/shared/item-select/item-select.service.spec.ts new file mode 100644 index 0000000000..f7b28a5b04 --- /dev/null +++ b/src/app/shared/item-select/item-select.service.spec.ts @@ -0,0 +1,96 @@ +import { ItemSelectService } from './item-select.service'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs/Observable'; +import { ItemSelectionsState } from './item-select.reducer'; +import { AppState } from '../../app.reducer'; +import { + ItemSelectionDeselectAction, + ItemSelectionInitialDeselectAction, + ItemSelectionInitialSelectAction, ItemSelectionResetAction, + ItemSelectionSelectAction, ItemSelectionSwitchAction +} from './item-select.actions'; + +describe('ItemSelectService', () => { + let service: ItemSelectService; + + const mockItemId = 'id1'; + + const store: Store = jasmine.createSpyObj('store', { + /* tslint:disable:no-empty */ + dispatch: {}, + /* tslint:enable:no-empty */ + select: Observable.of(true) + }); + + const appStore: Store = jasmine.createSpyObj('appStore', { + /* tslint:disable:no-empty */ + dispatch: {}, + /* tslint:enable:no-empty */ + select: Observable.of(true) + }); + + beforeEach(() => { + service = new ItemSelectService(store, appStore); + }); + + describe('when the initialSelect method is triggered', () => { + beforeEach(() => { + service.initialSelect(mockItemId); + }); + + it('ItemSelectionInitialSelectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionInitialSelectAction(mockItemId)); + }); + }); + + describe('when the initialDeselect method is triggered', () => { + beforeEach(() => { + service.initialDeselect(mockItemId); + }); + + it('ItemSelectionInitialDeselectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionInitialDeselectAction(mockItemId)); + }); + }); + + describe('when the select method is triggered', () => { + beforeEach(() => { + service.select(mockItemId); + }); + + it('ItemSelectionSelectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionSelectAction(mockItemId)); + }); + }); + + describe('when the deselect method is triggered', () => { + beforeEach(() => { + service.deselect(mockItemId); + }); + + it('ItemSelectionDeselectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionDeselectAction(mockItemId)); + }); + }); + + describe('when the switch method is triggered', () => { + beforeEach(() => { + service.switch(mockItemId); + }); + + it('ItemSelectionSwitchAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionSwitchAction(mockItemId)); + }); + }); + + describe('when the reset method is triggered', () => { + beforeEach(() => { + service.reset(); + }); + + it('ItemSelectionInitialSelectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionResetAction(null)); + }); + }); + +}); From 199e6c729943b74554ff1cf749b259d991a16f86 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 1 Oct 2018 11:43:37 +0200 Subject: [PATCH 017/142] 55693: Empty ItemSelectReducer test file --- .../shared/item-select/item-select.reducer.spec.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/app/shared/item-select/item-select.reducer.spec.ts diff --git a/src/app/shared/item-select/item-select.reducer.spec.ts b/src/app/shared/item-select/item-select.reducer.spec.ts new file mode 100644 index 0000000000..3067a4e93a --- /dev/null +++ b/src/app/shared/item-select/item-select.reducer.spec.ts @@ -0,0 +1,13 @@ +import { ItemSelectionSelectAction } from './item-select.actions'; + +class NullAction extends ItemSelectionSelectAction { + type = null; + + constructor() { + super(undefined); + } +} + +fdescribe('itemSelectionReducer', () => { + +}); From 5f56d5959da5b86db7a32c7117ce8a8f2edb1d5e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 1 Oct 2018 13:40:29 +0200 Subject: [PATCH 018/142] 55693: ItemSelectReducer tests --- .../item-select/item-select.reducer.spec.ts | 89 ++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/src/app/shared/item-select/item-select.reducer.spec.ts b/src/app/shared/item-select/item-select.reducer.spec.ts index 3067a4e93a..10c5db9b7f 100644 --- a/src/app/shared/item-select/item-select.reducer.spec.ts +++ b/src/app/shared/item-select/item-select.reducer.spec.ts @@ -1,4 +1,12 @@ -import { ItemSelectionSelectAction } from './item-select.actions'; +import { + ItemSelectionDeselectAction, ItemSelectionInitialDeselectAction, + ItemSelectionInitialSelectAction, ItemSelectionResetAction, + ItemSelectionSelectAction, ItemSelectionSwitchAction +} from './item-select.actions'; +import { itemSelectionReducer } from './item-select.reducer'; + +const itemId1 = 'id1'; +const itemId2 = 'id2'; class NullAction extends ItemSelectionSelectAction { type = null; @@ -8,6 +16,83 @@ class NullAction extends ItemSelectionSelectAction { } } -fdescribe('itemSelectionReducer', () => { +describe('itemSelectionReducer', () => { + + it('should return the current state when no valid actions have been made', () => { + const state = {}; + state[itemId1] = { checked: true }; + const action = new NullAction(); + const newState = itemSelectionReducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should start with an empty object', () => { + const state = {}; + const action = new NullAction(); + const newState = itemSelectionReducer(undefined, action); + + expect(newState).toEqual(state); + }); + + it('should set checked to true in response to the INITIAL_SELECT action', () => { + const action = new ItemSelectionInitialSelectAction(itemId1); + const newState = itemSelectionReducer(undefined, action); + + expect(newState[itemId1].checked).toBeTruthy(); + }); + + it('should set checked to true in response to the INITIAL_DESELECT action', () => { + const action = new ItemSelectionInitialDeselectAction(itemId1); + const newState = itemSelectionReducer(undefined, action); + + expect(newState[itemId1].checked).toBeFalsy(); + }); + + it('should set checked to true in response to the SELECT action', () => { + const state = {}; + state[itemId1] = { checked: false }; + const action = new ItemSelectionSelectAction(itemId1); + const newState = itemSelectionReducer(state, action); + + expect(newState[itemId1].checked).toBeTruthy(); + }); + + it('should set checked to false in response to the DESELECT action', () => { + const state = {}; + state[itemId1] = { checked: true }; + const action = new ItemSelectionDeselectAction(itemId1); + const newState = itemSelectionReducer(state, action); + + expect(newState[itemId1].checked).toBeFalsy(); + }); + + it('should set checked from false to true in response to the SWITCH action', () => { + const state = {}; + state[itemId1] = { checked: false }; + const action = new ItemSelectionSwitchAction(itemId1); + const newState = itemSelectionReducer(state, action); + + expect(newState[itemId1].checked).toBeTruthy(); + }); + + it('should set checked from true to false in response to the SWITCH action', () => { + const state = {}; + state[itemId1] = { checked: true }; + const action = new ItemSelectionSwitchAction(itemId1); + const newState = itemSelectionReducer(state, action); + + expect(newState[itemId1].checked).toBeFalsy(); + }); + + it('should set reset the state in response to the RESET action', () => { + const state = {}; + state[itemId1] = { checked: true }; + state[itemId2] = { checked: false }; + const action = new ItemSelectionResetAction(undefined); + const newState = itemSelectionReducer(state, action); + + expect(newState).toEqual({}); + }); }); From 24f6f982e90bd43fa0434674edcafaa7834c93b0 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 1 Oct 2018 14:14:26 +0200 Subject: [PATCH 019/142] 55693: Authentication Guard and TSDocs --- .../collection-item-mapper.component.ts | 41 +++++++++++++++++++ .../collection-page-routing.module.ts | 4 +- .../item-select/item-select.component.ts | 33 +++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) 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 9367dbd5b5..15f7db63b4 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 @@ -28,13 +28,37 @@ import { TranslateService } from '@ngx-translate/core'; fadeInOut ] }) +/** + * Collection used to map items to a collection + */ export class CollectionItemMapperComponent implements OnInit { + /** + * The collection to map items to + */ collectionRD$: Observable>; + + /** + * Search options + */ searchOptions$: Observable; + + /** + * List of items to show under the "Browse" tab + * Items inside the collection + */ collectionItemsRD$: Observable>>; + + /** + * List of items to show under the "Map" tab + * Items outside the collection + */ mappingItemsRD$: Observable>>; + /** + * Sort on title ASC by default + * @type {SortOptions} + */ defaultSortOptions: SortOptions = new SortOptions('dc.title', SortDirection.ASC); constructor(private route: ActivatedRoute, @@ -52,6 +76,11 @@ export class CollectionItemMapperComponent implements OnInit { this.loadItemLists(); } + /** + * Load collectionItemsRD$ with a fixed scope to only obtain the items this collection owns + * Load mappingItemsRD$ to only obtain items this collection doesn't own + * TODO: When the API support it, fetch items excluding the collection's scope (currently fetches all items) + */ loadItemLists() { const collectionAndOptions$ = Observable.combineLatest( this.collectionRD$, @@ -79,6 +108,10 @@ export class CollectionItemMapperComponent implements OnInit { ); } + /** + * Map the selected items to the collection and display notifications + * @param {string[]} ids The list of item UUID's to map to the collection + */ mapItems(ids: string[]) { const responses$ = this.collectionRD$.pipe( getSucceededRemoteData(), @@ -112,12 +145,20 @@ export class CollectionItemMapperComponent implements OnInit { }); } + /** + * 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('?')); diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index c85d102437..f04d40e234 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -4,6 +4,7 @@ import { RouterModule } from '@angular/router'; import { CollectionPageComponent } from './collection-page.component'; import { CollectionPageResolver } from './collection-page.resolver'; import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component'; +import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; @NgModule({ imports: [ @@ -22,7 +23,8 @@ import { CollectionItemMapperComponent } from './collection-item-mapper/collecti pathMatch: 'full', resolve: { collection: CollectionPageResolver - } + }, + canActivate: [AuthenticatedGuard] } ]) ], diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/item-select/item-select.component.ts index 00ca078bfa..3a2003a327 100644 --- a/src/app/shared/item-select/item-select.component.ts +++ b/src/app/shared/item-select/item-select.component.ts @@ -14,20 +14,40 @@ import { take } from 'rxjs/operators'; templateUrl: './item-select.component.html' }) +/** + * A component used to select items from a specific list and returning the UUIDs of the selected items + */ export class ItemSelectComponent implements OnInit { + /** + * The list of items to display + */ @Input() itemsRD$: Observable>>; + /** + * The pagination options used to display the items + */ @Input() paginationOptions: PaginationComponentOptions; + /** + * The message key used for the confirm button + * @type {string} + */ @Input() confirmButton = 'item.select.confirm'; + /** + * EventEmitter to return the selected UUIDs when the confirm button is pressed + * @type {EventEmitter} + */ @Output() confirm: EventEmitter = new EventEmitter(); + /** + * The list of selected UUIDs + */ selectedIds$: Observable; constructor(private itemSelectService: ItemSelectService) { @@ -37,14 +57,27 @@ export class ItemSelectComponent implements OnInit { this.selectedIds$ = this.itemSelectService.getAllSelected(); } + /** + * Switch the state of a checkbox + * @param {string} id + */ switch(id: string) { this.itemSelectService.switch(id); } + /** + * Get the current state of a checkbox + * @param {string} id The item's UUID + * @returns {Observable} + */ getSelected(id: string): Observable { return this.itemSelectService.getSelected(id); } + /** + * Called when the confirm button is pressed + * Sends the selected UUIDs to the parent component + */ confirmSelected() { this.selectedIds$.pipe( take(1) From ba4d2861f5d95fd827c0e0c6de5731314a0074ee Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 8 Oct 2018 17:50:52 +0200 Subject: [PATCH 020/142] 55946: Start of Edit Item Page --- resources/i18n/en.json | 53 ++++++++++++++++++ .../edit-item-page.component.html | 52 ++++++++++++++++++ .../edit-item-page.component.scss | 0 .../edit-item-page.component.spec.ts | 0 .../edit-item-page.component.ts | 55 +++++++++++++++++++ .../+item-page/item-page-routing.module.ts | 10 ++++ src/app/+item-page/item-page.module.ts | 4 +- 7 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/app/+item-page/edit-item-page/edit-item-page.component.html create mode 100644 src/app/+item-page/edit-item-page/edit-item-page.component.scss create mode 100644 src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts create mode 100644 src/app/+item-page/edit-item-page/edit-item-page.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 86ff55da66..7c45b612f7 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -72,6 +72,59 @@ "title": "Title" }, "confirm": "Confirm selected" + }, + "edit": { + "head": "Edit Item", + "tabs": { + "status": { + "head": "Item Status", + "description": "Welcome to the item management page. From here you can withdraw, reinstate, move or delete the item. You may also update or add new metadata / bitstreams on the other tabs.", + "labels": { + "id": "Item Internal ID", + "handle": "Handle", + "lastModified": "Last Modified", + "itemPage": "Item Page" + }, + "buttons": { + "authorizations": { + "label": "Edit item's authorization policies", + "button": "Authorizations..." + }, + "withdraw": { + "label": "Withdraw item from the repository", + "button": "Withdraw..." + }, + "move": { + "label": "Move item to another collection", + "button": "Move..." + }, + "private": { + "label": "Make item private", + "button": "Make it private..." + }, + "delete": { + "label": "Completely expunge item", + "button": "Permanently delete" + }, + "mapped-collections": { + "label": "Manage mapped collections", + "button": "Mapped collections" + } + } + }, + "bitstreams": { + "head": "Item Bitstreams" + }, + "metadata": { + "head": "Item Metadata" + }, + "view": { + "head": "View Item" + }, + "curate": { + "head": "Curate" + } + } } }, "nav": { diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.html b/src/app/+item-page/edit-item-page/edit-item-page.component.html new file mode 100644 index 0000000000..ca15e83ab9 --- /dev/null +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.html @@ -0,0 +1,52 @@ +
+
+
+

{{'item.edit.head' | translate}}

+
+ + + +

{{'item.edit.tabs.status.description' | translate}}

+
+
+
+ {{'item.edit.tabs.status.labels.' + statusKey | translate}}: +
+
+ {{(statusData$ | async)[statusKey]}} +
+
+
+ {{'item.edit.tabs.status.labels.itemPage' | translate}}: +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+
diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.scss b/src/app/+item-page/edit-item-page/edit-item-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts b/src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.ts b/src/app/+item-page/edit-item-page/edit-item-page.component.ts new file mode 100644 index 0000000000..eb017669fc --- /dev/null +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.ts @@ -0,0 +1,55 @@ +import { fadeIn, fadeInOut } from '../../shared/animations/fade'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../../core/data/remote-data'; +import { Item } from '../../core/shared/item.model'; +import { getSucceededRemoteData } from '../../core/shared/operators'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'ds-edit-item-page', + styleUrls: ['./edit-item-page.component.scss'], + templateUrl: './edit-item-page.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeIn, + fadeInOut + ] +}) +/** + * Page component for editing an item + */ +export class EditItemPageComponent implements OnInit { + + objectKeys = Object.keys; + + /** + * The item to edit + */ + itemRD$: Observable>; + statusData$: Observable; + + constructor(private route: ActivatedRoute, + private router: Router) { + } + + ngOnInit(): void { + this.itemRD$ = this.route.data.map((data) => data.item); + this.statusData$ = this.itemRD$.pipe( + getSucceededRemoteData(), + map((itemRD: RemoteData) => itemRD.payload), + map((item: Item) => Object.assign({ + id: item.id, + handle: item.handle, + lastModified: item.lastModified + })) + ) + } + + getItemPage(): string { + return this.router.url.substr(0, this.router.url.lastIndexOf('/')); + } + +} diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index 96158b867e..0105c947e9 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -4,6 +4,8 @@ import { RouterModule } from '@angular/router'; import { ItemPageComponent } from './simple/item-page.component'; import { FullItemPageComponent } from './full/full-item-page.component'; import { ItemPageResolver } from './item-page.resolver'; +import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; +import { EditItemPageComponent } from './edit-item-page/edit-item-page.component'; @NgModule({ imports: [ @@ -22,6 +24,14 @@ import { ItemPageResolver } from './item-page.resolver'; resolve: { item: ItemPageResolver } + }, + { + path: ':id/edit', + component: EditItemPageComponent, + resolve: { + item: ItemPageResolver + }, + canActivate: [AuthenticatedGuard] } ]) ], diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts index bd801923e3..45251bb1d8 100644 --- a/src/app/+item-page/item-page.module.ts +++ b/src/app/+item-page/item-page.module.ts @@ -18,6 +18,7 @@ import { FileSectionComponent } from './simple/field-components/file-section/fil import { CollectionsComponent } from './field-components/collections/collections.component'; import { FullItemPageComponent } from './full/full-item-page.component'; import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component'; +import { EditItemPageComponent } from './edit-item-page/edit-item-page.component'; @NgModule({ imports: [ @@ -39,7 +40,8 @@ import { FullFileSectionComponent } from './full/field-components/file-section/f ItemPageSpecificFieldComponent, FileSectionComponent, CollectionsComponent, - FullFileSectionComponent + FullFileSectionComponent, + EditItemPageComponent ] }) export class ItemPageModule { From 3a809cc6710eb9e54d1bc7c49a2d41c321013c87 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 9 Oct 2018 11:51:52 +0200 Subject: [PATCH 021/142] 55946: Finished edit-item-page and start of collection mapper --- resources/i18n/en.json | 2 +- .../edit-item-page.component.html | 18 +--- .../edit-item-page.component.ts | 24 +----- .../edit-item-page/edit-item-page.module.ts | 23 ++++++ .../edit-item-page.routing.module.ts | 32 ++++++++ .../item-collection-mapper.component.html | 7 ++ .../item-collection-mapper.component.scss | 0 .../item-collection-mapper.component.spec.ts | 0 .../item-collection-mapper.component.ts | 19 +++++ .../item-status/item-status.component.html | 30 +++++++ .../item-status/item-status.component.scss | 0 .../item-status/item-status.component.spec.ts | 0 .../item-status/item-status.component.ts | 82 +++++++++++++++++++ .../+item-page/item-page-routing.module.ts | 6 +- src/app/+item-page/item-page.module.ts | 6 +- 15 files changed, 201 insertions(+), 48 deletions(-) create mode 100644 src/app/+item-page/edit-item-page/edit-item-page.module.ts create mode 100644 src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts create mode 100644 src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html create mode 100644 src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.scss create mode 100644 src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts create mode 100644 src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts create mode 100644 src/app/+item-page/edit-item-page/item-status/item-status.component.html create mode 100644 src/app/+item-page/edit-item-page/item-status/item-status.component.scss create mode 100644 src/app/+item-page/edit-item-page/item-status/item-status.component.spec.ts create mode 100644 src/app/+item-page/edit-item-page/item-status/item-status.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 7c45b612f7..69c4a4174b 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -106,7 +106,7 @@ "label": "Completely expunge item", "button": "Permanently delete" }, - "mapped-collections": { + "mappedCollections": { "label": "Manage mapped collections", "button": "Mapped collections" } diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.html b/src/app/+item-page/edit-item-page/edit-item-page.component.html index ca15e83ab9..001b484c2c 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.component.html +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.html @@ -6,23 +6,7 @@ -

{{'item.edit.tabs.status.description' | translate}}

-
-
-
- {{'item.edit.tabs.status.labels.' + statusKey | translate}}: -
-
- {{(statusData$ | async)[statusKey]}} -
-
-
- {{'item.edit.tabs.status.labels.itemPage' | translate}}: -
- -
+
diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.ts b/src/app/+item-page/edit-item-page/edit-item-page.component.ts index eb017669fc..7702fc94e8 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.component.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.ts @@ -1,12 +1,9 @@ import { fadeIn, fadeInOut } from '../../shared/animations/fade'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { ItemDataService } from '../../core/data/item-data.service'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; -import { getSucceededRemoteData } from '../../core/shared/operators'; -import { map } from 'rxjs/operators'; @Component({ selector: 'ds-edit-item-page', @@ -23,33 +20,16 @@ import { map } from 'rxjs/operators'; */ export class EditItemPageComponent implements OnInit { - objectKeys = Object.keys; - /** * The item to edit */ itemRD$: Observable>; - statusData$: Observable; - constructor(private route: ActivatedRoute, - private router: Router) { + constructor(private route: ActivatedRoute) { } ngOnInit(): void { this.itemRD$ = this.route.data.map((data) => data.item); - this.statusData$ = this.itemRD$.pipe( - getSucceededRemoteData(), - map((itemRD: RemoteData) => itemRD.payload), - map((item: Item) => Object.assign({ - id: item.id, - handle: item.handle, - lastModified: item.lastModified - })) - ) - } - - getItemPage(): string { - return this.router.url.substr(0, this.router.url.lastIndexOf('/')); } } 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 new file mode 100644 index 0000000000..70f6fc7d3b --- /dev/null +++ b/src/app/+item-page/edit-item-page/edit-item-page.module.ts @@ -0,0 +1,23 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '../../shared/shared.module'; +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'; + +@NgModule({ + imports: [ + CommonModule, + SharedModule, + EditItemPageRoutingModule + ], + declarations: [ + EditItemPageComponent, + ItemStatusComponent, + ItemCollectionMapperComponent + ] +}) +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 new file mode 100644 index 0000000000..f2209cddcc --- /dev/null +++ b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts @@ -0,0 +1,32 @@ +import { ItemPageResolver } from '../item-page.resolver'; +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { EditItemPageComponent } from './edit-item-page.component'; +import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + component: EditItemPageComponent, + resolve: { + item: ItemPageResolver + } + }, + { + path: 'map', + component: ItemCollectionMapperComponent, + resolve: { + item: ItemPageResolver + } + } + ]) + ], + providers: [ + ItemPageResolver, + ] +}) +export class EditItemPageRoutingModule { + +} 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 new file mode 100644 index 0000000000..3fb829fe8b --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html @@ -0,0 +1,7 @@ +
+
+
+

It works!

+
+
+
diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.scss b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..592e3bd26c --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts @@ -0,0 +1,19 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; + +@Component({ + selector: 'ds-item-collection-mapper', + styleUrls: ['./item-collection-mapper.component.scss'], + templateUrl: './item-collection-mapper.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeIn, + fadeInOut + ] +}) +/** + * Component for mapping collections to an item + */ +export class ItemCollectionMapperComponent { + +} diff --git a/src/app/+item-page/edit-item-page/item-status/item-status.component.html b/src/app/+item-page/edit-item-page/item-status/item-status.component.html new file mode 100644 index 0000000000..0a93e7659d --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-status/item-status.component.html @@ -0,0 +1,30 @@ +

{{'item.edit.tabs.status.description' | translate}}

+
+
+
+ {{'item.edit.tabs.status.labels.' + statusKey | translate}}: +
+
+ {{statusData[statusKey]}} +
+
+
+ {{'item.edit.tabs.status.labels.itemPage' | translate}}: +
+ + +
+
+ + {{'item.edit.tabs.status.buttons.' + actionKey + '.label' | translate}} + +
+ +
+
diff --git a/src/app/+item-page/edit-item-page/item-status/item-status.component.scss b/src/app/+item-page/edit-item-page/item-status/item-status.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+item-page/edit-item-page/item-status/item-status.component.spec.ts b/src/app/+item-page/edit-item-page/item-status/item-status.component.spec.ts new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..715614c1d9 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-status/item-status.component.ts @@ -0,0 +1,82 @@ +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; +import { Item } from '../../../core/shared/item.model'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'ds-item-status', + styleUrls: ['./item-status.component.scss'], + templateUrl: './item-status.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + fadeIn, + fadeInOut + ] +}) +/** + * Component for displaying an item's status + */ +export class ItemStatusComponent implements OnInit { + + /** + * The item to display the status for + */ + @Input() item: Item; + + /** + * The data to show in the status + */ + statusData: any; + /** + * The keys of the data (to loop over) + */ + statusDataKeys; + + /** + * The possible actions that can be performed on the item + * key: id value: url to action's component + */ + actions: any; + /** + * The keys of the actions (to loop over) + */ + actionsKeys; + + constructor(private router: Router) { + } + + ngOnInit(): void { + this.statusData = Object.assign({ + id: this.item.id, + handle: this.item.handle, + lastModified: this.item.lastModified + }); + this.statusDataKeys = Object.keys(this.statusData); + + this.actions = Object.assign({ + mappedCollections: this.getCurrentUrl() + '/map' + }); + this.actionsKeys = Object.keys(this.actions); + } + + /** + * Get the url to the simple item page + * @returns {string} url + */ + getItemPage(): string { + return this.router.url.substr(0, this.router.url.lastIndexOf('/')); + } + + /** + * Get the current url without query params + * @returns {string} url + */ + getCurrentUrl(): string { + if (this.router.url.indexOf('?') > -1) { + return this.router.url.substr(0, this.router.url.indexOf('?')); + } else { + return this.router.url; + } + } + +} diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index 0105c947e9..be31b0a82d 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -5,7 +5,6 @@ import { ItemPageComponent } from './simple/item-page.component'; import { FullItemPageComponent } from './full/full-item-page.component'; import { ItemPageResolver } from './item-page.resolver'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; -import { EditItemPageComponent } from './edit-item-page/edit-item-page.component'; @NgModule({ imports: [ @@ -27,10 +26,7 @@ import { EditItemPageComponent } from './edit-item-page/edit-item-page.component }, { path: ':id/edit', - component: EditItemPageComponent, - resolve: { - item: ItemPageResolver - }, + loadChildren: './edit-item-page/edit-item-page.module#EditItemPageModule', canActivate: [AuthenticatedGuard] } ]) diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts index 45251bb1d8..d383189a9c 100644 --- a/src/app/+item-page/item-page.module.ts +++ b/src/app/+item-page/item-page.module.ts @@ -18,12 +18,13 @@ import { FileSectionComponent } from './simple/field-components/file-section/fil import { CollectionsComponent } from './field-components/collections/collections.component'; import { FullItemPageComponent } from './full/full-item-page.component'; import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component'; -import { EditItemPageComponent } from './edit-item-page/edit-item-page.component'; +import { EditItemPageModule } from './edit-item-page/edit-item-page.module'; @NgModule({ imports: [ CommonModule, SharedModule, + EditItemPageModule, ItemPageRoutingModule ], declarations: [ @@ -40,8 +41,7 @@ import { EditItemPageComponent } from './edit-item-page/edit-item-page.component ItemPageSpecificFieldComponent, FileSectionComponent, CollectionsComponent, - FullFileSectionComponent, - EditItemPageComponent + FullFileSectionComponent ] }) export class ItemPageModule { From 6bc4d061f10bd49bb6c1a5ef3dbe914abc30e858 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 9 Oct 2018 14:09:56 +0200 Subject: [PATCH 022/142] 55946: Intermediate commit --- resources/i18n/en.json | 21 +++++ .../edit-item-page/edit-item-page.module.ts | 2 + .../edit-item-page.routing.module.ts | 2 +- .../item-collection-mapper.component.html | 33 ++++++- .../item-collection-mapper.component.ts | 94 ++++++++++++++++++- .../item-status/item-status.component.ts | 2 +- src/app/core/core.module.ts | 2 + src/app/core/data/item-data.service.ts | 30 +++++- ...ing-collections-reponse-parsing.service.ts | 24 +++++ src/app/core/data/request.models.ts | 7 ++ 10 files changed, 208 insertions(+), 9 deletions(-) create mode 100644 src/app/core/data/mapping-collections-reponse-parsing.service.ts 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}}

+ +
+
+ + +
+
+ + + + +
+
{{col.name}}
+
+
+
+ + +
+ +
+
+
+
+ +
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); From 7021527f5c579cc1f07d7cf6e00651d0b93faad5 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 9 Oct 2018 16:33:33 +0200 Subject: [PATCH 023/142] 55946: Fixed api calls for fetching collections with/without item --- .../item-collection-mapper.component.ts | 4 +++- .../item-status/item-status.component.ts | 1 - src/app/core/data/item-data.service.ts | 13 +++++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) 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 eeb602292d..1ecbd7d1f0 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 @@ -82,7 +82,9 @@ export class ItemCollectionMapperComponent implements OnInit { map((itemRD: RemoteData) => itemRD.payload), switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id)) ); - this.mappingCollectionsRD$ = this.collectionDataService.findAll(); + this.mappingCollectionsRD$ = this.searchOptions$.pipe( + switchMap((searchOptions: PaginatedSearchOptions) => this.collectionDataService.findAll(searchOptions)) + ); } /** 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 a7f7e7915a..06dd838ce2 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 @@ -58,7 +58,6 @@ export class ItemStatusComponent implements OnInit { The value is supposed to be a href for the button */ this.actions = Object.assign({ - // TODO: Create mapping component on item level mappedCollections: this.getCurrentUrl() + '/mapper' }); this.actionsKeys = Object.keys(this.actions); diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 250d8c1303..0fe9a690e2 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -89,8 +89,17 @@ export class ItemDataService extends DataService { configureRequest(this.requestService) ); - // TODO: Create a remotedata object - return undefined; + const href$ = request$.pipe(map((request: RestRequest) => request.href)); + 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) => response.payload), + ensureArrayHasValue() + ); + + return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); } } From c4203f25d517d27fd758c6444a582de7a3b11735 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 9 Oct 2018 17:16:43 +0200 Subject: [PATCH 024/142] 55946: Refactored item-select to object-select to allow for easier implementation of collection-select --- src/app/app.reducer.ts | 6 +- src/app/core/core.module.ts | 4 +- .../shared/item-select/item-select.actions.ts | 75 ----------- .../item-select/item-select.reducer.spec.ts | 98 --------------- .../item-select/item-select.service.spec.ts | 96 -------------- .../shared/item-select/item-select.service.ts | 119 ------------------ .../item-select/item-select.component.html | 0 .../item-select/item-select.component.scss | 0 .../item-select/item-select.component.spec.ts | 37 +++--- .../item-select/item-select.component.ts | 23 ++-- .../object-select/object-select.actions.ts | 75 +++++++++++ .../object-select.reducer.spec.ts | 98 +++++++++++++++ .../object-select.reducer.ts} | 24 ++-- .../object-select.service.spec.ts | 96 ++++++++++++++ .../object-select/object-select.service.ts | 119 ++++++++++++++++++ src/app/shared/shared.module.ts | 2 +- ...-stub.ts => object-select-service-stub.ts} | 2 +- 17 files changed, 434 insertions(+), 440 deletions(-) delete mode 100644 src/app/shared/item-select/item-select.actions.ts delete mode 100644 src/app/shared/item-select/item-select.reducer.spec.ts delete mode 100644 src/app/shared/item-select/item-select.service.spec.ts delete mode 100644 src/app/shared/item-select/item-select.service.ts rename src/app/shared/{ => object-select}/item-select/item-select.component.html (100%) rename src/app/shared/{ => object-select}/item-select/item-select.component.scss (100%) rename src/app/shared/{ => object-select}/item-select/item-select.component.spec.ts (76%) rename src/app/shared/{ => object-select}/item-select/item-select.component.ts (72%) create mode 100644 src/app/shared/object-select/object-select.actions.ts create mode 100644 src/app/shared/object-select/object-select.reducer.spec.ts rename src/app/shared/{item-select/item-select.reducer.ts => object-select/object-select.reducer.ts} (67%) create mode 100644 src/app/shared/object-select/object-select.service.spec.ts create mode 100644 src/app/shared/object-select/object-select.service.ts rename src/app/shared/testing/{item-select-service-stub.ts => object-select-service-stub.ts} (94%) diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index ba882b50b8..4702bbe354 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -14,7 +14,7 @@ import { } from './+search-page/search-filters/search-filter/search-filter.reducer'; import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers'; import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer'; -import { itemSelectionReducer, ItemSelectionsState } from './shared/item-select/item-select.reducer'; +import { objectSelectionReducer, ObjectSelectionsState } from './shared/object-select/object-select.reducer'; export interface AppState { router: fromRouter.RouterReducerState; @@ -25,7 +25,7 @@ export interface AppState { searchSidebar: SearchSidebarState; searchFilter: SearchFiltersState; truncatable: TruncatablesState, - itemSelection: ItemSelectionsState + objectSelection: ObjectSelectionsState } export const appReducers: ActionReducerMap = { @@ -37,7 +37,7 @@ export const appReducers: ActionReducerMap = { searchSidebar: sidebarReducer, searchFilter: filterReducer, truncatable: truncatableReducer, - itemSelection: itemSelectionReducer + objectSelection: objectSelectionReducer }; export const routerStateSelector = (state: AppState) => state.router; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 4ea3aa8df7..256d58b5b8 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -64,8 +64,8 @@ import { NotificationsService } from '../shared/notifications/notifications.serv 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'; +import { ObjectSelectService } from '../shared/object-select/object-select.service'; const IMPORTS = [ CommonModule, @@ -131,7 +131,7 @@ const PROVIDERS = [ UploaderService, UUIDService, DSpaceObjectDataService, - ItemSelectService, + ObjectSelectService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/shared/item-select/item-select.actions.ts b/src/app/shared/item-select/item-select.actions.ts deleted file mode 100644 index 0f17575a28..0000000000 --- a/src/app/shared/item-select/item-select.actions.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { type } from '../ngrx/type'; -import { Action } from '@ngrx/store'; - -export const ItemSelectionActionTypes = { - INITIAL_DESELECT: type('dspace/item-select/INITIAL_DESELECT'), - INITIAL_SELECT: type('dspace/item-select/INITIAL_SELECT'), - SELECT: type('dspace/item-select/SELECT'), - DESELECT: type('dspace/item-select/DESELECT'), - SWITCH: type('dspace/item-select/SWITCH'), - RESET: type('dspace/item-select/RESET') -}; - -export class ItemSelectionAction implements Action { - /** - * UUID of the item a select action can be performed on - */ - id: string; - - /** - * Type of action that will be performed - */ - type; - - /** - * Initialize with the item's UUID - * @param {string} id of the item - */ - constructor(id: string) { - this.id = id; - } -} - -/* tslint:disable:max-classes-per-file */ -/** - * Used to set the initial state to deselected - */ -export class ItemSelectionInitialDeselectAction extends ItemSelectionAction { - type = ItemSelectionActionTypes.INITIAL_DESELECT; -} - -/** - * Used to set the initial state to selected - */ -export class ItemSelectionInitialSelectAction extends ItemSelectionAction { - type = ItemSelectionActionTypes.INITIAL_SELECT; -} - -/** - * Used to select an item - */ -export class ItemSelectionSelectAction extends ItemSelectionAction { - type = ItemSelectionActionTypes.SELECT; -} - -/** - * Used to deselect an item - */ -export class ItemSelectionDeselectAction extends ItemSelectionAction { - type = ItemSelectionActionTypes.DESELECT; -} - -/** - * Used to switch an item between selected and deselected - */ -export class ItemSelectionSwitchAction extends ItemSelectionAction { - type = ItemSelectionActionTypes.SWITCH; -} - -/** - * Used to reset all item's selected to be deselected - */ -export class ItemSelectionResetAction extends ItemSelectionAction { - type = ItemSelectionActionTypes.RESET; -} -/* tslint:enable:max-classes-per-file */ diff --git a/src/app/shared/item-select/item-select.reducer.spec.ts b/src/app/shared/item-select/item-select.reducer.spec.ts deleted file mode 100644 index 10c5db9b7f..0000000000 --- a/src/app/shared/item-select/item-select.reducer.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { - ItemSelectionDeselectAction, ItemSelectionInitialDeselectAction, - ItemSelectionInitialSelectAction, ItemSelectionResetAction, - ItemSelectionSelectAction, ItemSelectionSwitchAction -} from './item-select.actions'; -import { itemSelectionReducer } from './item-select.reducer'; - -const itemId1 = 'id1'; -const itemId2 = 'id2'; - -class NullAction extends ItemSelectionSelectAction { - type = null; - - constructor() { - super(undefined); - } -} - -describe('itemSelectionReducer', () => { - - it('should return the current state when no valid actions have been made', () => { - const state = {}; - state[itemId1] = { checked: true }; - const action = new NullAction(); - const newState = itemSelectionReducer(state, action); - - expect(newState).toEqual(state); - }); - - it('should start with an empty object', () => { - const state = {}; - const action = new NullAction(); - const newState = itemSelectionReducer(undefined, action); - - expect(newState).toEqual(state); - }); - - it('should set checked to true in response to the INITIAL_SELECT action', () => { - const action = new ItemSelectionInitialSelectAction(itemId1); - const newState = itemSelectionReducer(undefined, action); - - expect(newState[itemId1].checked).toBeTruthy(); - }); - - it('should set checked to true in response to the INITIAL_DESELECT action', () => { - const action = new ItemSelectionInitialDeselectAction(itemId1); - const newState = itemSelectionReducer(undefined, action); - - expect(newState[itemId1].checked).toBeFalsy(); - }); - - it('should set checked to true in response to the SELECT action', () => { - const state = {}; - state[itemId1] = { checked: false }; - const action = new ItemSelectionSelectAction(itemId1); - const newState = itemSelectionReducer(state, action); - - expect(newState[itemId1].checked).toBeTruthy(); - }); - - it('should set checked to false in response to the DESELECT action', () => { - const state = {}; - state[itemId1] = { checked: true }; - const action = new ItemSelectionDeselectAction(itemId1); - const newState = itemSelectionReducer(state, action); - - expect(newState[itemId1].checked).toBeFalsy(); - }); - - it('should set checked from false to true in response to the SWITCH action', () => { - const state = {}; - state[itemId1] = { checked: false }; - const action = new ItemSelectionSwitchAction(itemId1); - const newState = itemSelectionReducer(state, action); - - expect(newState[itemId1].checked).toBeTruthy(); - }); - - it('should set checked from true to false in response to the SWITCH action', () => { - const state = {}; - state[itemId1] = { checked: true }; - const action = new ItemSelectionSwitchAction(itemId1); - const newState = itemSelectionReducer(state, action); - - expect(newState[itemId1].checked).toBeFalsy(); - }); - - it('should set reset the state in response to the RESET action', () => { - const state = {}; - state[itemId1] = { checked: true }; - state[itemId2] = { checked: false }; - const action = new ItemSelectionResetAction(undefined); - const newState = itemSelectionReducer(state, action); - - expect(newState).toEqual({}); - }); - -}); diff --git a/src/app/shared/item-select/item-select.service.spec.ts b/src/app/shared/item-select/item-select.service.spec.ts deleted file mode 100644 index f7b28a5b04..0000000000 --- a/src/app/shared/item-select/item-select.service.spec.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { ItemSelectService } from './item-select.service'; -import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs/Observable'; -import { ItemSelectionsState } from './item-select.reducer'; -import { AppState } from '../../app.reducer'; -import { - ItemSelectionDeselectAction, - ItemSelectionInitialDeselectAction, - ItemSelectionInitialSelectAction, ItemSelectionResetAction, - ItemSelectionSelectAction, ItemSelectionSwitchAction -} from './item-select.actions'; - -describe('ItemSelectService', () => { - let service: ItemSelectService; - - const mockItemId = 'id1'; - - const store: Store = jasmine.createSpyObj('store', { - /* tslint:disable:no-empty */ - dispatch: {}, - /* tslint:enable:no-empty */ - select: Observable.of(true) - }); - - const appStore: Store = jasmine.createSpyObj('appStore', { - /* tslint:disable:no-empty */ - dispatch: {}, - /* tslint:enable:no-empty */ - select: Observable.of(true) - }); - - beforeEach(() => { - service = new ItemSelectService(store, appStore); - }); - - describe('when the initialSelect method is triggered', () => { - beforeEach(() => { - service.initialSelect(mockItemId); - }); - - it('ItemSelectionInitialSelectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionInitialSelectAction(mockItemId)); - }); - }); - - describe('when the initialDeselect method is triggered', () => { - beforeEach(() => { - service.initialDeselect(mockItemId); - }); - - it('ItemSelectionInitialDeselectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionInitialDeselectAction(mockItemId)); - }); - }); - - describe('when the select method is triggered', () => { - beforeEach(() => { - service.select(mockItemId); - }); - - it('ItemSelectionSelectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionSelectAction(mockItemId)); - }); - }); - - describe('when the deselect method is triggered', () => { - beforeEach(() => { - service.deselect(mockItemId); - }); - - it('ItemSelectionDeselectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionDeselectAction(mockItemId)); - }); - }); - - describe('when the switch method is triggered', () => { - beforeEach(() => { - service.switch(mockItemId); - }); - - it('ItemSelectionSwitchAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionSwitchAction(mockItemId)); - }); - }); - - describe('when the reset method is triggered', () => { - beforeEach(() => { - service.reset(); - }); - - it('ItemSelectionInitialSelectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ItemSelectionResetAction(null)); - }); - }); - -}); diff --git a/src/app/shared/item-select/item-select.service.ts b/src/app/shared/item-select/item-select.service.ts deleted file mode 100644 index bf4c7ba239..0000000000 --- a/src/app/shared/item-select/item-select.service.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Injectable } from '@angular/core'; -import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; -import { ItemSelectionsState, ItemSelectionState } from './item-select.reducer'; -import { - ItemSelectionDeselectAction, - ItemSelectionInitialDeselectAction, - ItemSelectionInitialSelectAction, ItemSelectionResetAction, - ItemSelectionSelectAction, ItemSelectionSwitchAction -} from './item-select.actions'; -import { Observable } from 'rxjs/Observable'; -import { hasValue } from '../empty.util'; -import { map } from 'rxjs/operators'; -import { AppState } from '../../app.reducer'; - -const selectionStateSelector = (state: ItemSelectionsState) => state.itemSelection; -const itemSelectionsStateSelector = (state: AppState) => state.itemSelection; - -/** - * Service that takes care of selecting and deselecting items - */ -@Injectable() -export class ItemSelectService { - - constructor( - private store: Store, - private appStore: Store - ) { - } - - /** - * Request the current selection of a given item - * @param {string} id The UUID of the item - * @returns {Observable} Emits the current selection state of the given item, if it's unavailable, return false - */ - getSelected(id: string): Observable { - return this.store.select(selectionByIdSelector(id)).pipe( - map((object: ItemSelectionState) => { - if (object) { - return object.checked; - } else { - return false; - } - }) - ); - } - - /** - * Request the current selection of a given item - * @param {string} id The UUID of the item - * @returns {Observable} Emits the current selection state of the given item, if it's unavailable, return false - */ - getAllSelected(): Observable { - return this.appStore.select(itemSelectionsStateSelector).pipe( - map((state: ItemSelectionsState) => Object.keys(state).filter((key) => state[key].checked)) - ); - } - - /** - * Dispatches an initial select action to the store for a given item - * @param {string} id The UUID of the item to select - */ - public initialSelect(id: string): void { - this.store.dispatch(new ItemSelectionInitialSelectAction(id)); - } - - /** - * Dispatches an initial deselect action to the store for a given item - * @param {string} id The UUID of the item to deselect - */ - public initialDeselect(id: string): void { - this.store.dispatch(new ItemSelectionInitialDeselectAction(id)); - } - - /** - * Dispatches a select action to the store for a given item - * @param {string} id The UUID of the item to select - */ - public select(id: string): void { - this.store.dispatch(new ItemSelectionSelectAction(id)); - } - - /** - * Dispatches a deselect action to the store for a given item - * @param {string} id The UUID of the item to deselect - */ - public deselect(id: string): void { - this.store.dispatch(new ItemSelectionDeselectAction(id)); - } - - /** - * Dispatches a switch action to the store for a given item - * @param {string} id The UUID of the item to select - */ - public switch(id: string): void { - this.store.dispatch(new ItemSelectionSwitchAction(id)); - } - - /** - * Dispatches a reset action to the store for all items - */ - public reset(): void { - this.store.dispatch(new ItemSelectionResetAction(null)); - } - -} - -function selectionByIdSelector(id: string): MemoizedSelector { - return keySelector(id); -} - -export function keySelector(key: string): MemoizedSelector { - return createSelector(selectionStateSelector, (state: ItemSelectionState) => { - if (hasValue(state)) { - return state[key]; - } else { - return undefined; - } - }); -} diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/object-select/item-select/item-select.component.html similarity index 100% rename from src/app/shared/item-select/item-select.component.html rename to src/app/shared/object-select/item-select/item-select.component.html diff --git a/src/app/shared/item-select/item-select.component.scss b/src/app/shared/object-select/item-select/item-select.component.scss similarity index 100% rename from src/app/shared/item-select/item-select.component.scss rename to src/app/shared/object-select/item-select/item-select.component.scss diff --git a/src/app/shared/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts similarity index 76% rename from src/app/shared/item-select/item-select.component.spec.ts rename to src/app/shared/object-select/item-select/item-select.component.spec.ts index 0f3a9d5fae..9708e43ca9 100644 --- a/src/app/shared/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -1,30 +1,25 @@ import { ItemSelectComponent } from './item-select.component'; import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; -import { SharedModule } from '../shared.module'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { TranslateModule } from '@ngx-translate/core'; -import { ItemSelectService } from './item-select.service'; -import { ItemSelectServiceStub } from '../testing/item-select-service-stub'; -import { Observable } from 'rxjs/Observable'; -import { RemoteData } from '../../core/data/remote-data'; -import { PaginatedList } from '../../core/data/paginated-list'; -import { PageInfo } from '../../core/shared/page-info.model'; -import { Item } from '../../core/shared/item.model'; -import { By } from '@angular/platform-browser'; -import { ActivatedRoute, Route, Router } from '@angular/router'; -import { ActivatedRouteStub } from '../testing/active-router-stub'; -import { RouterStub } from '../testing/router-stub'; -import { HostWindowService } from '../host-window.service'; -import { HostWindowServiceStub } from '../testing/host-window-service-stub'; -import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; -import { LocationStrategy } from '@angular/common'; -import { MockLocationStrategy } from '@angular/common/testing'; import { RouterTestingModule } from '@angular/router/testing'; +import { Item } from '../../../core/shared/item.model'; +import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { PageInfo } from '../../../core/shared/page-info.model'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { TranslateModule } from '@ngx-translate/core'; +import { SharedModule } from '../../shared.module'; +import { ObjectSelectServiceStub } from '../../testing/object-select-service-stub'; +import { ObjectSelectService } from '../object-select.service'; +import { HostWindowService } from '../../host-window.service'; +import { HostWindowServiceStub } from '../../testing/host-window-service-stub'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; let fixture: ComponentFixture; - let itemSelectService: ItemSelectService; + let itemSelectService: ObjectSelectService; const mockItemList = [ Object.assign(new Item(), { @@ -70,7 +65,7 @@ describe('ItemSelectComponent', () => { imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], declarations: [], providers: [ - { provide: ItemSelectService, useValue: new ItemSelectServiceStub() }, + { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/object-select/item-select/item-select.component.ts similarity index 72% rename from src/app/shared/item-select/item-select.component.ts rename to src/app/shared/object-select/item-select/item-select.component.ts index 3a2003a327..6ada45cb3b 100644 --- a/src/app/shared/item-select/item-select.component.ts +++ b/src/app/shared/object-select/item-select/item-select.component.ts @@ -1,12 +1,11 @@ import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { ItemDataService } from '../../core/data/item-data.service'; -import { PaginatedList } from '../../core/data/paginated-list'; -import { RemoteData } from '../../core/data/remote-data'; -import { Observable } from 'rxjs/Observable'; -import { Item } from '../../core/shared/item.model'; -import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; -import { ItemSelectService } from './item-select.service'; import { take } from 'rxjs/operators'; +import { Observable } from 'rxjs/Observable'; +import { Item } from '../../../core/shared/item.model'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { ObjectSelectService } from '../object-select.service'; @Component({ selector: 'ds-item-select', @@ -50,11 +49,11 @@ export class ItemSelectComponent implements OnInit { */ selectedIds$: Observable; - constructor(private itemSelectService: ItemSelectService) { + constructor(private objectelectService: ObjectSelectService) { } ngOnInit(): void { - this.selectedIds$ = this.itemSelectService.getAllSelected(); + this.selectedIds$ = this.objectelectService.getAllSelected(); } /** @@ -62,7 +61,7 @@ export class ItemSelectComponent implements OnInit { * @param {string} id */ switch(id: string) { - this.itemSelectService.switch(id); + this.objectelectService.switch(id); } /** @@ -71,7 +70,7 @@ export class ItemSelectComponent implements OnInit { * @returns {Observable} */ getSelected(id: string): Observable { - return this.itemSelectService.getSelected(id); + return this.objectelectService.getSelected(id); } /** @@ -83,7 +82,7 @@ export class ItemSelectComponent implements OnInit { take(1) ).subscribe((ids: string[]) => { this.confirm.emit(ids); - this.itemSelectService.reset(); + this.objectelectService.reset(); }); } diff --git a/src/app/shared/object-select/object-select.actions.ts b/src/app/shared/object-select/object-select.actions.ts new file mode 100644 index 0000000000..4adaeb9fed --- /dev/null +++ b/src/app/shared/object-select/object-select.actions.ts @@ -0,0 +1,75 @@ +import { type } from '../ngrx/type'; +import { Action } from '@ngrx/store'; + +export const ObjectSelectionActionTypes = { + INITIAL_DESELECT: type('dspace/object-select/INITIAL_DESELECT'), + INITIAL_SELECT: type('dspace/object-select/INITIAL_SELECT'), + SELECT: type('dspace/object-select/SELECT'), + DESELECT: type('dspace/object-select/DESELECT'), + SWITCH: type('dspace/object-select/SWITCH'), + RESET: type('dspace/object-select/RESET') +}; + +export class ObjectSelectionAction implements Action { + /** + * UUID of the object a select action can be performed on + */ + id: string; + + /** + * Type of action that will be performed + */ + type; + + /** + * Initialize with the object's UUID + * @param {string} id of the object + */ + constructor(id: string) { + this.id = id; + } +} + +/* tslint:disable:max-classes-per-file */ +/** + * Used to set the initial state to deselected + */ +export class ObjectSelectionInitialDeselectAction extends ObjectSelectionAction { + type = ObjectSelectionActionTypes.INITIAL_DESELECT; +} + +/** + * Used to set the initial state to selected + */ +export class ObjectSelectionInitialSelectAction extends ObjectSelectionAction { + type = ObjectSelectionActionTypes.INITIAL_SELECT; +} + +/** + * Used to select an object + */ +export class ObjectSelectionSelectAction extends ObjectSelectionAction { + type = ObjectSelectionActionTypes.SELECT; +} + +/** + * Used to deselect an object + */ +export class ObjectSelectionDeselectAction extends ObjectSelectionAction { + type = ObjectSelectionActionTypes.DESELECT; +} + +/** + * Used to switch an object between selected and deselected + */ +export class ObjectSelectionSwitchAction extends ObjectSelectionAction { + type = ObjectSelectionActionTypes.SWITCH; +} + +/** + * Used to reset all objects selected to be deselected + */ +export class ObjectSelectionResetAction extends ObjectSelectionAction { + type = ObjectSelectionActionTypes.RESET; +} +/* tslint:enable:max-classes-per-file */ diff --git a/src/app/shared/object-select/object-select.reducer.spec.ts b/src/app/shared/object-select/object-select.reducer.spec.ts new file mode 100644 index 0000000000..696df97d39 --- /dev/null +++ b/src/app/shared/object-select/object-select.reducer.spec.ts @@ -0,0 +1,98 @@ +import { + ObjectSelectionDeselectAction, ObjectSelectionInitialDeselectAction, + ObjectSelectionInitialSelectAction, ObjectSelectionResetAction, + ObjectSelectionSelectAction, ObjectSelectionSwitchAction +} from './object-select.actions'; +import { objectSelectionReducer } from './object-select.reducer'; + +const objectId1 = 'id1'; +const objectId2 = 'id2'; + +class NullAction extends ObjectSelectionSelectAction { + type = null; + + constructor() { + super(undefined); + } +} + +describe('objectSelectionReducer', () => { + + it('should return the current state when no valid actions have been made', () => { + const state = {}; + state[objectId1] = { checked: true }; + const action = new NullAction(); + const newState = objectSelectionReducer(state, action); + + expect(newState).toEqual(state); + }); + + it('should start with an empty object', () => { + const state = {}; + const action = new NullAction(); + const newState = objectSelectionReducer(undefined, action); + + expect(newState).toEqual(state); + }); + + it('should set checked to true in response to the INITIAL_SELECT action', () => { + const action = new ObjectSelectionInitialSelectAction(objectId1); + const newState = objectSelectionReducer(undefined, action); + + expect(newState[objectId1].checked).toBeTruthy(); + }); + + it('should set checked to true in response to the INITIAL_DESELECT action', () => { + const action = new ObjectSelectionInitialDeselectAction(objectId1); + const newState = objectSelectionReducer(undefined, action); + + expect(newState[objectId1].checked).toBeFalsy(); + }); + + it('should set checked to true in response to the SELECT action', () => { + const state = {}; + state[objectId1] = { checked: false }; + const action = new ObjectSelectionSelectAction(objectId1); + const newState = objectSelectionReducer(state, action); + + expect(newState[objectId1].checked).toBeTruthy(); + }); + + it('should set checked to false in response to the DESELECT action', () => { + const state = {}; + state[objectId1] = { checked: true }; + const action = new ObjectSelectionDeselectAction(objectId1); + const newState = objectSelectionReducer(state, action); + + expect(newState[objectId1].checked).toBeFalsy(); + }); + + it('should set checked from false to true in response to the SWITCH action', () => { + const state = {}; + state[objectId1] = { checked: false }; + const action = new ObjectSelectionSwitchAction(objectId1); + const newState = objectSelectionReducer(state, action); + + expect(newState[objectId1].checked).toBeTruthy(); + }); + + it('should set checked from true to false in response to the SWITCH action', () => { + const state = {}; + state[objectId1] = { checked: true }; + const action = new ObjectSelectionSwitchAction(objectId1); + const newState = objectSelectionReducer(state, action); + + expect(newState[objectId1].checked).toBeFalsy(); + }); + + it('should set reset the state in response to the RESET action', () => { + const state = {}; + state[objectId1] = { checked: true }; + state[objectId2] = { checked: false }; + const action = new ObjectSelectionResetAction(undefined); + const newState = objectSelectionReducer(state, action); + + expect(newState).toEqual({}); + }); + +}); diff --git a/src/app/shared/item-select/item-select.reducer.ts b/src/app/shared/object-select/object-select.reducer.ts similarity index 67% rename from src/app/shared/item-select/item-select.reducer.ts rename to src/app/shared/object-select/object-select.reducer.ts index 6306adf0c4..bd54e43a35 100644 --- a/src/app/shared/item-select/item-select.reducer.ts +++ b/src/app/shared/object-select/object-select.reducer.ts @@ -1,21 +1,21 @@ import { isEmpty } from '../empty.util'; -import { ItemSelectionAction, ItemSelectionActionTypes } from './item-select.actions'; +import { ObjectSelectionAction, ObjectSelectionActionTypes } from './object-select.actions'; /** * Interface that represents the state for a single filters */ -export interface ItemSelectionState { +export interface ObjectSelectionState { checked: boolean; } /** * Interface that represents the state for all available filters */ -export interface ItemSelectionsState { - [id: string]: ItemSelectionState +export interface ObjectSelectionsState { + [id: string]: ObjectSelectionState } -const initialState: ItemSelectionsState = Object.create(null); +const initialState: ObjectSelectionsState = Object.create(null); /** * Performs a search filter action on the current state @@ -23,11 +23,11 @@ const initialState: ItemSelectionsState = Object.create(null); * @param {SearchFilterAction} action The action that should be performed * @returns {SearchFiltersState} The state after the action is performed */ -export function itemSelectionReducer(state = initialState, action: ItemSelectionAction): ItemSelectionsState { +export function objectSelectionReducer(state = initialState, action: ObjectSelectionAction): ObjectSelectionsState { switch (action.type) { - case ItemSelectionActionTypes.INITIAL_SELECT: { + case ObjectSelectionActionTypes.INITIAL_SELECT: { if (isEmpty(state) || isEmpty(state[action.id])) { return Object.assign({}, state, { [action.id]: { @@ -38,7 +38,7 @@ export function itemSelectionReducer(state = initialState, action: ItemSelection return state; } - case ItemSelectionActionTypes.INITIAL_DESELECT: { + case ObjectSelectionActionTypes.INITIAL_DESELECT: { if (isEmpty(state) || isEmpty(state[action.id])) { return Object.assign({}, state, { [action.id]: { @@ -49,7 +49,7 @@ export function itemSelectionReducer(state = initialState, action: ItemSelection return state; } - case ItemSelectionActionTypes.SELECT: { + case ObjectSelectionActionTypes.SELECT: { return Object.assign({}, state, { [action.id]: { checked: true @@ -57,7 +57,7 @@ export function itemSelectionReducer(state = initialState, action: ItemSelection }); } - case ItemSelectionActionTypes.DESELECT: { + case ObjectSelectionActionTypes.DESELECT: { return Object.assign({}, state, { [action.id]: { checked: false @@ -65,7 +65,7 @@ export function itemSelectionReducer(state = initialState, action: ItemSelection }); } - case ItemSelectionActionTypes.SWITCH: { + case ObjectSelectionActionTypes.SWITCH: { return Object.assign({}, state, { [action.id]: { checked: (isEmpty(state) || isEmpty(state[action.id])) ? true : !state[action.id].checked @@ -73,7 +73,7 @@ export function itemSelectionReducer(state = initialState, action: ItemSelection }); } - case ItemSelectionActionTypes.RESET: { + case ObjectSelectionActionTypes.RESET: { return {}; } diff --git a/src/app/shared/object-select/object-select.service.spec.ts b/src/app/shared/object-select/object-select.service.spec.ts new file mode 100644 index 0000000000..3b5bcec06f --- /dev/null +++ b/src/app/shared/object-select/object-select.service.spec.ts @@ -0,0 +1,96 @@ +import { ObjectSelectService } from './object-select.service'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs/Observable'; +import { ObjectSelectionsState } from './object-select.reducer'; +import { AppState } from '../../app.reducer'; +import { + ObjectSelectionDeselectAction, + ObjectSelectionInitialDeselectAction, + ObjectSelectionInitialSelectAction, ObjectSelectionResetAction, + ObjectSelectionSelectAction, ObjectSelectionSwitchAction +} from './object-select.actions'; + +describe('ObjectSelectService', () => { + let service: ObjectSelectService; + + const mockObjectId = 'id1'; + + const store: Store = jasmine.createSpyObj('store', { + /* tslint:disable:no-empty */ + dispatch: {}, + /* tslint:enable:no-empty */ + select: Observable.of(true) + }); + + const appStore: Store = jasmine.createSpyObj('appStore', { + /* tslint:disable:no-empty */ + dispatch: {}, + /* tslint:enable:no-empty */ + select: Observable.of(true) + }); + + beforeEach(() => { + service = new ObjectSelectService(store, appStore); + }); + + describe('when the initialSelect method is triggered', () => { + beforeEach(() => { + service.initialSelect(mockObjectId); + }); + + it('ObjectSelectionInitialSelectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionInitialSelectAction(mockObjectId)); + }); + }); + + describe('when the initialDeselect method is triggered', () => { + beforeEach(() => { + service.initialDeselect(mockObjectId); + }); + + it('ObjectSelectionInitialDeselectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionInitialDeselectAction(mockObjectId)); + }); + }); + + describe('when the select method is triggered', () => { + beforeEach(() => { + service.select(mockObjectId); + }); + + it('ObjectSelectionSelectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionSelectAction(mockObjectId)); + }); + }); + + describe('when the deselect method is triggered', () => { + beforeEach(() => { + service.deselect(mockObjectId); + }); + + it('ObjectSelectionDeselectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionDeselectAction(mockObjectId)); + }); + }); + + describe('when the switch method is triggered', () => { + beforeEach(() => { + service.switch(mockObjectId); + }); + + it('ObjectSelectionSwitchAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionSwitchAction(mockObjectId)); + }); + }); + + describe('when the reset method is triggered', () => { + beforeEach(() => { + service.reset(); + }); + + it('ObjectSelectionInitialSelectAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionResetAction(null)); + }); + }); + +}); diff --git a/src/app/shared/object-select/object-select.service.ts b/src/app/shared/object-select/object-select.service.ts new file mode 100644 index 0000000000..adc394d4e1 --- /dev/null +++ b/src/app/shared/object-select/object-select.service.ts @@ -0,0 +1,119 @@ +import { Injectable } from '@angular/core'; +import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; +import { ObjectSelectionsState, ObjectSelectionState } from './object-select.reducer'; +import { + ObjectSelectionDeselectAction, + ObjectSelectionInitialDeselectAction, + ObjectSelectionInitialSelectAction, ObjectSelectionResetAction, + ObjectSelectionSelectAction, ObjectSelectionSwitchAction +} from './object-select.actions'; +import { Observable } from 'rxjs/Observable'; +import { hasValue } from '../empty.util'; +import { map } from 'rxjs/operators'; +import { AppState } from '../../app.reducer'; + +const selectionStateSelector = (state: ObjectSelectionsState) => state.objectSelection; +const objectSelectionsStateSelector = (state: AppState) => state.objectSelection; + +/** + * Service that takes care of selecting and deselecting objects + */ +@Injectable() +export class ObjectSelectService { + + constructor( + private store: Store, + private appStore: Store + ) { + } + + /** + * Request the current selection of a given object + * @param {string} id The UUID of the object + * @returns {Observable} Emits the current selection state of the given object, if it's unavailable, return false + */ + getSelected(id: string): Observable { + return this.store.select(selectionByIdSelector(id)).pipe( + map((object: ObjectSelectionState) => { + if (object) { + return object.checked; + } else { + return false; + } + }) + ); + } + + /** + * Request the current selection of a given object + * @param {string} id The UUID of the object + * @returns {Observable} Emits the current selection state of the given object, if it's unavailable, return false + */ + getAllSelected(): Observable { + return this.appStore.select(objectSelectionsStateSelector).pipe( + map((state: ObjectSelectionsState) => Object.keys(state).filter((key) => state[key].checked)) + ); + } + + /** + * Dispatches an initial select action to the store for a given object + * @param {string} id The UUID of the object to select + */ + public initialSelect(id: string): void { + this.store.dispatch(new ObjectSelectionInitialSelectAction(id)); + } + + /** + * Dispatches an initial deselect action to the store for a given object + * @param {string} id The UUID of the object to deselect + */ + public initialDeselect(id: string): void { + this.store.dispatch(new ObjectSelectionInitialDeselectAction(id)); + } + + /** + * Dispatches a select action to the store for a given object + * @param {string} id The UUID of the object to select + */ + public select(id: string): void { + this.store.dispatch(new ObjectSelectionSelectAction(id)); + } + + /** + * Dispatches a deselect action to the store for a given object + * @param {string} id The UUID of the object to deselect + */ + public deselect(id: string): void { + this.store.dispatch(new ObjectSelectionDeselectAction(id)); + } + + /** + * Dispatches a switch action to the store for a given object + * @param {string} id The UUID of the object to select + */ + public switch(id: string): void { + this.store.dispatch(new ObjectSelectionSwitchAction(id)); + } + + /** + * Dispatches a reset action to the store for all objects + */ + public reset(): void { + this.store.dispatch(new ObjectSelectionResetAction(null)); + } + +} + +function selectionByIdSelector(id: string): MemoizedSelector { + return keySelector(id); +} + +export function keySelector(key: string): MemoizedSelector { + return createSelector(selectionStateSelector, (state: ObjectSelectionState) => { + if (hasValue(state)) { + return state[key]; + } else { + return undefined; + } + }); +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index c5c6cad09b..a10855bf5e 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -83,7 +83,7 @@ import { InputSuggestionsComponent } from './input-suggestions/input-suggestions import { CapitalizePipe } from './utils/capitalize.pipe'; import { MomentModule } from 'angular2-moment'; import { ObjectKeysPipe } from './utils/object-keys-pipe'; -import { ItemSelectComponent } from './item-select/item-select.component'; +import { ItemSelectComponent } from './object-select/item-select/item-select.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here diff --git a/src/app/shared/testing/item-select-service-stub.ts b/src/app/shared/testing/object-select-service-stub.ts similarity index 94% rename from src/app/shared/testing/item-select-service-stub.ts rename to src/app/shared/testing/object-select-service-stub.ts index 690d1e1435..f4bcccae77 100644 --- a/src/app/shared/testing/item-select-service-stub.ts +++ b/src/app/shared/testing/object-select-service-stub.ts @@ -1,6 +1,6 @@ import { Observable } from 'rxjs/Observable'; -export class ItemSelectServiceStub { +export class ObjectSelectServiceStub { ids: string[] = []; From 0b3b5d3965aaca7a6ca69a9659ac281f1f0e2841 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 10 Oct 2018 13:30:29 +0200 Subject: [PATCH 025/142] 55946: Refactoring of object-select and collection-select component --- resources/i18n/en.json | 6 + .../collection-item-mapper.component.html | 2 +- .../item-collection-mapper.component.html | 8 +- .../item-collection-mapper.component.ts | 10 ++ .../collection-select.component.html | 27 ++++ .../collection-select.component.scss | 0 .../collection-select.component.spec.ts | 126 ++++++++++++++++++ .../collection-select.component.ts | 29 ++++ .../item-select/item-select.component.html | 38 +++--- .../item-select/item-select.component.ts | 73 ++-------- .../object-select/object-select.component.ts | 86 ++++++++++++ src/app/shared/shared.module.ts | 4 +- 12 files changed, 323 insertions(+), 86 deletions(-) create mode 100644 src/app/shared/object-select/collection-select/collection-select.component.html create mode 100644 src/app/shared/object-select/collection-select/collection-select.component.scss create mode 100644 src/app/shared/object-select/collection-select/collection-select.component.spec.ts create mode 100644 src/app/shared/object-select/collection-select/collection-select.component.ts create mode 100644 src/app/shared/object-select/object-select/object-select.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 2db47f300c..05d7e300b0 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -34,6 +34,12 @@ } }, "return": "Return" + }, + "select": { + "table": { + "title": "Title" + }, + "confirm": "Confirm selected" } }, "community": { 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 a7a1416cb0..7ba2d8d68a 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 @@ -32,7 +32,7 @@
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 21149eafaf..990ac70c64 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 @@ -15,7 +15,7 @@
- +
@@ -26,7 +26,11 @@
- +
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 1ecbd7d1f0..6f80fcde27 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 @@ -87,6 +87,16 @@ export class ItemCollectionMapperComponent implements OnInit { ); } + /** + * Map the item to the selected collections and display notifications + * @param {string[]} ids The list of collection UUID's to map the item to + */ + mapCollections(ids: string[]) { + // TODO: Map item to selected collections and display notifications + console.log('mapped to collections:'); + console.log(ids); + } + /** * Clear url parameters on tab change (temporary fix until pagination is improved) * @param event 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 new file mode 100644 index 0000000000..551d33ba3b --- /dev/null +++ b/src/app/shared/object-select/collection-select/collection-select.component.html @@ -0,0 +1,27 @@ + + +
+ + + + + + + + + + + + + +
{{'collection.select.table.title' | translate}}
{{collection.name}}
+
+
+ +
diff --git a/src/app/shared/object-select/collection-select/collection-select.component.scss b/src/app/shared/object-select/collection-select/collection-select.component.scss new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ae4a6aa0a7 --- /dev/null +++ b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts @@ -0,0 +1,126 @@ +import { CollectionSelectComponent } from './item-select.component'; +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Item } from '../../../core/shared/item.model'; +import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { PageInfo } from '../../../core/shared/page-info.model'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { TranslateModule } from '@ngx-translate/core'; +import { SharedModule } from '../../shared.module'; +import { ObjectSelectServiceStub } from '../../testing/object-select-service-stub'; +import { ObjectSelectService } from '../object-select.service'; +import { HostWindowService } from '../../host-window.service'; +import { HostWindowServiceStub } from '../../testing/host-window-service-stub'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; + +describe('ItemSelectComponent', () => { + let comp: CollectionSelectComponent; + let fixture: ComponentFixture; + let itemSelectService: ObjectSelectService; + + const mockItemList = [ + Object.assign(new Item(), { + id: 'id1', + bitstreams: Observable.of({}), + metadata: [ + { + key: 'dc.title', + language: 'en_US', + value: 'This is just a title' + }, + { + key: 'dc.type', + language: null, + value: 'Article' + }] + }), + Object.assign(new Item(), { + id: 'id2', + bitstreams: Observable.of({}), + metadata: [ + { + key: 'dc.title', + language: 'en_US', + value: 'This is just another title' + }, + { + key: 'dc.type', + language: null, + value: 'Article' + }] + }) + ]; + const mockItems = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), mockItemList))); + const mockPaginationOptions = Object.assign(new PaginationComponentOptions(), { + id: 'search-page-configuration', + pageSize: 10, + currentPage: 1 + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], + declarations: [], + providers: [ + { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, + { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectionSelectComponent); + comp = fixture.componentInstance; + comp.itemsRD$ = mockItems; + comp.paginationOptions = mockPaginationOptions; + fixture.detectChanges(); + itemSelectService = (comp as any).itemSelectService; + }); + + it(`should show a list of ${mockItemList.length} items`, () => { + const tbody: HTMLElement = fixture.debugElement.query(By.css('table#item-select tbody')).nativeElement; + expect(tbody.children.length).toBe(mockItemList.length); + }); + + describe('checkboxes', () => { + let checkbox: HTMLInputElement; + + beforeEach(() => { + checkbox = fixture.debugElement.query(By.css('input.item-checkbox')).nativeElement; + }); + + it('should initially be unchecked',() => { + expect(checkbox.checked).toBeFalsy(); + }); + + it('should be checked when clicked', () => { + checkbox.click(); + fixture.detectChanges(); + expect(checkbox.checked).toBeTruthy(); + }); + + it('should switch the value through item-select-service', () => { + spyOn((comp as any).itemSelectService, 'switch').and.callThrough(); + checkbox.click(); + expect((comp as any).itemSelectService.switch).toHaveBeenCalled(); + }); + }); + + describe('when confirm is clicked', () => { + let confirmButton: HTMLButtonElement; + + beforeEach(() => { + confirmButton = fixture.debugElement.query(By.css('button.item-confirm')).nativeElement; + spyOn(comp.confirm, 'emit').and.callThrough(); + }); + + it('should emit the selected items',() => { + confirmButton.click(); + expect(comp.confirm.emit).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/shared/object-select/collection-select/collection-select.component.ts b/src/app/shared/object-select/collection-select/collection-select.component.ts new file mode 100644 index 0000000000..489e9109fc --- /dev/null +++ b/src/app/shared/object-select/collection-select/collection-select.component.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; +import { Collection } from '../../../core/shared/collection.model'; +import { ObjectSelectComponent } from '../object-select/object-select.component'; +import { isNotEmpty } from '../../empty.util'; +import { ObjectSelectService } from '../object-select.service'; + +@Component({ + selector: 'ds-collection-select', + styleUrls: ['./collection-select.component.scss'], + templateUrl: './collection-select.component.html' +}) + +/** + * A component used to select collections from a specific list and returning the UUIDs of the selected collections + */ +export class CollectionSelectComponent extends ObjectSelectComponent { + + constructor(protected objectSelectService: ObjectSelectService) { + super(objectSelectService); + } + + ngOnInit(): void { + super.ngOnInit(); + if (!isNotEmpty(this.confirmButton)) { + this.confirmButton = 'collection.select.confirm'; + } + } + +} 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 9c08cfae87..9546623ecf 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 @@ -1,29 +1,31 @@ - -
- - + + +
+
+ - - - + + + - -
{{'item.select.table.collection' | translate}} {{'item.select.table.author' | translate}} {{'item.select.table.title' | translate}}
{{(item.owningCollection | async)?.payload?.name}} {{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}} {{item.findMetadata("dc.title")}}
-
-
- + + +
+ + + diff --git a/src/app/shared/object-select/item-select/item-select.component.ts b/src/app/shared/object-select/item-select/item-select.component.ts index 6ada45cb3b..348be4b37d 100644 --- a/src/app/shared/object-select/item-select/item-select.component.ts +++ b/src/app/shared/object-select/item-select/item-select.component.ts @@ -6,6 +6,8 @@ import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; import { ObjectSelectService } from '../object-select.service'; +import { ObjectSelectComponent } from '../object-select/object-select.component'; +import { isNotEmpty } from '../../empty.util'; @Component({ selector: 'ds-item-select', @@ -16,74 +18,17 @@ import { ObjectSelectService } from '../object-select.service'; /** * A component used to select items from a specific list and returning the UUIDs of the selected items */ -export class ItemSelectComponent implements OnInit { +export class ItemSelectComponent extends ObjectSelectComponent { - /** - * The list of items to display - */ - @Input() - itemsRD$: Observable>>; - - /** - * The pagination options used to display the items - */ - @Input() - paginationOptions: PaginationComponentOptions; - - /** - * The message key used for the confirm button - * @type {string} - */ - @Input() - confirmButton = 'item.select.confirm'; - - /** - * EventEmitter to return the selected UUIDs when the confirm button is pressed - * @type {EventEmitter} - */ - @Output() - confirm: EventEmitter = new EventEmitter(); - - /** - * The list of selected UUIDs - */ - selectedIds$: Observable; - - constructor(private objectelectService: ObjectSelectService) { + constructor(protected objectSelectService: ObjectSelectService) { + super(objectSelectService); } ngOnInit(): void { - this.selectedIds$ = this.objectelectService.getAllSelected(); - } - - /** - * Switch the state of a checkbox - * @param {string} id - */ - switch(id: string) { - this.objectelectService.switch(id); - } - - /** - * Get the current state of a checkbox - * @param {string} id The item's UUID - * @returns {Observable} - */ - getSelected(id: string): Observable { - return this.objectelectService.getSelected(id); - } - - /** - * Called when the confirm button is pressed - * Sends the selected UUIDs to the parent component - */ - confirmSelected() { - this.selectedIds$.pipe( - take(1) - ).subscribe((ids: string[]) => { - this.confirm.emit(ids); - this.objectelectService.reset(); - }); + super.ngOnInit(); + if (!isNotEmpty(this.confirmButton)) { + this.confirmButton = 'item.select.confirm'; + } } } diff --git a/src/app/shared/object-select/object-select/object-select.component.ts b/src/app/shared/object-select/object-select/object-select.component.ts new file mode 100644 index 0000000000..fb9e28edc2 --- /dev/null +++ b/src/app/shared/object-select/object-select/object-select.component.ts @@ -0,0 +1,86 @@ +import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { take } from 'rxjs/operators'; +import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { ObjectSelectService } from '../object-select.service'; + +/** + * An abstract component used to select DSpaceObjects from a specific list and returning the UUIDs of the selected DSpaceObjects + */ +export abstract class ObjectSelectComponent implements OnInit, OnDestroy { + + /** + * The list of DSpaceObjects to display + */ + @Input() + dsoRD$: Observable>>; + + /** + * The pagination options used to display the DSpaceObjects + */ + @Input() + paginationOptions: PaginationComponentOptions; + + /** + * The message key used for the confirm button + * @type {string} + */ + @Input() + confirmButton: string; + + /** + * EventEmitter to return the selected UUIDs when the confirm button is pressed + * @type {EventEmitter} + */ + @Output() + confirm: EventEmitter = new EventEmitter(); + + /** + * The list of selected UUIDs + */ + selectedIds$: Observable; + + constructor(protected objectSelectService: ObjectSelectService) { + } + + ngOnInit(): void { + this.selectedIds$ = this.objectSelectService.getAllSelected(); + } + + ngOnDestroy(): void { + this.objectSelectService.reset(); + } + + /** + * Switch the state of a checkbox + * @param {string} id + */ + switch(id: string) { + this.objectSelectService.switch(id); + } + + /** + * Get the current state of a checkbox + * @param {string} id The dso's UUID + * @returns {Observable} + */ + getSelected(id: string): Observable { + return this.objectSelectService.getSelected(id); + } + + /** + * Called when the confirm button is pressed + * Sends the selected UUIDs to the parent component + */ + confirmSelected() { + this.selectedIds$.pipe( + take(1) + ).subscribe((ids: string[]) => { + this.confirm.emit(ids); + this.objectSelectService.reset(); + }); + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index a10855bf5e..6cb9098410 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -84,6 +84,7 @@ import { CapitalizePipe } from './utils/capitalize.pipe'; import { MomentModule } from 'angular2-moment'; import { ObjectKeysPipe } from './utils/object-keys-pipe'; import { ItemSelectComponent } from './object-select/item-select/item-select.component'; +import { CollectionSelectComponent } from './object-select/collection-select/collection-select.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -158,7 +159,8 @@ const COMPONENTS = [ TruncatablePartComponent, BrowseByComponent, InputSuggestionsComponent, - ItemSelectComponent + ItemSelectComponent, + CollectionSelectComponent ]; const ENTRY_COMPONENTS = [ From 0cf91fb05e7d3809f8bc3126ed82252ea53bb2b2 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 10 Oct 2018 13:51:02 +0200 Subject: [PATCH 026/142] 55946: Removal of unnecessary scss files and test fixes --- .../collection-select.component.scss | 0 .../collection-select.component.spec.ts | 64 ++++++------------- .../collection-select.component.ts | 1 - .../item-select/item-select.component.scss | 0 .../item-select/item-select.component.spec.ts | 12 ++-- .../item-select/item-select.component.ts | 1 - 6 files changed, 27 insertions(+), 51 deletions(-) delete mode 100644 src/app/shared/object-select/collection-select/collection-select.component.scss delete mode 100644 src/app/shared/object-select/item-select/item-select.component.scss diff --git a/src/app/shared/object-select/collection-select/collection-select.component.scss b/src/app/shared/object-select/collection-select/collection-select.component.scss deleted file mode 100644 index e69de29bb2..0000000000 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 ae4a6aa0a7..477f823928 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 @@ -1,7 +1,5 @@ -import { CollectionSelectComponent } from './item-select.component'; -import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { Item } from '../../../core/shared/item.model'; import { Observable } from 'rxjs/Observable'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; @@ -15,45 +13,25 @@ import { HostWindowService } from '../../host-window.service'; import { HostWindowServiceStub } from '../../testing/host-window-service-stub'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; +import { CollectionSelectComponent } from './collection-select.component'; +import { Collection } from '../../../core/shared/collection.model'; describe('ItemSelectComponent', () => { let comp: CollectionSelectComponent; let fixture: ComponentFixture; - let itemSelectService: ObjectSelectService; + let objectSelectService: ObjectSelectService; - const mockItemList = [ - Object.assign(new Item(), { + const mockCollectionList = [ + Object.assign(new Collection(), { id: 'id1', - bitstreams: Observable.of({}), - metadata: [ - { - key: 'dc.title', - language: 'en_US', - value: 'This is just a title' - }, - { - key: 'dc.type', - language: null, - value: 'Article' - }] + name: 'name1' }), - Object.assign(new Item(), { + Object.assign(new Collection(), { id: 'id2', - bitstreams: Observable.of({}), - metadata: [ - { - key: 'dc.title', - language: 'en_US', - value: 'This is just another title' - }, - { - key: 'dc.type', - language: null, - value: 'Article' - }] + name: 'name2' }) ]; - const mockItems = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), mockItemList))); + const mockCollections = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), mockCollectionList))); const mockPaginationOptions = Object.assign(new PaginationComponentOptions(), { id: 'search-page-configuration', pageSize: 10, @@ -75,22 +53,22 @@ describe('ItemSelectComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(CollectionSelectComponent); comp = fixture.componentInstance; - comp.itemsRD$ = mockItems; + comp.dsoRD$ = mockCollections; comp.paginationOptions = mockPaginationOptions; fixture.detectChanges(); - itemSelectService = (comp as any).itemSelectService; + objectSelectService = (comp as any).objectSelectService; }); - it(`should show a list of ${mockItemList.length} items`, () => { - const tbody: HTMLElement = fixture.debugElement.query(By.css('table#item-select tbody')).nativeElement; - expect(tbody.children.length).toBe(mockItemList.length); + it(`should show a list of ${mockCollectionList.length} collections`, () => { + const tbody: HTMLElement = fixture.debugElement.query(By.css('table#collection-select tbody')).nativeElement; + expect(tbody.children.length).toBe(mockCollectionList.length); }); describe('checkboxes', () => { let checkbox: HTMLInputElement; beforeEach(() => { - checkbox = fixture.debugElement.query(By.css('input.item-checkbox')).nativeElement; + checkbox = fixture.debugElement.query(By.css('input.collection-checkbox')).nativeElement; }); it('should initially be unchecked',() => { @@ -103,10 +81,10 @@ describe('ItemSelectComponent', () => { expect(checkbox.checked).toBeTruthy(); }); - it('should switch the value through item-select-service', () => { - spyOn((comp as any).itemSelectService, 'switch').and.callThrough(); + it('should switch the value through object-select-service', () => { + spyOn((comp as any).objectSelectService, 'switch').and.callThrough(); checkbox.click(); - expect((comp as any).itemSelectService.switch).toHaveBeenCalled(); + expect((comp as any).objectSelectService.switch).toHaveBeenCalled(); }); }); @@ -114,11 +92,11 @@ describe('ItemSelectComponent', () => { let confirmButton: HTMLButtonElement; beforeEach(() => { - confirmButton = fixture.debugElement.query(By.css('button.item-confirm')).nativeElement; + confirmButton = fixture.debugElement.query(By.css('button.collection-confirm')).nativeElement; spyOn(comp.confirm, 'emit').and.callThrough(); }); - it('should emit the selected items',() => { + it('should emit the selected collections',() => { confirmButton.click(); expect(comp.confirm.emit).toHaveBeenCalled(); }); diff --git a/src/app/shared/object-select/collection-select/collection-select.component.ts b/src/app/shared/object-select/collection-select/collection-select.component.ts index 489e9109fc..3d40b469da 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.ts +++ b/src/app/shared/object-select/collection-select/collection-select.component.ts @@ -6,7 +6,6 @@ import { ObjectSelectService } from '../object-select.service'; @Component({ selector: 'ds-collection-select', - styleUrls: ['./collection-select.component.scss'], templateUrl: './collection-select.component.html' }) diff --git a/src/app/shared/object-select/item-select/item-select.component.scss b/src/app/shared/object-select/item-select/item-select.component.scss deleted file mode 100644 index e69de29bb2..0000000000 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 9708e43ca9..e07858360e 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 @@ -19,7 +19,7 @@ import { By } from '@angular/platform-browser'; describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; let fixture: ComponentFixture; - let itemSelectService: ObjectSelectService; + let objectSelectService: ObjectSelectService; const mockItemList = [ Object.assign(new Item(), { @@ -75,10 +75,10 @@ describe('ItemSelectComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ItemSelectComponent); comp = fixture.componentInstance; - comp.itemsRD$ = mockItems; + comp.dsoRD$ = mockItems; comp.paginationOptions = mockPaginationOptions; fixture.detectChanges(); - itemSelectService = (comp as any).itemSelectService; + objectSelectService = (comp as any).objectSelectService; }); it(`should show a list of ${mockItemList.length} items`, () => { @@ -103,10 +103,10 @@ describe('ItemSelectComponent', () => { expect(checkbox.checked).toBeTruthy(); }); - it('should switch the value through item-select-service', () => { - spyOn((comp as any).itemSelectService, 'switch').and.callThrough(); + it('should switch the value through object-select-service', () => { + spyOn((comp as any).objectSelectService, 'switch').and.callThrough(); checkbox.click(); - expect((comp as any).itemSelectService.switch).toHaveBeenCalled(); + expect((comp as any).objectSelectService.switch).toHaveBeenCalled(); }); }); diff --git a/src/app/shared/object-select/item-select/item-select.component.ts b/src/app/shared/object-select/item-select/item-select.component.ts index 348be4b37d..d8d4eef34a 100644 --- a/src/app/shared/object-select/item-select/item-select.component.ts +++ b/src/app/shared/object-select/item-select/item-select.component.ts @@ -11,7 +11,6 @@ import { isNotEmpty } from '../../empty.util'; @Component({ selector: 'ds-item-select', - styleUrls: ['./item-select.component.scss'], templateUrl: './item-select.component.html' }) From dd38e612306c2f7c3e177f2b807344047bc0ad39 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 10 Oct 2018 15:31:58 +0200 Subject: [PATCH 027/142] 55946: Functional item-collection-mapper --- .../item-collection-mapper.component.ts | 46 +++++++++++++++---- 1 file changed, 36 insertions(+), 10 deletions(-) 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 6f80fcde27..8708065488 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 @@ -15,6 +15,9 @@ import { SearchConfigurationService } from '../../../+search-page/search-service import { map, switchMap } from 'rxjs/operators'; import { CollectionDataService } from '../../../core/data/collection-data.service'; import { ItemDataService } from '../../../core/data/item-data.service'; +import { RestResponse } from '../../../core/cache/response-cache.models'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; @Component({ selector: 'ds-item-collection-mapper', @@ -52,18 +55,14 @@ export class ItemCollectionMapperComponent implements OnInit { */ 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) { + private notificationsService: NotificationsService, + private itemDataService: ItemDataService, + private translateService: TranslateService) { } ngOnInit(): void { @@ -92,9 +91,36 @@ export class ItemCollectionMapperComponent implements OnInit { * @param {string[]} ids The list of collection UUID's to map the item to */ mapCollections(ids: string[]) { - // TODO: Map item to selected collections and display notifications - console.log('mapped to collections:'); - console.log(ids); + const responses$ = this.itemRD$.pipe( + getSucceededRemoteData(), + map((itemRD: RemoteData) => itemRD.payload.id), + switchMap((itemId: string) => Observable.combineLatest(ids.map((id: string) => this.itemDataService.mapToCollection(itemId, id)))) + ); + + responses$.subscribe((responses: RestResponse[]) => { + const successful = responses.filter((response: RestResponse) => response.isSuccessful); + const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); + if (successful.length > 0) { + const successMessages = Observable.combineLatest( + this.translateService.get('item.edit.item-mapper.notifications.success.head'), + this.translateService.get('item.edit.item-mapper.notifications.success.content', { amount: successful.length }) + ); + + successMessages.subscribe(([head, content]) => { + this.notificationsService.success(head, content); + }); + } + if (unsuccessful.length > 0) { + const unsuccessMessages = Observable.combineLatest( + this.translateService.get('item.edit.item-mapper.notifications.error.head'), + this.translateService.get('item.edit.item-mapper.notifications.error.content', { amount: unsuccessful.length }) + ); + + unsuccessMessages.subscribe(([head, content]) => { + this.notificationsService.error(head, content); + }); + } + }); } /** From 378fbe86f4473648b1d3d7801f9a3732c5291f12 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 10 Oct 2018 16:15:53 +0200 Subject: [PATCH 028/142] 55946: Functionality for removing mappings --- resources/i18n/en.json | 29 ++++++++++++---- .../item-collection-mapper.component.html | 8 +++-- .../item-collection-mapper.component.ts | 34 ++++++++++++++++--- src/app/core/data/item-data.service.ts | 26 +++++++++++--- ...ing-collections-reponse-parsing.service.ts | 12 ++++++- 5 files changed, 90 insertions(+), 19 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 05d7e300b0..dcc19e6a21 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -135,19 +135,34 @@ "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" }, + "buttons": { + "add": "Map item to selected collections", + "remove": "Remove item's mapping for selected collections" + }, "notifications": { - "success": { - "head": "Mapping completed", - "content": "Successfully mapped item to {{amount}} collections." + "add": { + "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." + } }, - "error": { - "head": "Mapping errors", - "content": "Errors occurred for mapping of item to {{amount}} collections." + "remove": { + "success": { + "head": "Removal of mapping completed", + "content": "Successfully removed mapping of item to {{amount}} collections." + }, + "error": { + "head": "Removal of mapping errors", + "content": "Errors occurred for the removal of the mapping to {{amount}} collections." + } } }, "return": "Return" 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 990ac70c64..f02a62b769 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 @@ -19,7 +19,11 @@
-
{{col.name}}
+
@@ -29,7 +33,7 @@
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 8708065488..95c2ffd361 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 @@ -18,6 +18,7 @@ import { ItemDataService } from '../../../core/data/item-data.service'; import { RestResponse } from '../../../core/cache/response-cache.models'; import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { C } from '@angular/core/src/render3'; @Component({ selector: 'ds-item-collection-mapper', @@ -47,7 +48,7 @@ export class ItemCollectionMapperComponent implements OnInit { * List of collections to show under the "Browse" tab * Collections that are mapped to the item */ - itemCollectionsRD$: Observable>; + itemCollectionsRD$: Observable>>; /** * List of collections to show under the "Map" tab @@ -97,13 +98,36 @@ export class ItemCollectionMapperComponent implements OnInit { switchMap((itemId: string) => Observable.combineLatest(ids.map((id: string) => this.itemDataService.mapToCollection(itemId, id)))) ); + this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add'); + } + + /** + * Remove the mapping of the item to the selected collections and display notifications + * @param {string[]} ids The list of collection UUID's to remove the mapping of the item for + */ + removeMappings(ids: string[]) { + const responses$ = this.itemRD$.pipe( + getSucceededRemoteData(), + map((itemRD: RemoteData) => itemRD.payload.id), + switchMap((itemId: string) => Observable.combineLatest(ids.map((id: string) => this.itemDataService.removeMappingFromCollection(itemId, id)))) + ); + + this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove'); + } + + /** + * Display notifications + * @param {Observable} responses$ The responses after adding/removing a mapping + * @param {string} messagePrefix The prefix to build the notification messages with + */ + private showNotifications(responses$: Observable, messagePrefix: string) { responses$.subscribe((responses: RestResponse[]) => { const successful = responses.filter((response: RestResponse) => response.isSuccessful); const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); if (successful.length > 0) { const successMessages = Observable.combineLatest( - this.translateService.get('item.edit.item-mapper.notifications.success.head'), - this.translateService.get('item.edit.item-mapper.notifications.success.content', { amount: successful.length }) + this.translateService.get(`${messagePrefix}.success.head`), + this.translateService.get(`${messagePrefix}.success.content`, { amount: successful.length }) ); successMessages.subscribe(([head, content]) => { @@ -112,8 +136,8 @@ export class ItemCollectionMapperComponent implements OnInit { } if (unsuccessful.length > 0) { const unsuccessMessages = Observable.combineLatest( - this.translateService.get('item.edit.item-mapper.notifications.error.head'), - this.translateService.get('item.edit.item-mapper.notifications.error.content', { amount: unsuccessful.length }) + this.translateService.get(`${messagePrefix}.error.head`), + this.translateService.get(`${messagePrefix}.error.content`, { amount: unsuccessful.length }) ); unsuccessMessages.subscribe(([head, content]) => { diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 0fe9a690e2..3b6d3d90ab 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -15,7 +15,14 @@ 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, GetRequest, MappingCollectionsRequest, PostRequest, RestRequest } from './request.models'; +import { + DeleteRequest, + FindAllOptions, + GetRequest, + MappingCollectionsRequest, + PostRequest, + RestRequest +} from './request.models'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { configureRequest, @@ -69,6 +76,18 @@ export class ItemDataService extends DataService { ); } + public removeMappingFromCollection(itemId: string, collectionId: string): Observable { + return this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( + isNotEmptyOperator(), + distinctUntilChanged(), + map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)), + configureRequest(this.requestService), + map((request: RestRequest) => request.href), + getResponseFromSelflink(this.responseCache), + map((responseCacheEntry: ResponseCacheEntry) => responseCacheEntry.response) + ); + } + public mapToCollection(itemId: string, collectionId: string): Observable { return this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( isNotEmptyOperator(), @@ -81,7 +100,7 @@ export class ItemDataService extends DataService { ); } - public getMappedCollections(itemId: string): Observable> { + public getMappedCollections(itemId: string): Observable>> { const request$ = this.getMappingCollectionsEndpoint(itemId).pipe( isNotEmptyOperator(), distinctUntilChanged(), @@ -95,8 +114,7 @@ export class ItemDataService extends DataService { const payload$ = responseCache$.pipe( filterSuccessfulResponses(), map((entry: ResponseCacheEntry) => entry.response), - map((response: GenericSuccessResponse) => response.payload), - ensureArrayHasValue() + map((response: GenericSuccessResponse>) => response.payload) ); return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); diff --git a/src/app/core/data/mapping-collections-reponse-parsing.service.ts b/src/app/core/data/mapping-collections-reponse-parsing.service.ts index 1b1ff3368f..0ae014301c 100644 --- a/src/app/core/data/mapping-collections-reponse-parsing.service.ts +++ b/src/app/core/data/mapping-collections-reponse-parsing.service.ts @@ -3,6 +3,8 @@ 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 { PaginatedList } from './paginated-list'; +import { PageInfo } from '../shared/page-info.model'; @Injectable() export class MappingCollectionsReponseParsingService implements ResponseParsingService { @@ -11,7 +13,15 @@ export class MappingCollectionsReponseParsingService implements ResponseParsingS if (payload._embedded && payload._embedded.mappingCollections) { const mappingCollections = payload._embedded.mappingCollections; - return new GenericSuccessResponse(mappingCollections, data.statusCode); + // TODO: When the API supports it, change this to fetch a paginated list, instead of creating static one + // Reason: Pagination is currently not supported on the mappingCollections endpoint + const paginatedMappingCollections = new PaginatedList(Object.assign(new PageInfo(), { + elementsPerPage: mappingCollections.length, + totalElements: mappingCollections.length, + totalPages: 1, + currentPage: 1 + }), mappingCollections); + return new GenericSuccessResponse(paginatedMappingCollections, data.statusCode); } else { return new ErrorResponse( Object.assign( From 497f089c2fe689b653d0603b70ccc63b526c6628 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 10 Oct 2018 17:31:18 +0200 Subject: [PATCH 029/142] 55946: Improvement on mapping by excluding already existing mapped collections --- .../item-collection-mapper.component.ts | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) 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 95c2ffd361..ce82c99c8a 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 @@ -92,10 +92,22 @@ export class ItemCollectionMapperComponent implements OnInit { * @param {string[]} ids The list of collection UUID's to map the item to */ mapCollections(ids: string[]) { - const responses$ = this.itemRD$.pipe( - getSucceededRemoteData(), - map((itemRD: RemoteData) => itemRD.payload.id), - switchMap((itemId: string) => Observable.combineLatest(ids.map((id: string) => this.itemDataService.mapToCollection(itemId, id)))) + const itemIdAndExcludingIds$ = Observable.combineLatest( + this.itemRD$.pipe( + getSucceededRemoteData(), + map((rd: RemoteData) => rd.payload), + map((item: Item) => item.id) + ), + this.itemCollectionsRD$.pipe( + getSucceededRemoteData(), + map((rd: RemoteData>) => rd.payload.page), + map((collections: Collection[]) => collections.map((collection: Collection) => collection.id)) + ) + ); + + // Map the item to the collections found in ids, excluding the collections the item is already mapped to + const responses$ = itemIdAndExcludingIds$.pipe( + switchMap(([itemId, excludingIds]) => Observable.combineLatest(this.filterIds(ids, excludingIds).map((id: string) => this.itemDataService.mapToCollection(itemId, id)))) ); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add'); @@ -106,6 +118,7 @@ export class ItemCollectionMapperComponent implements OnInit { * @param {string[]} ids The list of collection UUID's to remove the mapping of the item for */ removeMappings(ids: string[]) { + // TODO: When the API supports fetching collections excluding the item's scope, make sure to exclude ids from mappingCollectionsRD$ here const responses$ = this.itemRD$.pipe( getSucceededRemoteData(), map((itemRD: RemoteData) => itemRD.payload.id), @@ -115,6 +128,16 @@ export class ItemCollectionMapperComponent implements OnInit { this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove'); } + /** + * Filters ids from a given list of ids, which exist in a second given list of ids + * @param {string[]} ids The list of ids to filter out of + * @param {string[]} excluding The ids that should be excluded from the first list + * @returns {string[]} + */ + private filterIds(ids: string[], excluding: string[]): string[] { + return ids.filter((id: string) => excluding.indexOf(id) < 0); + } + /** * Display notifications * @param {Observable} responses$ The responses after adding/removing a mapping From 8f0d7b6a4ec1cbff2a0ecb2c8a51ec5e763ca115 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 11 Oct 2018 10:10:45 +0200 Subject: [PATCH 030/142] 55946: ItemCollectionMapperComponent tests --- .../item-collection-mapper.component.spec.ts | 162 ++++++++++++++++++ .../item-collection-mapper.component.ts | 1 - 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts index e69de29bb2..1eb91e30b1 100644 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts @@ -0,0 +1,162 @@ +import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CommonModule } from '@angular/common'; +import { ItemCollectionMapperComponent } from './item-collection-mapper.component'; +import { ActivatedRoute, Router } from '@angular/router'; +import { SearchConfigurationService } from '../../../+search-page/search-service/search-configuration.service'; +import { SearchService } from '../../../+search-page/search-service/search.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { Collection } from '../../../core/shared/collection.model'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Observable } from 'rxjs/Observable'; +import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { RouterStub } from '../../../shared/testing/router-stub'; +import { RestResponse } from '../../../core/cache/response-cache.models'; +import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub'; +import { EventEmitter } from '@angular/core'; +import { SearchServiceStub } from '../../../shared/testing/search-service-stub'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { PageInfo } from '../../../core/shared/page-info.model'; +import { FormsModule } from '@angular/forms'; +import { SharedModule } from '../../../shared/shared.module'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; +import { HostWindowService } from '../../../shared/host-window.service'; +import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub'; +import { By } from '@angular/platform-browser'; +import { Item } from '../../../core/shared/item.model'; +import { CollectionDataService } from '../../../core/data/collection-data.service'; +import { ObjectSelectService } from '../../../shared/object-select/object-select.service'; +import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service-stub'; + +fdescribe('ItemCollectionMapperComponent', () => { + let comp: ItemCollectionMapperComponent; + let fixture: ComponentFixture; + + let route: ActivatedRoute; + let router: Router; + let searchConfigService: SearchConfigurationService; + let notificationsService: NotificationsService; + let itemDataService: ItemDataService; + let collectionDataService: CollectionDataService; + + const mockItem: Item = Object.assign(new Item(), { + id: '932c7d50-d85a-44cb-b9dc-b427b12877bd', + name: 'test-item' + }); + const mockItemRD: RemoteData = new RemoteData(false, false, true, null, mockItem); + const mockSearchOptions = Observable.of(new PaginatedSearchOptions({ + pagination: Object.assign(new PaginationComponentOptions(), { + id: 'search-page-configuration', + pageSize: 10, + currentPage: 1 + }), + sort: new SortOptions('dc.title', SortDirection.ASC) + })); + const routerStub = Object.assign(new RouterStub(), { + url: 'http://test.url' + }); + const searchConfigServiceStub = { + paginatedSearchOptions: mockSearchOptions + }; + const mockCollectionsRD = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [])); + const itemDataServiceStub = { + mapToCollection: () => Observable.of(new RestResponse(true, '200')), + removeMappingFromCollection: () => Observable.of(new RestResponse(true, '200')), + getMappedCollections: () => Observable.of(mockCollectionsRD) + }; + const collectionDataServiceStub = { + findAll: () => Observable.of(mockCollectionsRD) + }; + const activatedRouteStub = new ActivatedRouteStub({}, { item: mockItemRD }); + const translateServiceStub = { + get: () => Observable.of('test-message of item ' + mockItem.name), + onLangChange: new EventEmitter(), + onTranslationChange: new EventEmitter(), + onDefaultLangChange: new EventEmitter() + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CommonModule, FormsModule, SharedModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [ItemCollectionMapperComponent], + providers: [ + { provide: ActivatedRoute, useValue: activatedRouteStub }, + { provide: Router, useValue: routerStub }, + { provide: SearchConfigurationService, useValue: searchConfigServiceStub }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: ItemDataService, useValue: itemDataServiceStub }, + { provide: CollectionDataService, useValue: collectionDataServiceStub }, + { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, + { provide: TranslateService, useValue: translateServiceStub }, + { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemCollectionMapperComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + route = (comp as any).route; + router = (comp as any).router; + searchConfigService = (comp as any).searchConfigService; + notificationsService = (comp as any).notificationsService; + itemDataService = (comp as any).itemDataService; + collectionDataService = (comp as any).collectionDataService; + }); + + it('should display the correct collection name', () => { + const name: HTMLElement = fixture.debugElement.query(By.css('#item-name')).nativeElement; + expect(name.innerHTML).toContain(mockItem.name); + }); + + describe('mapCollections', () => { + const ids = ['id1', 'id2', 'id3', 'id4']; + + beforeEach(() => { + spyOn(notificationsService, 'success').and.callThrough(); + spyOn(notificationsService, 'error').and.callThrough(); + }); + + it('should display a success message if at least one mapping was successful', () => { + comp.mapCollections(ids); + expect(notificationsService.success).toHaveBeenCalled(); + expect(notificationsService.error).not.toHaveBeenCalled(); + }); + + it('should display an error message if at least one mapping was unsuccessful', () => { + spyOn(itemDataService, 'mapToCollection').and.returnValue(Observable.of(new RestResponse(false, '404'))); + comp.mapCollections(ids); + expect(notificationsService.success).not.toHaveBeenCalled(); + expect(notificationsService.error).toHaveBeenCalled(); + }); + }); + + describe('removeMappings', () => { + const ids = ['id1', 'id2', 'id3', 'id4']; + + beforeEach(() => { + spyOn(notificationsService, 'success').and.callThrough(); + spyOn(notificationsService, 'error').and.callThrough(); + }); + + it('should display a success message if the removal of at least one mapping was successful', () => { + comp.removeMappings(ids); + expect(notificationsService.success).toHaveBeenCalled(); + expect(notificationsService.error).not.toHaveBeenCalled(); + }); + + it('should display an error message if the removal of at least one mapping was unsuccessful', () => { + spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(Observable.of(new RestResponse(false, '404'))); + comp.removeMappings(ids); + expect(notificationsService.success).not.toHaveBeenCalled(); + expect(notificationsService.error).toHaveBeenCalled(); + }); + }); + +}); 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 ce82c99c8a..03d42596ab 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 @@ -59,7 +59,6 @@ export class ItemCollectionMapperComponent implements OnInit { constructor(private route: ActivatedRoute, private router: Router, private searchConfigService: SearchConfigurationService, - private searchService: SearchService, private collectionDataService: CollectionDataService, private notificationsService: NotificationsService, private itemDataService: ItemDataService, From fc967004759c2460de6ba2d5f76de0cf0d2076a2 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 11 Oct 2018 11:16:47 +0200 Subject: [PATCH 031/142] 55946: Fixed search query not working --- .../item-collection-mapper.component.html | 1 - .../item-collection-mapper.component.spec.ts | 14 +++++++------- .../item-collection-mapper.component.ts | 14 ++++++++++---- src/app/core/data/data.service.ts | 4 ++-- 4 files changed, 19 insertions(+), 14 deletions(-) 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 f02a62b769..c1989acf22 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 @@ -9,7 +9,6 @@
diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts index 1eb91e30b1..fcd9a18d49 100644 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts @@ -33,16 +33,16 @@ import { CollectionDataService } from '../../../core/data/collection-data.servic import { ObjectSelectService } from '../../../shared/object-select/object-select.service'; import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service-stub'; -fdescribe('ItemCollectionMapperComponent', () => { +describe('ItemCollectionMapperComponent', () => { let comp: ItemCollectionMapperComponent; let fixture: ComponentFixture; let route: ActivatedRoute; let router: Router; let searchConfigService: SearchConfigurationService; + let searchService: SearchService; let notificationsService: NotificationsService; let itemDataService: ItemDataService; - let collectionDataService: CollectionDataService; const mockItem: Item = Object.assign(new Item(), { id: '932c7d50-d85a-44cb-b9dc-b427b12877bd', @@ -69,9 +69,9 @@ fdescribe('ItemCollectionMapperComponent', () => { removeMappingFromCollection: () => Observable.of(new RestResponse(true, '200')), getMappedCollections: () => Observable.of(mockCollectionsRD) }; - const collectionDataServiceStub = { - findAll: () => Observable.of(mockCollectionsRD) - }; + const searchServiceStub = Object.assign(new SearchServiceStub(), { + search: () => Observable.of(mockCollectionsRD) + }); const activatedRouteStub = new ActivatedRouteStub({}, { item: mockItemRD }); const translateServiceStub = { get: () => Observable.of('test-message of item ' + mockItem.name), @@ -90,7 +90,7 @@ fdescribe('ItemCollectionMapperComponent', () => { { provide: SearchConfigurationService, useValue: searchConfigServiceStub }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: ItemDataService, useValue: itemDataServiceStub }, - { provide: CollectionDataService, useValue: collectionDataServiceStub }, + { provide: SearchService, useValue: searchServiceStub }, { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, { provide: TranslateService, useValue: translateServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } @@ -107,7 +107,7 @@ fdescribe('ItemCollectionMapperComponent', () => { searchConfigService = (comp as any).searchConfigService; notificationsService = (comp as any).notificationsService; itemDataService = (comp as any).itemDataService; - collectionDataService = (comp as any).collectionDataService; + searchService = (comp as any).searchService; }); it('should display the correct collection name', () => { 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 03d42596ab..45755a52c0 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 @@ -8,7 +8,7 @@ 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 { getSucceededRemoteData, toDSpaceObjectListRD } 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'; @@ -19,6 +19,7 @@ import { RestResponse } from '../../../core/cache/response-cache.models'; import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { C } from '@angular/core/src/render3'; +import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; @Component({ selector: 'ds-item-collection-mapper', @@ -59,7 +60,7 @@ export class ItemCollectionMapperComponent implements OnInit { constructor(private route: ActivatedRoute, private router: Router, private searchConfigService: SearchConfigurationService, - private collectionDataService: CollectionDataService, + private searchService: SearchService, private notificationsService: NotificationsService, private itemDataService: ItemDataService, private translateService: TranslateService) { @@ -82,8 +83,13 @@ export class ItemCollectionMapperComponent implements OnInit { switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id)) ); this.mappingCollectionsRD$ = this.searchOptions$.pipe( - switchMap((searchOptions: PaginatedSearchOptions) => this.collectionDataService.findAll(searchOptions)) - ); + switchMap((searchOptions: PaginatedSearchOptions) => { + return this.searchService.search(Object.assign(searchOptions, { + dsoType: DSpaceObjectType.COLLECTION + })); + }), + toDSpaceObjectListRD() + ) as Observable>>; } /** diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 3948c3672f..3c22ccfad9 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,4 +1,4 @@ -import { filter, take } from 'rxjs/operators'; +import { filter, take, tap } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; @@ -58,7 +58,7 @@ export abstract class DataService hrefObs.pipe( filter((href: string) => hasValue(href)), - take(1)) + take(1), tap((value) => console.log(value))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); this.requestService.configure(request); From 3d9e4a66ff8b18ef31684fba5a6273ad5729c9f8 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 6 Nov 2018 16:18:25 +0100 Subject: [PATCH 032/142] 55693: Mapped items using new REST endpoint + item-select component --- resources/i18n/en.json | 1 + .../collection-item-mapper.component.html | 12 ++-- .../collection-item-mapper.component.ts | 20 +++++-- src/app/core/core.module.ts | 2 + src/app/core/data/collection-data.service.ts | 59 +++++++++++++++++++ .../mapping-items-response-parsing.service.ts | 44 ++++++++++++++ .../item-select/item-select.component.html | 4 +- .../item-select/item-select.component.ts | 3 + .../object-collection.component.ts | 1 + 9 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 src/app/core/data/mapping-items-response-parsing.service.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 86ff55da66..0c6ce010d8 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -19,6 +19,7 @@ "collection": "Collection: \"{{name}}\"", "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", + "remove": "Remove selected item mappings", "tabs": { "browse": "Browse", "map": "Map" 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 a7a1416cb0..2a83a4bdd6 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 @@ -19,12 +19,12 @@
- - +
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 15f7db63b4..24f5b07ae5 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 @@ -17,6 +17,8 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { ItemDataService } from '../../core/data/item-data.service'; import { RestResponse } from '../../core/cache/response-cache.models'; import { TranslateService } from '@ngx-translate/core'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { Item } from '../../core/shared/item.model'; @Component({ selector: 'ds-collection-item-mapper', @@ -67,6 +69,7 @@ export class CollectionItemMapperComponent implements OnInit { private searchService: SearchService, private notificationsService: NotificationsService, private itemDataService: ItemDataService, + private collectionDataService: CollectionDataService, private translateService: TranslateService) { } @@ -88,13 +91,10 @@ export class CollectionItemMapperComponent implements OnInit { ); this.collectionItemsRD$ = collectionAndOptions$.pipe( switchMap(([collectionRD, options]) => { - return this.searchService.search(Object.assign(options, { - scope: collectionRD.payload.id, - dsoType: DSpaceObjectType.ITEM, + return this.collectionDataService.getMappedItems(collectionRD.payload.id, Object.assign(options, { sort: this.defaultSortOptions - })); - }), - toDSpaceObjectListRD() + })) + }) ); this.mappingItemsRD$ = this.searchOptions$.pipe( 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) * @param event diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 31b9b31244..73c55a3df4 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 { MappingItemsResponseParsingService } from './data/mapping-items-response-parsing.service'; const IMPORTS = [ CommonModule, @@ -130,6 +131,7 @@ const PROVIDERS = [ UUIDService, DSpaceObjectDataService, ItemSelectService, + MappingItemsResponseParsingService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 7d1e463dbe..103fdee163 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -11,6 +11,26 @@ import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; import { RequestService } from './request.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() export class CollectionDataService extends ComColDataService { @@ -27,4 +47,43 @@ export class CollectionDataService extends ComColDataService { + 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>> { + 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 { + 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) => new PaginatedList(response.pageInfo, response.payload)) + ); + + return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + } + } diff --git a/src/app/core/data/mapping-items-response-parsing.service.ts b/src/app/core/data/mapping-items-response-parsing.service.ts new file mode 100644 index 0000000000..5d3c39dece --- /dev/null +++ b/src/app/core/data/mapping-items-response-parsing.service.ts @@ -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 } + ) + ); + } + } + +} diff --git a/src/app/shared/item-select/item-select.component.html b/src/app/shared/item-select/item-select.component.html index 9c08cfae87..8caefb096c 100644 --- a/src/app/shared/item-select/item-select.component.html +++ b/src/app/shared/item-select/item-select.component.html @@ -10,7 +10,7 @@ - {{'item.select.table.collection' | translate}} + {{'item.select.table.collection' | translate}} {{'item.select.table.author' | translate}} {{'item.select.table.title' | translate}} @@ -18,7 +18,7 @@ - {{(item.owningCollection | async)?.payload?.name}} + {{(item.owningCollection | async)?.payload?.name}} {{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}} {{item.findMetadata("dc.title")}} diff --git a/src/app/shared/item-select/item-select.component.ts b/src/app/shared/item-select/item-select.component.ts index 3a2003a327..68949c2baf 100644 --- a/src/app/shared/item-select/item-select.component.ts +++ b/src/app/shared/item-select/item-select.component.ts @@ -38,6 +38,9 @@ export class ItemSelectComponent implements OnInit { @Input() confirmButton = 'item.select.confirm'; + @Input() + hideCollection = false; + /** * EventEmitter to return the selected UUIDs when the confirm button is pressed * @type {EventEmitter} diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index b4a436c5de..fc4a4e9768 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -75,6 +75,7 @@ export class ObjectCollectionComponent implements OnChanges, OnInit { this.currentMode = params.view; } }); + console.log(this.objects); } /** From dd3691349627997bc06b2430b9abb76abda62408 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 6 Nov 2018 16:38:00 +0100 Subject: [PATCH 033/142] 55693: Functionality for removing mappings --- resources/i18n/en.json | 24 ++++++++++---- .../collection-item-mapper.component.html | 2 +- .../collection-item-mapper.component.ts | 31 +++++++++---------- src/app/core/data/item-data.service.ts | 14 ++++++++- 4 files changed, 47 insertions(+), 24 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 0c6ce010d8..3a70a3b6d6 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -25,13 +25,25 @@ "map": "Map" }, "notifications": { - "success": { - "head": "Mapping completed", - "content": "Successfully mapped {{amount}} items." + "map": { + "success": { + "head": "Mapping completed", + "content": "Successfully mapped {{amount}} items." + }, + "error": { + "head": "Mapping errors", + "content": "Errors occurred for mapping of {{amount}} items." + } }, - "error": { - "head": "Mapping errors", - "content": "Errors occurred for mapping of {{amount}} items." + "unmap": { + "success": { + "head": "Remove mapping completed", + "content": "Successfully removed the mappings of {{amount}} items." + }, + "error": { + "head": "Remove mapping errors", + "content": "Errors occurred for removing the mappings of {{amount}} items." + } } }, "return": "Return" 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 2a83a4bdd6..2408f2540a 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 @@ -24,7 +24,7 @@ [paginationOptions]="(searchOptions$ | async)?.pagination" [confirmButton]="'collection.item-mapper.remove'" [hideCollection]="true" - (confirm)="unmapItems($event)"> + (confirm)="mapItems($event, 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 24f5b07ae5..531b48d258 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 @@ -109,23 +109,30 @@ export class CollectionItemMapperComponent implements OnInit { } /** - * Map the selected items to the collection and display notifications - * @param {string[]} ids The list of item UUID's to map to the collection + * Map/Unmap the selected items to the collection and display notifications + * @param ids The list of item UUID's to map/unmap to the collection + * @param remove Whether or not it's supposed to remove mappings */ - mapItems(ids: string[]) { + mapItems(ids: string[], remove?: boolean) { const responses$ = this.collectionRD$.pipe( getSucceededRemoteData(), map((collectionRD: RemoteData) => collectionRD.payload.id), - switchMap((collectionId: string) => Observable.combineLatest(ids.map((id: string) => this.itemDataService.mapToCollection(id, collectionId)))) + switchMap((collectionId: string) => + Observable.combineLatest(ids.map((id: string) => + remove ? this.itemDataService.removeMappingFromCollection(id, collectionId) : this.itemDataService.mapToCollection(id, collectionId) + )) + ) ); + const messageInsertion = remove ? 'unmap' : 'map'; + responses$.subscribe((responses: RestResponse[]) => { const successful = responses.filter((response: RestResponse) => response.isSuccessful); const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); if (successful.length > 0) { const successMessages = Observable.combineLatest( - this.translateService.get('collection.item-mapper.notifications.success.head'), - this.translateService.get('collection.item-mapper.notifications.success.content', { amount: successful.length }) + this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.head`), + this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.content`, { amount: successful.length }) ); successMessages.subscribe(([head, content]) => { @@ -134,8 +141,8 @@ export class CollectionItemMapperComponent implements OnInit { } if (unsuccessful.length > 0) { const unsuccessMessages = Observable.combineLatest( - this.translateService.get('collection.item-mapper.notifications.error.head'), - this.translateService.get('collection.item-mapper.notifications.error.content', { amount: unsuccessful.length }) + this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.head`), + this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.content`, { amount: unsuccessful.length }) ); unsuccessMessages.subscribe(([head, content]) => { @@ -145,14 +152,6 @@ 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) * @param event diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 7c2c4e572d..2a863dc6a7 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -15,7 +15,7 @@ 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 { DeleteRequest, FindAllOptions, PostRequest, RestRequest } from './request.models'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { configureRequest, getResponseFromSelflink } from '../shared/operators'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; @@ -71,4 +71,16 @@ export class ItemDataService extends DataService { ); } + public removeMappingFromCollection(itemId: string, collectionId: string): Observable { + return this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( + isNotEmptyOperator(), + distinctUntilChanged(), + map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)), + configureRequest(this.requestService), + map((request: RestRequest) => request.href), + getResponseFromSelflink(this.responseCache), + map((responseCacheEntry: ResponseCacheEntry) => responseCacheEntry.response) + ); + } + } From 9902209fb94fe5e6cb6758e1696635459eb1e8db Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 7 Nov 2018 13:09:00 +0100 Subject: [PATCH 034/142] 55946: Small import fix --- .../collection-item-mapper.component.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index d7ae41f023..06d108d9a4 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -7,7 +7,6 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { SearchFormComponent } from '../../shared/search-form/search-form.component'; import { SearchPageModule } from '../../+search-page/search-page.module'; import { ObjectCollectionComponent } from '../../shared/object-collection/object-collection.component'; -import { ItemSelectComponent } from '../../shared/item-select/item-select.component'; import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; import { RouterStub } from '../../shared/testing/router-stub'; From 0d89eb6cce2a366964e486c9b446b48f2448c816 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 7 Nov 2018 14:39:49 +0100 Subject: [PATCH 035/142] 55693: TODO location query --- .../collection-item-mapper.component.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 531b48d258..88c5579f02 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 @@ -96,9 +96,11 @@ export class CollectionItemMapperComponent implements OnInit { })) }) ); - this.mappingItemsRD$ = this.searchOptions$.pipe( - flatMap((options: PaginatedSearchOptions) => { + this.mappingItemsRD$ = collectionAndOptions$.pipe( + switchMap(([collectionRD, options]) => { return this.searchService.search(Object.assign(options, { + // TODO: Exclude items already mapped to collection without overwriting search query + // query: `-location.coll:\"${collectionRD.payload.id}\"`, scope: undefined, dsoType: DSpaceObjectType.ITEM, sort: this.defaultSortOptions From 7bd8fae72a7b547197ce91b5bca0b2211ebba571 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 13 Nov 2018 15:31:32 +0100 Subject: [PATCH 036/142] 55693: Fixed getMappedItems to use object cache instead of response --- src/app/core/core.module.ts | 2 - src/app/core/data/collection-data.service.ts | 30 +++---------- .../mapping-items-response-parsing.service.ts | 44 ------------------- 3 files changed, 7 insertions(+), 69 deletions(-) delete mode 100644 src/app/core/data/mapping-items-response-parsing.service.ts diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 73c55a3df4..31b9b31244 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -65,7 +65,6 @@ 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 { MappingItemsResponseParsingService } from './data/mapping-items-response-parsing.service'; const IMPORTS = [ CommonModule, @@ -131,7 +130,6 @@ const PROVIDERS = [ UUIDService, DSpaceObjectDataService, ItemSelectService, - MappingItemsResponseParsingService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 103fdee163..b4648097c3 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -1,6 +1,5 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; -import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedCollection } from '../cache/models/normalized-collection.model'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -14,23 +13,17 @@ 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 { hasValue, isNotEmptyOperator } from '../../shared/empty.util'; +import { GetRequest } from './request.models'; import { - configureRequest, - filterSuccessfulResponses, - getRequestFromSelflink, - getResponseFromSelflink + configureRequest } 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'; +import { DSOResponseParsingService } from './dso-response-parsing.service'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -67,23 +60,14 @@ export class CollectionDataService extends ComColDataService { - return MappingItemsResponseParsingService; + return DSOResponseParsingService; } }); }), 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) => new PaginatedList(response.pageInfo, response.payload)) - ); - - return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + return this.rdbService.buildList(href$); } } diff --git a/src/app/core/data/mapping-items-response-parsing.service.ts b/src/app/core/data/mapping-items-response-parsing.service.ts deleted file mode 100644 index 5d3c39dece..0000000000 --- a/src/app/core/data/mapping-items-response-parsing.service.ts +++ /dev/null @@ -1,44 +0,0 @@ -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 } - ) - ); - } - } - -} From 469b424cfe87d7359239327b17de2e69ba21f9f4 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 13 Nov 2018 17:50:02 +0100 Subject: [PATCH 037/142] 55693: Map tab excludes already mapped items --- .../collection-item-mapper.component.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) 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 88c5579f02..704ec61ee1 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 @@ -19,6 +19,7 @@ import { RestResponse } from '../../core/cache/response-cache.models'; import { TranslateService } from '@ngx-translate/core'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { Item } from '../../core/shared/item.model'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-collection-item-mapper', @@ -98,9 +99,8 @@ export class CollectionItemMapperComponent implements OnInit { ); this.mappingItemsRD$ = collectionAndOptions$.pipe( switchMap(([collectionRD, options]) => { - return this.searchService.search(Object.assign(options, { - // TODO: Exclude items already mapped to collection without overwriting search query - // query: `-location.coll:\"${collectionRD.payload.id}\"`, + return this.searchService.search(Object.assign(new PaginatedSearchOptions(options), { + query: this.buildQuery(collectionRD.payload.id, options.query), scope: undefined, dsoType: DSpaceObjectType.ITEM, sort: this.defaultSortOptions @@ -175,4 +175,13 @@ export class CollectionItemMapperComponent implements OnInit { return this.router.url; } + buildQuery(collectionId: string, query: string): string { + const excludeColQuery = `-location.coll:\"${collectionId}\"`; + if (isNotEmpty(query)) { + return `${excludeColQuery} AND ${query}`; + } else { + return excludeColQuery; + } + } + } From e61467bb7f7cc1cc3c6a968dc521143dba19b148 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 14 Nov 2018 10:37:27 +0100 Subject: [PATCH 038/142] 55693: JSDocs and Test fixes --- .../collection-item-mapper.component.spec.ts | 19 +++++++++++++++---- .../collection-item-mapper.component.ts | 5 +++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index d7ae41f023..6aeefcd8c4 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -32,6 +32,11 @@ import { By } from '@angular/platform-browser'; import { RestResponse } from '../../core/cache/response-cache.models'; import { PaginatedList } from '../../core/data/paginated-list'; import { PageInfo } from '../../core/shared/page-info.model'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { ItemSelectService } from '../../shared/item-select/item-select.service'; +import { PaginationComponent } from '../../shared/pagination/pagination.component'; +import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe'; +import { ItemSelectServiceStub } from '../../shared/testing/item-select-service-stub'; describe('CollectionItemMapperComponent', () => { let comp: CollectionItemMapperComponent; @@ -74,14 +79,18 @@ describe('CollectionItemMapperComponent', () => { onTranslationChange: new EventEmitter(), onDefaultLangChange: new EventEmitter() }; + const emptyList = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [])); const searchServiceStub = Object.assign(new SearchServiceStub(), { - search: () => Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))) + search: () => Observable.of(emptyList) }); + const collectionDataServiceStub = { + getMappedItems: () => Observable.of(emptyList) + }; beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [CommonModule, FormsModule, SharedModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], - declarations: [CollectionItemMapperComponent], + imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [CollectionItemMapperComponent, ItemSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: Router, useValue: routerStub }, @@ -89,8 +98,10 @@ describe('CollectionItemMapperComponent', () => { { provide: SearchService, useValue: searchServiceStub }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: ItemDataService, useValue: itemDataServiceStub }, + { provide: CollectionDataService, useValue: collectionDataServiceStub }, { provide: TranslateService, useValue: translateServiceStub }, - { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } + { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, + { provide: ItemSelectService, useValue: new ItemSelectServiceStub() } ] }).compileComponents(); })); 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 704ec61ee1..1bd39f4cc8 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 @@ -175,6 +175,11 @@ export class CollectionItemMapperComponent implements OnInit { return this.router.url; } + /** + * Build a query where items that are already mapped to a collection are excluded from + * @param collectionId The collection's UUID + * @param query The query to add to it + */ buildQuery(collectionId: string, query: string): string { const excludeColQuery = `-location.coll:\"${collectionId}\"`; if (isNotEmpty(query)) { From b11a168e72ce04cc3a9f4f975a059d1b151aa2b2 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 14 Nov 2018 10:54:47 +0100 Subject: [PATCH 039/142] 55946: CollectionItemMapper test fixes after merge --- .../collection-item-mapper.component.spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index 0e5738094a..0007a96195 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -24,7 +24,7 @@ import { Observable } from 'rxjs/Observable'; import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { EventEmitter } from '@angular/core'; +import { EventEmitter, NgModule } from '@angular/core'; import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub'; import { By } from '@angular/platform-browser'; @@ -32,10 +32,12 @@ import { RestResponse } from '../../core/cache/response-cache.models'; import { PaginatedList } from '../../core/data/paginated-list'; import { PageInfo } from '../../core/shared/page-info.model'; import { CollectionDataService } from '../../core/data/collection-data.service'; -import { ItemSelectService } from '../../shared/item-select/item-select.service'; import { PaginationComponent } from '../../shared/pagination/pagination.component'; import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe'; -import { ItemSelectServiceStub } from '../../shared/testing/item-select-service-stub'; +import { ItemSelectComponent } from '../../shared/object-select/item-select/item-select.component'; +import { ObjectSelectService } from '../../shared/object-select/object-select.service'; +import { ObjectSelectServiceStub } from '../../shared/testing/object-select-service-stub'; +import { VarDirective } from '../../shared/utils/var.directive'; describe('CollectionItemMapperComponent', () => { let comp: CollectionItemMapperComponent; @@ -89,7 +91,7 @@ describe('CollectionItemMapperComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], - declarations: [CollectionItemMapperComponent, ItemSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe], + declarations: [CollectionItemMapperComponent, ItemSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe, VarDirective], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: Router, useValue: routerStub }, @@ -100,7 +102,7 @@ describe('CollectionItemMapperComponent', () => { { provide: CollectionDataService, useValue: collectionDataServiceStub }, { provide: TranslateService, useValue: translateServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: ItemSelectService, useValue: new ItemSelectServiceStub() } + { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() } ] }).compileComponents(); })); From 08063154e74ffa71b91c0835714f1a670a75dc8a Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 15 Nov 2018 14:13:16 +0100 Subject: [PATCH 040/142] 55946: object-select store lists intermediate commit --- .../object-select/object-select.actions.ts | 9 ++- .../object-select/object-select.reducer.ts | 65 +++++++++++++------ .../object-select/object-select.service.ts | 57 +++++++++------- 3 files changed, 84 insertions(+), 47 deletions(-) diff --git a/src/app/shared/object-select/object-select.actions.ts b/src/app/shared/object-select/object-select.actions.ts index 4adaeb9fed..f6c3e035fa 100644 --- a/src/app/shared/object-select/object-select.actions.ts +++ b/src/app/shared/object-select/object-select.actions.ts @@ -11,6 +11,11 @@ export const ObjectSelectionActionTypes = { }; export class ObjectSelectionAction implements Action { + /** + * Key of the list (of selections) for which the action should be performed + */ + key: string; + /** * UUID of the object a select action can be performed on */ @@ -23,9 +28,11 @@ export class ObjectSelectionAction implements Action { /** * Initialize with the object's UUID + * @param {string} key of the list * @param {string} id of the object */ - constructor(id: string) { + constructor(key: string, id: string) { + this.key = key; this.id = id; } } diff --git a/src/app/shared/object-select/object-select.reducer.ts b/src/app/shared/object-select/object-select.reducer.ts index bd54e43a35..a023ce4a9e 100644 --- a/src/app/shared/object-select/object-select.reducer.ts +++ b/src/app/shared/object-select/object-select.reducer.ts @@ -2,36 +2,45 @@ import { isEmpty } from '../empty.util'; import { ObjectSelectionAction, ObjectSelectionActionTypes } from './object-select.actions'; /** - * Interface that represents the state for a single filters + * Interface that represents the state for a single selection of an object */ export interface ObjectSelectionState { checked: boolean; } /** - * Interface that represents the state for all available filters + * Interface that represents the state for all selected items within a certain category defined by a key */ export interface ObjectSelectionsState { [id: string]: ObjectSelectionState } -const initialState: ObjectSelectionsState = Object.create(null); +/** + * Interface that represents the state for all selected items + */ +export interface ObjectSelectionListState { + [key: string]: ObjectSelectionsState +} + +const initialState: ObjectSelectionListState = Object.create(null); /** - * Performs a search filter action on the current state - * @param {SearchFiltersState} state The state before the action is performed - * @param {SearchFilterAction} action The action that should be performed - * @returns {SearchFiltersState} The state after the action is performed + * Performs a selection action on the current state + * @param {ObjectSelectionListState} state The state before the action is performed + * @param {ObjectSelectionAction} action The action that should be performed + * @returns {ObjectSelectionListState} The state after the action is performed */ -export function objectSelectionReducer(state = initialState, action: ObjectSelectionAction): ObjectSelectionsState { +export function objectSelectionReducer(state = initialState, action: ObjectSelectionAction): ObjectSelectionListState { switch (action.type) { case ObjectSelectionActionTypes.INITIAL_SELECT: { - if (isEmpty(state) || isEmpty(state[action.id])) { + if (isEmpty(state) || isEmpty(state[action.key]) || isEmpty(state[action.key][action.id])) { return Object.assign({}, state, { - [action.id]: { - checked: true + [action.key]: { + [action.id]: { + checked: true + } } }); } @@ -39,10 +48,12 @@ export function objectSelectionReducer(state = initialState, action: ObjectSelec } case ObjectSelectionActionTypes.INITIAL_DESELECT: { - if (isEmpty(state) || isEmpty(state[action.id])) { + if (isEmpty(state) || isEmpty(state[action.key]) || isEmpty(state[action.key][action.id])) { return Object.assign({}, state, { - [action.id]: { - checked: false + [action.key]: { + [action.id]: { + checked: false + } } }); } @@ -51,30 +62,42 @@ export function objectSelectionReducer(state = initialState, action: ObjectSelec case ObjectSelectionActionTypes.SELECT: { return Object.assign({}, state, { - [action.id]: { - checked: true + [action.key]: { + [action.id]: { + checked: true + } } }); } case ObjectSelectionActionTypes.DESELECT: { return Object.assign({}, state, { - [action.id]: { - checked: false + [action.key]: { + [action.id]: { + checked: false + } } }); } case ObjectSelectionActionTypes.SWITCH: { return Object.assign({}, state, { - [action.id]: { - checked: (isEmpty(state) || isEmpty(state[action.id])) ? true : !state[action.id].checked + [action.key]: { + [action.id]: { + checked: (isEmpty(state) || isEmpty(state[action.key]) || isEmpty(state[action.key][action.id])) ? true : !state[action.key][action.id].checked + } } }); } case ObjectSelectionActionTypes.RESET: { - return {}; + if (isEmpty(action.key)) { + return {}; + } else { + return Object.assign({}, state, { + [action.key]: {} + }); + } } default: { diff --git a/src/app/shared/object-select/object-select.service.ts b/src/app/shared/object-select/object-select.service.ts index adc394d4e1..91772d2db4 100644 --- a/src/app/shared/object-select/object-select.service.ts +++ b/src/app/shared/object-select/object-select.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; -import { ObjectSelectionsState, ObjectSelectionState } from './object-select.reducer'; +import { ObjectSelectionListState, ObjectSelectionsState, ObjectSelectionState } from './object-select.reducer'; import { ObjectSelectionDeselectAction, ObjectSelectionInitialDeselectAction, @@ -13,7 +13,8 @@ import { map } from 'rxjs/operators'; import { AppState } from '../../app.reducer'; const selectionStateSelector = (state: ObjectSelectionsState) => state.objectSelection; -const objectSelectionsStateSelector = (state: AppState) => state.objectSelection; +const objectSelectionsStateSelector = (state: ObjectSelectionListState) => state.objectSelection; +const objectSelectionListStateSelector = (state: AppState) => state.objectSelection; /** * Service that takes care of selecting and deselecting objects @@ -28,11 +29,12 @@ export class ObjectSelectService { } /** - * Request the current selection of a given object + * Request the current selection of a given object in a given list + * @param {string} key The key of the list where the selection resides in * @param {string} id The UUID of the object * @returns {Observable} Emits the current selection state of the given object, if it's unavailable, return false */ - getSelected(id: string): Observable { + getSelected(key: string, id: string): Observable { return this.store.select(selectionByIdSelector(id)).pipe( map((object: ObjectSelectionState) => { if (object) { @@ -45,9 +47,8 @@ export class ObjectSelectService { } /** - * Request the current selection of a given object - * @param {string} id The UUID of the object - * @returns {Observable} Emits the current selection state of the given object, if it's unavailable, return false + * Request the current selection of all objects + * @returns {Observable} Emits the current selection state of all objects */ getAllSelected(): Observable { return this.appStore.select(objectSelectionsStateSelector).pipe( @@ -56,50 +57,56 @@ export class ObjectSelectService { } /** - * Dispatches an initial select action to the store for a given object + * Dispatches an initial select action to the store for a given object in a given list + * @param {string} key The key of the list to select the object in * @param {string} id The UUID of the object to select */ - public initialSelect(id: string): void { - this.store.dispatch(new ObjectSelectionInitialSelectAction(id)); + public initialSelect(key: string, id: string): void { + this.store.dispatch(new ObjectSelectionInitialSelectAction(key, id)); } /** - * Dispatches an initial deselect action to the store for a given object + * Dispatches an initial deselect action to the store for a given object in a given list + * @param {string} key The key of the list to deselect the object in * @param {string} id The UUID of the object to deselect */ - public initialDeselect(id: string): void { - this.store.dispatch(new ObjectSelectionInitialDeselectAction(id)); + public initialDeselect(key: string, id: string): void { + this.store.dispatch(new ObjectSelectionInitialDeselectAction(key, id)); } /** - * Dispatches a select action to the store for a given object + * Dispatches a select action to the store for a given object in a given list + * @param {string} key The key of the list to select the object in * @param {string} id The UUID of the object to select */ - public select(id: string): void { - this.store.dispatch(new ObjectSelectionSelectAction(id)); + public select(key: string, id: string): void { + this.store.dispatch(new ObjectSelectionSelectAction(key, id)); } /** - * Dispatches a deselect action to the store for a given object + * Dispatches a deselect action to the store for a given object in a given list + * @param {string} key The key of the list to deselect the object in * @param {string} id The UUID of the object to deselect */ - public deselect(id: string): void { - this.store.dispatch(new ObjectSelectionDeselectAction(id)); + public deselect(key: string, id: string): void { + this.store.dispatch(new ObjectSelectionDeselectAction(key, id)); } /** - * Dispatches a switch action to the store for a given object + * Dispatches a switch action to the store for a given object in a given list + * @param {string} key The key of the list to select the object in * @param {string} id The UUID of the object to select */ - public switch(id: string): void { - this.store.dispatch(new ObjectSelectionSwitchAction(id)); + public switch(key: string, id: string): void { + this.store.dispatch(new ObjectSelectionSwitchAction(key, id)); } /** - * Dispatches a reset action to the store for all objects + * Dispatches a reset action to the store for all objects (in a list) + * @param {string} key The key of the list to clear all selections for */ - public reset(): void { - this.store.dispatch(new ObjectSelectionResetAction(null)); + public reset(key?: string): void { + this.store.dispatch(new ObjectSelectionResetAction(key, null)); } } From 8d396f383283312efcec2ba5057689efbdce7cee Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 16 Nov 2018 13:55:00 +0100 Subject: [PATCH 041/142] 55946: Multi-list object select support --- .../collection-item-mapper.component.html | 4 ++- .../item-collection-mapper.component.html | 2 ++ src/app/app.reducer.ts | 8 ++++-- .../object-select/object-select.reducer.ts | 20 +++++++-------- .../object-select/object-select.service.ts | 25 +++++++++---------- .../object-select/object-select.component.ts | 13 ++++++---- 6 files changed, 41 insertions(+), 31 deletions(-) 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 20da923cc3..b455ecfe3f 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 @@ -15,11 +15,12 @@ - +
= { diff --git a/src/app/shared/object-select/object-select.reducer.ts b/src/app/shared/object-select/object-select.reducer.ts index a023ce4a9e..617e0242e0 100644 --- a/src/app/shared/object-select/object-select.reducer.ts +++ b/src/app/shared/object-select/object-select.reducer.ts @@ -37,11 +37,11 @@ export function objectSelectionReducer(state = initialState, action: ObjectSelec case ObjectSelectionActionTypes.INITIAL_SELECT: { if (isEmpty(state) || isEmpty(state[action.key]) || isEmpty(state[action.key][action.id])) { return Object.assign({}, state, { - [action.key]: { + [action.key]: Object.assign({}, state[action.key], { [action.id]: { checked: true } - } + }) }); } return state; @@ -50,11 +50,11 @@ export function objectSelectionReducer(state = initialState, action: ObjectSelec case ObjectSelectionActionTypes.INITIAL_DESELECT: { if (isEmpty(state) || isEmpty(state[action.key]) || isEmpty(state[action.key][action.id])) { return Object.assign({}, state, { - [action.key]: { + [action.key]: Object.assign({}, state[action.key], { [action.id]: { checked: false } - } + }) }); } return state; @@ -62,31 +62,31 @@ export function objectSelectionReducer(state = initialState, action: ObjectSelec case ObjectSelectionActionTypes.SELECT: { return Object.assign({}, state, { - [action.key]: { + [action.key]: Object.assign({}, state[action.key], { [action.id]: { checked: true } - } + }) }); } case ObjectSelectionActionTypes.DESELECT: { return Object.assign({}, state, { - [action.key]: { + [action.key]: Object.assign({}, state[action.key], { [action.id]: { checked: false } - } + }) }); } case ObjectSelectionActionTypes.SWITCH: { return Object.assign({}, state, { - [action.key]: { + [action.key]: Object.assign({}, state[action.key], { [action.id]: { checked: (isEmpty(state) || isEmpty(state[action.key]) || isEmpty(state[action.key][action.id])) ? true : !state[action.key][action.id].checked } - } + }) }); } diff --git a/src/app/shared/object-select/object-select.service.ts b/src/app/shared/object-select/object-select.service.ts index 91772d2db4..2dbb69fe53 100644 --- a/src/app/shared/object-select/object-select.service.ts +++ b/src/app/shared/object-select/object-select.service.ts @@ -12,7 +12,6 @@ import { hasValue } from '../empty.util'; import { map } from 'rxjs/operators'; import { AppState } from '../../app.reducer'; -const selectionStateSelector = (state: ObjectSelectionsState) => state.objectSelection; const objectSelectionsStateSelector = (state: ObjectSelectionListState) => state.objectSelection; const objectSelectionListStateSelector = (state: AppState) => state.objectSelection; @@ -23,7 +22,7 @@ const objectSelectionListStateSelector = (state: AppState) => state.objectSelect export class ObjectSelectService { constructor( - private store: Store, + private store: Store, private appStore: Store ) { } @@ -35,7 +34,7 @@ export class ObjectSelectService { * @returns {Observable} Emits the current selection state of the given object, if it's unavailable, return false */ getSelected(key: string, id: string): Observable { - return this.store.select(selectionByIdSelector(id)).pipe( + return this.store.select(selectionByKeyAndIdSelector(key, id)).pipe( map((object: ObjectSelectionState) => { if (object) { return object.checked; @@ -47,12 +46,12 @@ export class ObjectSelectService { } /** - * Request the current selection of all objects + * Request the current selection of all objects within a specific list * @returns {Observable} Emits the current selection state of all objects */ - getAllSelected(): Observable { - return this.appStore.select(objectSelectionsStateSelector).pipe( - map((state: ObjectSelectionsState) => Object.keys(state).filter((key) => state[key].checked)) + getAllSelected(key: string): Observable { + return this.appStore.select(objectSelectionListStateSelector).pipe( + map((state: ObjectSelectionListState) => Object.keys(state[key]).filter((id) => state[key][id].checked)) ); } @@ -111,14 +110,14 @@ export class ObjectSelectService { } -function selectionByIdSelector(id: string): MemoizedSelector { - return keySelector(id); +function selectionByKeyAndIdSelector(key: string, id: string): MemoizedSelector { + return keyAndIdSelector(key, id); } -export function keySelector(key: string): MemoizedSelector { - return createSelector(selectionStateSelector, (state: ObjectSelectionState) => { - if (hasValue(state)) { - return state[key]; +export function keyAndIdSelector(key: string, id: string): MemoizedSelector { + return createSelector(objectSelectionsStateSelector, (state: ObjectSelectionsState) => { + if (hasValue(state) && hasValue(state[key])) { + return state[key][id]; } else { return undefined; } diff --git a/src/app/shared/object-select/object-select/object-select.component.ts b/src/app/shared/object-select/object-select/object-select.component.ts index 58953d3abb..d99330a15b 100644 --- a/src/app/shared/object-select/object-select/object-select.component.ts +++ b/src/app/shared/object-select/object-select/object-select.component.ts @@ -11,6 +11,9 @@ import { ObjectSelectService } from '../object-select.service'; */ export abstract class ObjectSelectComponent implements OnInit, OnDestroy { + @Input() + key: string; + /** * The list of DSpaceObjects to display */ @@ -49,11 +52,11 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro } ngOnInit(): void { - this.selectedIds$ = this.objectSelectService.getAllSelected(); + this.selectedIds$ = this.objectSelectService.getAllSelected(this.key); } ngOnDestroy(): void { - this.objectSelectService.reset(); + this.objectSelectService.reset(this.key); } /** @@ -61,7 +64,7 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro * @param {string} id */ switch(id: string) { - this.objectSelectService.switch(id); + this.objectSelectService.switch(this.key, id); } /** @@ -70,7 +73,7 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro * @returns {Observable} */ getSelected(id: string): Observable { - return this.objectSelectService.getSelected(id); + return this.objectSelectService.getSelected(this.key, id); } /** @@ -82,7 +85,7 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro take(1) ).subscribe((ids: string[]) => { this.confirm.emit(ids); - this.objectSelectService.reset(); + this.objectSelectService.reset(this.key); }); } From 904ee2cab4d9a39672dadb4295d328a2f739e199 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 16 Nov 2018 14:21:48 +0100 Subject: [PATCH 042/142] 55693: Search box inside Map tab --- .../collection-item-mapper.component.html | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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 2408f2540a..977c2c543d 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 @@ -5,16 +5,6 @@

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

-
-
- - -
-
- @@ -30,7 +20,17 @@ -
+
+
+ + +
+
+ +
Date: Fri, 16 Nov 2018 14:32:13 +0100 Subject: [PATCH 043/142] 55946: Search box inside Map tab on item-level --- .../item-collection-mapper.component.html | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 92cdc71a61..fab7a16d79 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 @@ -5,15 +5,6 @@

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

-
-
- - -
-
- @@ -29,7 +20,16 @@ -
+
+
+ + +
+
+ +
Date: Fri, 16 Nov 2018 15:38:49 +0100 Subject: [PATCH 044/142] 55946: Exclude already mapped collections from Map tab --- .../item-collection-mapper.component.ts | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) 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 45755a52c0..3ab0d8b516 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 @@ -20,6 +20,7 @@ import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { C } from '@angular/core/src/render3'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; +import { isNotEmpty } from '../../../shared/empty.util'; @Component({ selector: 'ds-item-collection-mapper', @@ -82,9 +83,15 @@ export class ItemCollectionMapperComponent implements OnInit { map((itemRD: RemoteData) => itemRD.payload), switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id)) ); - this.mappingCollectionsRD$ = this.searchOptions$.pipe( - switchMap((searchOptions: PaginatedSearchOptions) => { - return this.searchService.search(Object.assign(searchOptions, { + + const itemCollectionsAndOptions$ = Observable.combineLatest( + this.itemCollectionsRD$, + this.searchOptions$ + ); + this.mappingCollectionsRD$ = itemCollectionsAndOptions$.pipe( + switchMap(([itemCollectionsRD, searchOptions]) => { + return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), { + query: this.buildQuery(itemCollectionsRD.payload.page, searchOptions.query), dsoType: DSpaceObjectType.COLLECTION })); }), @@ -196,4 +203,31 @@ export class ItemCollectionMapperComponent implements OnInit { return this.router.url; } + /** + * Build a query to exclude collections from + * @param collections The collections their UUIDs + * @param query The query to add to it + */ + buildQuery(collections: Collection[], query: string): string { + let result = query; + for (const collection of collections) { + result = this.addExcludeCollection(collection.id, result); + } + return result; + } + + /** + * Add an exclusion of a collection to a query + * @param collectionId The collection's UUID + * @param query The query to add the exclusion to + */ + addExcludeCollection(collectionId: string, query: string): string { + const excludeQuery = `-search.resourceid:${collectionId}`; + if (isNotEmpty(query)) { + return `${query} AND ${excludeQuery}`; + } else { + return excludeQuery; + } + } + } From aa172b6c680f6104d32f2cc6c0605f54834806b5 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 23 Nov 2018 17:03:35 +0100 Subject: [PATCH 045/142] 55946: Changes to comply to the new response cache features --- .../collection-item-mapper.component.ts | 22 +++++++++--------- .../edit-item-page.component.ts | 5 ++-- .../item-collection-mapper.component.ts | 23 ++++++++----------- src/app/core/data/collection-data.service.ts | 2 +- ...ing-collections-reponse-parsing.service.ts | 2 +- .../item-select/item-select.component.ts | 7 +----- .../object-select/object-select.service.ts | 2 +- .../object-select/object-select.component.ts | 4 ++-- .../testing/object-select-service-stub.ts | 9 ++++---- 9 files changed, 35 insertions(+), 41 deletions(-) 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 1bd39f4cc8..9490e22c6c 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 @@ -1,13 +1,14 @@ +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; + import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { fadeIn, fadeInOut } from '../../shared/animations/fade'; -import { ActivatedRoute, PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; -import { Observable } from 'rxjs/Observable'; import { Collection } from '../../core/shared/collection.model'; import { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service'; import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; import { PaginatedList } from '../../core/data/paginated-list'; -import { flatMap, map, switchMap } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; import { SearchService } from '../../+search-page/search-service/search.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; @@ -15,11 +16,10 @@ import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ItemDataService } from '../../core/data/item-data.service'; -import { RestResponse } from '../../core/cache/response-cache.models'; import { TranslateService } from '@ngx-translate/core'; import { CollectionDataService } from '../../core/data/collection-data.service'; -import { Item } from '../../core/shared/item.model'; -import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { isNotEmpty } from '../../shared/empty.util'; +import { RestResponse } from '../../core/cache/response.models'; @Component({ selector: 'ds-collection-item-mapper', @@ -75,7 +75,7 @@ export class CollectionItemMapperComponent implements OnInit { } ngOnInit(): void { - this.collectionRD$ = this.route.data.map((data) => data.collection).pipe(getSucceededRemoteData()) as Observable>; + this.collectionRD$ = this.route.data.pipe(map((data) => data.collection)).pipe(getSucceededRemoteData()) as Observable>; this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; this.loadItemLists(); } @@ -86,7 +86,7 @@ export class CollectionItemMapperComponent implements OnInit { * TODO: When the API support it, fetch items excluding the collection's scope (currently fetches all items) */ loadItemLists() { - const collectionAndOptions$ = Observable.combineLatest( + const collectionAndOptions$ = observableCombineLatest( this.collectionRD$, this.searchOptions$ ); @@ -120,7 +120,7 @@ export class CollectionItemMapperComponent implements OnInit { getSucceededRemoteData(), map((collectionRD: RemoteData) => collectionRD.payload.id), switchMap((collectionId: string) => - Observable.combineLatest(ids.map((id: string) => + observableCombineLatest(ids.map((id: string) => remove ? this.itemDataService.removeMappingFromCollection(id, collectionId) : this.itemDataService.mapToCollection(id, collectionId) )) ) @@ -132,7 +132,7 @@ export class CollectionItemMapperComponent implements OnInit { const successful = responses.filter((response: RestResponse) => response.isSuccessful); const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); if (successful.length > 0) { - const successMessages = Observable.combineLatest( + 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 }) ); @@ -142,7 +142,7 @@ export class CollectionItemMapperComponent implements OnInit { }); } if (unsuccessful.length > 0) { - const unsuccessMessages = Observable.combineLatest( + 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 }) ); diff --git a/src/app/+item-page/edit-item-page/edit-item-page.component.ts b/src/app/+item-page/edit-item-page/edit-item-page.component.ts index 8bcf53f140..d276a15005 100644 --- a/src/app/+item-page/edit-item-page/edit-item-page.component.ts +++ b/src/app/+item-page/edit-item-page/edit-item-page.component.ts @@ -1,9 +1,10 @@ import { fadeIn, fadeInOut } from '../../shared/animations/fade'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; +import { map } from 'rxjs/operators'; @Component({ selector: 'ds-edit-item-page', @@ -28,7 +29,7 @@ export class EditItemPageComponent implements OnInit { } ngOnInit(): void { - this.itemRD$ = this.route.data.map((data) => data.item); + this.itemRD$ = this.route.data.pipe(map((data) => data.item)); } } 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 3ab0d8b516..633cb7037b 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,9 +1,8 @@ +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; + 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'; @@ -13,14 +12,12 @@ 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'; -import { RestResponse } from '../../../core/cache/response-cache.models'; import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; -import { C } from '@angular/core/src/render3'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; import { isNotEmpty } from '../../../shared/empty.util'; +import { RestResponse } from '../../../core/cache/response.models'; @Component({ selector: 'ds-item-collection-mapper', @@ -68,7 +65,7 @@ export class ItemCollectionMapperComponent implements OnInit { } ngOnInit(): void { - this.itemRD$ = this.route.data.map((data) => data.item).pipe(getSucceededRemoteData()) as Observable>; + this.itemRD$ = this.route.data.pipe(map((data) => data.item)).pipe(getSucceededRemoteData()) as Observable>; this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; this.loadCollectionLists(); } @@ -84,7 +81,7 @@ export class ItemCollectionMapperComponent implements OnInit { switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id)) ); - const itemCollectionsAndOptions$ = Observable.combineLatest( + const itemCollectionsAndOptions$ = observableCombineLatest( this.itemCollectionsRD$, this.searchOptions$ ); @@ -104,7 +101,7 @@ export class ItemCollectionMapperComponent implements OnInit { * @param {string[]} ids The list of collection UUID's to map the item to */ mapCollections(ids: string[]) { - const itemIdAndExcludingIds$ = Observable.combineLatest( + const itemIdAndExcludingIds$ = observableCombineLatest( this.itemRD$.pipe( getSucceededRemoteData(), map((rd: RemoteData) => rd.payload), @@ -119,7 +116,7 @@ export class ItemCollectionMapperComponent implements OnInit { // Map the item to the collections found in ids, excluding the collections the item is already mapped to const responses$ = itemIdAndExcludingIds$.pipe( - switchMap(([itemId, excludingIds]) => Observable.combineLatest(this.filterIds(ids, excludingIds).map((id: string) => this.itemDataService.mapToCollection(itemId, id)))) + switchMap(([itemId, excludingIds]) => observableCombineLatest(this.filterIds(ids, excludingIds).map((id: string) => this.itemDataService.mapToCollection(itemId, id)))) ); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add'); @@ -134,7 +131,7 @@ export class ItemCollectionMapperComponent implements OnInit { const responses$ = this.itemRD$.pipe( getSucceededRemoteData(), map((itemRD: RemoteData) => itemRD.payload.id), - switchMap((itemId: string) => Observable.combineLatest(ids.map((id: string) => this.itemDataService.removeMappingFromCollection(itemId, id)))) + switchMap((itemId: string) => observableCombineLatest(ids.map((id: string) => this.itemDataService.removeMappingFromCollection(itemId, id)))) ); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove'); @@ -160,7 +157,7 @@ export class ItemCollectionMapperComponent implements OnInit { const successful = responses.filter((response: RestResponse) => response.isSuccessful); const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); if (successful.length > 0) { - const successMessages = Observable.combineLatest( + const successMessages = observableCombineLatest( this.translateService.get(`${messagePrefix}.success.head`), this.translateService.get(`${messagePrefix}.success.content`, { amount: successful.length }) ); @@ -170,7 +167,7 @@ export class ItemCollectionMapperComponent implements OnInit { }); } if (unsuccessful.length > 0) { - const unsuccessMessages = Observable.combineLatest( + const unsuccessMessages = observableCombineLatest( this.translateService.get(`${messagePrefix}.error.head`), this.translateService.get(`${messagePrefix}.error.content`, { amount: unsuccessful.length }) ); diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index d0479a9da6..59a6c8ca01 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -9,7 +9,7 @@ import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; import { distinctUntilChanged, map } from 'rxjs/operators'; diff --git a/src/app/core/data/mapping-collections-reponse-parsing.service.ts b/src/app/core/data/mapping-collections-reponse-parsing.service.ts index 0ae014301c..31200be3fb 100644 --- a/src/app/core/data/mapping-collections-reponse-parsing.service.ts +++ b/src/app/core/data/mapping-collections-reponse-parsing.service.ts @@ -2,9 +2,9 @@ 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'; import { PaginatedList } from './paginated-list'; import { PageInfo } from '../shared/page-info.model'; +import { ErrorResponse, GenericSuccessResponse, RestResponse } from '../cache/response.models'; @Injectable() export class MappingCollectionsReponseParsingService implements ResponseParsingService { diff --git a/src/app/shared/object-select/item-select/item-select.component.ts b/src/app/shared/object-select/item-select/item-select.component.ts index d8d4eef34a..2cd5b502df 100644 --- a/src/app/shared/object-select/item-select/item-select.component.ts +++ b/src/app/shared/object-select/item-select/item-select.component.ts @@ -1,10 +1,5 @@ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; -import { take } from 'rxjs/operators'; -import { Observable } from 'rxjs/Observable'; +import { Component} from '@angular/core'; import { Item } from '../../../core/shared/item.model'; -import { RemoteData } from '../../../core/data/remote-data'; -import { PaginatedList } from '../../../core/data/paginated-list'; -import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; import { ObjectSelectService } from '../object-select.service'; import { ObjectSelectComponent } from '../object-select/object-select.component'; import { isNotEmpty } from '../../empty.util'; diff --git a/src/app/shared/object-select/object-select.service.ts b/src/app/shared/object-select/object-select.service.ts index 2dbb69fe53..03ddc0078c 100644 --- a/src/app/shared/object-select/object-select.service.ts +++ b/src/app/shared/object-select/object-select.service.ts @@ -7,7 +7,7 @@ import { ObjectSelectionInitialSelectAction, ObjectSelectionResetAction, ObjectSelectionSelectAction, ObjectSelectionSwitchAction } from './object-select.actions'; -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; import { hasValue } from '../empty.util'; import { map } from 'rxjs/operators'; import { AppState } from '../../app.reducer'; diff --git a/src/app/shared/object-select/object-select/object-select.component.ts b/src/app/shared/object-select/object-select/object-select.component.ts index d99330a15b..d41cb771a6 100644 --- a/src/app/shared/object-select/object-select/object-select.component.ts +++ b/src/app/shared/object-select/object-select/object-select.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { take } from 'rxjs/operators'; -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; diff --git a/src/app/shared/testing/object-select-service-stub.ts b/src/app/shared/testing/object-select-service-stub.ts index f4bcccae77..7b3ee38752 100644 --- a/src/app/shared/testing/object-select-service-stub.ts +++ b/src/app/shared/testing/object-select-service-stub.ts @@ -1,4 +1,5 @@ -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; +import { of } from 'rxjs/internal/observable/of'; export class ObjectSelectServiceStub { @@ -12,14 +13,14 @@ export class ObjectSelectServiceStub { getSelected(id: string): Observable { if (this.ids.indexOf(id) > -1) { - return Observable.of(true); + return of(true); } else { - return Observable.of(false); + return of(false); } } getAllSelected(): Observable { - return Observable.of(this.ids); + return of(this.ids); } switch(id: string) { From e275fe590be583b5c038956f735eee793a83b43c Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 23 Nov 2018 17:58:14 +0100 Subject: [PATCH 046/142] 55946: Remove href-to-uuid index cache on mapping --- .../collection-item-mapper.component.ts | 6 ++++- src/app/core/data/collection-data.service.ts | 17 ++++++++---- src/app/core/index/index.actions.ts | 27 ++++++++++++++++++- src/app/core/index/index.reducer.ts | 20 +++++++++++++- 4 files changed, 62 insertions(+), 8 deletions(-) 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 9490e22c6c..9199e010b8 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 @@ -8,7 +8,7 @@ import { Collection } from '../../core/shared/collection.model'; import { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service'; import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; import { PaginatedList } from '../../core/data/paginated-list'; -import { map, switchMap } from 'rxjs/operators'; +import { map, switchMap, take } from 'rxjs/operators'; import { getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; import { SearchService } from '../../+search-page/search-service/search.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; @@ -152,6 +152,10 @@ export class CollectionItemMapperComponent implements OnInit { }); } }); + + this.collectionRD$.pipe(take(1)).subscribe((collectionRD: RemoteData) => { + this.collectionDataService.clearMappingItemsRequests(collectionRD.payload.id); + }); } /** diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 59a6c8ca01..6733681e0f 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -12,17 +12,17 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Observable } from 'rxjs'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; -import { distinctUntilChanged, map } from 'rxjs/operators'; +import { distinctUntilChanged, map, take } from 'rxjs/operators'; import { hasValue, isNotEmptyOperator } from '../../shared/empty.util'; import { GetRequest } from './request.models'; -import { - configureRequest -} from '../shared/operators'; +import { configureRequest } from '../shared/operators'; import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; import { GenericConstructor } from '../shared/generic-constructor'; import { ResponseParsingService } from './parsing.service'; import { DSpaceObject } from '../shared/dspace-object.model'; import { DSOResponseParsingService } from './dso-response-parsing.service'; +import { IndexName, IndexState } from '../index/index.reducer'; +import { RemoveFromIndexBySubstringAction } from '../index/index.actions'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -34,7 +34,8 @@ export class CollectionDataService extends ComColDataService, protected cds: CommunityDataService, protected halService: HALEndpointService, - protected objectCache: ObjectCacheService + protected objectCache: ObjectCacheService, + protected indexStore: Store ) { super(); } @@ -68,4 +69,10 @@ export class CollectionDataService extends ComColDataService { + this.indexStore.dispatch(new RemoveFromIndexBySubstringAction(IndexName.REQUEST, href)); + }); + } + } diff --git a/src/app/core/index/index.actions.ts b/src/app/core/index/index.actions.ts index 014b6561a3..98d07d59d5 100644 --- a/src/app/core/index/index.actions.ts +++ b/src/app/core/index/index.actions.ts @@ -8,7 +8,8 @@ import { IndexName } from './index.reducer'; */ export const IndexActionTypes = { ADD: type('dspace/core/index/ADD'), - REMOVE_BY_VALUE: type('dspace/core/index/REMOVE_BY_VALUE') + REMOVE_BY_VALUE: type('dspace/core/index/REMOVE_BY_VALUE'), + REMOVE_BY_SUBSTRING: type('dspace/core/index/REMOVE_BY_SUBSTRING') }; /* tslint:disable:max-classes-per-file */ @@ -60,6 +61,30 @@ export class RemoveFromIndexByValueAction implements Action { this.payload = { name, value }; } +} + +/** + * An ngrx action to remove multiple values from the index by substring + */ +export class RemoveFromIndexBySubstringAction implements Action { + type = IndexActionTypes.REMOVE_BY_SUBSTRING; + payload: { + name: IndexName, + value: string + }; + + /** + * Create a new RemoveFromIndexByValueAction + * + * @param name + * the name of the index to remove from + * @param value + * the value to remove the UUID for + */ + constructor(name: IndexName, value: string) { + this.payload = { name, value }; + } + } /* tslint:enable:max-classes-per-file */ diff --git a/src/app/core/index/index.reducer.ts b/src/app/core/index/index.reducer.ts index c179182509..dae7874794 100644 --- a/src/app/core/index/index.reducer.ts +++ b/src/app/core/index/index.reducer.ts @@ -2,7 +2,8 @@ import { IndexAction, IndexActionTypes, AddToIndexAction, - RemoveFromIndexByValueAction + RemoveFromIndexByValueAction, + RemoveFromIndexBySubstringAction } from './index.actions'; export enum IndexName { @@ -31,6 +32,10 @@ export function indexReducer(state = initialState, action: IndexAction): IndexSt return removeFromIndexByValue(state, action as RemoveFromIndexByValueAction) } + case IndexActionTypes.REMOVE_BY_SUBSTRING: { + return removeFromIndexBySubstring(state, action as RemoveFromIndexBySubstringAction) + } + default: { return state; } @@ -60,3 +65,16 @@ function removeFromIndexByValue(state: IndexState, action: RemoveFromIndexByValu [action.payload.name]: newSubState }); } + +function removeFromIndexBySubstring(state: IndexState, action: RemoveFromIndexByValueAction): IndexState { + const subState = state[action.payload.name]; + const newSubState = Object.create(null); + for (const value in subState) { + if (value.indexOf(action.payload.value) < 0) { + newSubState[value] = subState[value]; + } + } + return Object.assign({}, state, { + [action.payload.name]: newSubState + }); +} From 4307cb0ff18e66f8936b52f8e4129dca86b55950 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 26 Nov 2018 13:18:17 +0100 Subject: [PATCH 047/142] 55946: Clear data/request attached to the hrefs deleted from index --- src/app/core/data/collection-data.service.ts | 9 ++-- src/app/core/data/request.actions.ts | 24 +++++++++- src/app/core/data/request.reducer.ts | 16 ++++++- src/app/core/data/request.service.ts | 49 ++++++++++++++++++-- 4 files changed, 88 insertions(+), 10 deletions(-) diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 6733681e0f..bc28ccc98c 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core'; -import { Store } from '@ngrx/store'; +import { MemoizedSelector, select, Store } from '@ngrx/store'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedCollection } from '../cache/models/normalized-collection.model'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { CoreState } from '../core.reducers'; +import { coreSelector, CoreState } from '../core.reducers'; import { Collection } from '../shared/collection.model'; import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; @@ -12,7 +12,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Observable } from 'rxjs'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; -import { distinctUntilChanged, map, take } from 'rxjs/operators'; +import { distinctUntilChanged, map, mergeMap, switchMap, take } from 'rxjs/operators'; import { hasValue, isNotEmptyOperator } from '../../shared/empty.util'; import { GetRequest } from './request.models'; import { configureRequest } from '../shared/operators'; @@ -23,6 +23,8 @@ import { DSpaceObject } from '../shared/dspace-object.model'; import { DSOResponseParsingService } from './dso-response-parsing.service'; import { IndexName, IndexState } from '../index/index.reducer'; import { RemoveFromIndexBySubstringAction } from '../index/index.actions'; +import { pathSelector } from '../shared/selectors'; +import { RequestState } from './request.reducer'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -71,6 +73,7 @@ export class CollectionDataService extends ComColDataService { + this.requestService.removeByHrefSubstring(href); this.indexStore.dispatch(new RemoveFromIndexBySubstringAction(IndexName.REQUEST, href)); }); } diff --git a/src/app/core/data/request.actions.ts b/src/app/core/data/request.actions.ts index 28149c2ead..2b2de13504 100644 --- a/src/app/core/data/request.actions.ts +++ b/src/app/core/data/request.actions.ts @@ -10,7 +10,8 @@ export const RequestActionTypes = { CONFIGURE: type('dspace/core/data/request/CONFIGURE'), EXECUTE: type('dspace/core/data/request/EXECUTE'), COMPLETE: type('dspace/core/data/request/COMPLETE'), - RESET_TIMESTAMPS: type('dspace/core/data/request/RESET_TIMESTAMPS') + RESET_TIMESTAMPS: type('dspace/core/data/request/RESET_TIMESTAMPS'), + REMOVE: type('dspace/core/data/request/REMOVE') }; /* tslint:disable:max-classes-per-file */ @@ -82,6 +83,24 @@ export class ResetResponseTimestampsAction implements Action { } } +/** + * An ngrx action to remove a cached request + */ +export class RequestRemoveAction implements Action { + type = RequestActionTypes.REMOVE; + uuid: string; + + /** + * Create a new RequestRemoveAction + * + * @param uuid + * the request's uuid + */ + constructor(uuid: string) { + this.uuid = uuid + } +} + /* tslint:enable:max-classes-per-file */ /** @@ -91,4 +110,5 @@ export type RequestAction = RequestConfigureAction | RequestExecuteAction | RequestCompleteAction - | ResetResponseTimestampsAction; + | ResetResponseTimestampsAction + | RequestRemoveAction; diff --git a/src/app/core/data/request.reducer.ts b/src/app/core/data/request.reducer.ts index a680de2d6b..322ac46727 100644 --- a/src/app/core/data/request.reducer.ts +++ b/src/app/core/data/request.reducer.ts @@ -1,6 +1,6 @@ import { RequestActionTypes, RequestAction, RequestConfigureAction, - RequestExecuteAction, RequestCompleteAction, ResetResponseTimestampsAction + RequestExecuteAction, RequestCompleteAction, ResetResponseTimestampsAction, RequestRemoveAction } from './request.actions'; import { RestRequest } from './request.models'; import { RestResponse } from '../cache/response.models'; @@ -38,6 +38,10 @@ export function requestReducer(state = initialState, action: RequestAction): Req return resetResponseTimestamps(state, action as ResetResponseTimestampsAction); } + case RequestActionTypes.REMOVE: { + return removeRequest(state, action as RequestRemoveAction); + } + default: { return state; } @@ -95,3 +99,13 @@ function resetResponseTimestamps(state: RequestState, action: ResetResponseTimes }); return newState; } + +function removeRequest(state: RequestState, action: RequestRemoveAction): RequestState { + const newState = Object.create(null); + for (const value in state) { + if (value !== action.uuid) { + newState[value] = state[value]; + } + } + return newState; +} diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 285ed06545..a447df3051 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -15,16 +15,16 @@ import { import { race as observableRace } from 'rxjs'; import { Injectable } from '@angular/core'; -import { MemoizedSelector, select, Store } from '@ngrx/store'; -import { hasNoValue, hasValue, isNotUndefined } from '../../shared/empty.util'; +import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; +import { hasNoValue, hasValue, isNotEmpty, isNotUndefined } from '../../shared/empty.util'; import { CacheableObject } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; import { DSOSuccessResponse, RestResponse } from '../cache/response.models'; import { coreSelector, CoreState } from '../core.reducers'; -import { IndexName } from '../index/index.reducer'; +import { IndexName, IndexState } from '../index/index.reducer'; import { pathSelector } from '../shared/selectors'; import { UUIDService } from '../shared/uuid.service'; -import { RequestConfigureAction, RequestExecuteAction } from './request.actions'; +import { RequestConfigureAction, RequestExecuteAction, RequestRemoveAction } from './request.actions'; import { GetRequest, RestRequest } from './request.models'; import { RequestEntry } from './request.reducer'; @@ -54,6 +54,25 @@ export class RequestService { return pathSelector(coreSelector, 'index', IndexName.UUID_MAPPING, uuid); } + private uuidsFromHrefSubstringSelector(selector: MemoizedSelector, name: string, href: string): MemoizedSelector { + return createSelector(selector, (state: IndexState) => this.getUuidsFromHrefSubstring(state, name, href)); + } + + private getUuidsFromHrefSubstring(state: IndexState, name: string, href: string): string[] { + let result = []; + if (isNotEmpty(state)) { + const subState = state[name]; + if (isNotEmpty(subState)) { + for (const value in subState) { + if (value.indexOf(href) > -1) { + result = [...result, subState[value]]; + } + } + } + } + return result; + } + generateRequestId(): string { return `client/${this.uuidService.generate()}`; } @@ -119,6 +138,28 @@ export class RequestService { } } + removeByHref(href: string) { + this.store.pipe( + select(this.uuidFromHrefSelector(href)) + ).subscribe((uuid: string) => { + this.removeByUuid(uuid); + }); + } + + removeByHrefSubstring(href: string) { + this.store.pipe( + select(this.uuidsFromHrefSubstringSelector(pathSelector(coreSelector, 'index'), IndexName.REQUEST, href)) + ).subscribe((uuids: string[]) => { + for (const uuid of uuids) { + this.removeByUuid(uuid); + } + }); + } + + removeByUuid(uuid: string) { + this.store.dispatch(new RequestRemoveAction(uuid)); + } + /** * Check if a request is in the cache or if it's still pending * @param {GetRequest} request The request to check From 55e37a63bc91c95daeaa03959be99301df7d6b3c Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 27 Nov 2018 14:20:24 +0100 Subject: [PATCH 048/142] 55946: discovery requests reset and reload and list updates --- .../collection-item-mapper.component.ts | 40 +++++++++++++------ .../search-service/search.service.ts | 11 ++++- .../builders/remote-data-build.service.ts | 6 +-- src/app/core/data/collection-data.service.ts | 16 ++++---- src/app/core/data/item-data.service.ts | 7 ++-- src/app/core/data/request.service.ts | 18 ++++----- .../object-collection.component.ts | 1 - 7 files changed, 59 insertions(+), 40 deletions(-) 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 9199e010b8..8058398884 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 @@ -8,7 +8,7 @@ import { Collection } from '../../core/shared/collection.model'; import { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service'; import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; import { PaginatedList } from '../../core/data/paginated-list'; -import { map, switchMap, take } from 'rxjs/operators'; +import { map, switchMap, take, tap } from 'rxjs/operators'; import { getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; import { SearchService } from '../../+search-page/search-service/search.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; @@ -20,6 +20,10 @@ import { TranslateService } from '@ngx-translate/core'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { isNotEmpty } from '../../shared/empty.util'; import { RestResponse } from '../../core/cache/response.models'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { Actions, ofType } from '@ngrx/effects'; +import { IndexActionTypes } from '../../core/index/index.actions'; +import { RequestActionTypes } from '../../core/data/request.actions'; @Component({ selector: 'ds-collection-item-mapper', @@ -64,6 +68,8 @@ export class CollectionItemMapperComponent implements OnInit { */ defaultSortOptions: SortOptions = new SortOptions('dc.title', SortDirection.ASC); + shouldUpdate$: BehaviorSubject; + constructor(private route: ActivatedRoute, private router: Router, private searchConfigService: SearchConfigurationService, @@ -86,25 +92,31 @@ export class CollectionItemMapperComponent implements OnInit { * TODO: When the API support it, fetch items excluding the collection's scope (currently fetches all items) */ loadItemLists() { + this.shouldUpdate$ = new BehaviorSubject(true); const collectionAndOptions$ = observableCombineLatest( this.collectionRD$, - this.searchOptions$ + this.searchOptions$, + this.shouldUpdate$ ); this.collectionItemsRD$ = collectionAndOptions$.pipe( - switchMap(([collectionRD, options]) => { - return this.collectionDataService.getMappedItems(collectionRD.payload.id, Object.assign(options, { - sort: this.defaultSortOptions - })) + switchMap(([collectionRD, options, shouldUpdate]) => { + if (shouldUpdate) { + return this.collectionDataService.getMappedItems(collectionRD.payload.id, Object.assign(options, { + sort: this.defaultSortOptions + })) + } }) ); this.mappingItemsRD$ = collectionAndOptions$.pipe( - switchMap(([collectionRD, options]) => { - return this.searchService.search(Object.assign(new PaginatedSearchOptions(options), { - query: this.buildQuery(collectionRD.payload.id, options.query), - scope: undefined, - dsoType: DSpaceObjectType.ITEM, - sort: this.defaultSortOptions - })); + switchMap(([collectionRD, options, shouldUpdate]) => { + if (shouldUpdate) { + return this.searchService.search(Object.assign(new PaginatedSearchOptions(options), { + query: this.buildQuery(collectionRD.payload.id, options.query), + scope: undefined, + dsoType: DSpaceObjectType.ITEM, + sort: this.defaultSortOptions + })); + } }), toDSpaceObjectListRD() ); @@ -151,10 +163,12 @@ export class CollectionItemMapperComponent implements OnInit { this.notificationsService.error(head, content); }); } + this.shouldUpdate$.next(true); }); this.collectionRD$.pipe(take(1)).subscribe((collectionRD: RemoteData) => { this.collectionDataService.clearMappingItemsRequests(collectionRD.payload.id); + this.searchService.clearDiscoveryRequests(); }); } diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 275b0b3340..049e4b1542 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -7,7 +7,7 @@ import { Router, UrlSegmentGroup } from '@angular/router'; -import { map, switchMap, tap } from 'rxjs/operators'; +import { map, switchMap, take, tap } from 'rxjs/operators'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; import { FacetConfigSuccessResponse, @@ -47,6 +47,9 @@ import { CommunityDataService } from '../../core/data/community-data.service'; import { ViewMode } from '../../core/shared/view-mode.model'; import { ResourceType } from '../../core/shared/resource-type'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; +import { Store } from '@ngrx/store'; +import { IndexName, IndexState } from '../../core/index/index.reducer'; +import { RemoveFromIndexBySubstringAction } from '../../core/index/index.actions'; /** * Service that performs all general actions that have to do with the search page @@ -319,6 +322,12 @@ export class SearchService implements OnDestroy { return '/' + g.toString(); } + 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/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 52ec4382ae..c22b63f618 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -6,7 +6,7 @@ import { } from 'rxjs'; import { Injectable } from '@angular/core'; import { distinctUntilChanged, first, flatMap, map, startWith, switchMap } from 'rxjs/operators'; -import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../../shared/empty.util'; +import { hasValue, hasValueOperator, isEmpty, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util'; import { PaginatedList } from '../../data/paginated-list'; import { RemoteData } from '../../data/remote-data'; import { RemoteDataError } from '../../data/remote-data-error'; @@ -86,8 +86,8 @@ export class RemoteDataBuildService { toRemoteDataObservable(requestEntry$: Observable, payload$: Observable) { return observableCombineLatest(requestEntry$, payload$).pipe( map(([reqEntry, payload]) => { - const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true; - const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false; + const requestPending = hasValue(reqEntry) && hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true; + const responsePending = hasValue(reqEntry) && hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false; let isSuccessful: boolean; let error: RemoteDataError; if (hasValue(reqEntry) && hasValue(reqEntry.response)) { diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index bc28ccc98c..e9ae5d38a5 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core'; -import { MemoizedSelector, select, Store } from '@ngrx/store'; +import { Action, Store } from '@ngrx/store'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedCollection } from '../cache/models/normalized-collection.model'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { coreSelector, CoreState } from '../core.reducers'; +import { CoreState } from '../core.reducers'; import { Collection } from '../shared/collection.model'; import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; @@ -12,7 +12,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Observable } from 'rxjs'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; -import { distinctUntilChanged, map, mergeMap, switchMap, take } from 'rxjs/operators'; +import { distinctUntilChanged, map, take, tap } from 'rxjs/operators'; import { hasValue, isNotEmptyOperator } from '../../shared/empty.util'; import { GetRequest } from './request.models'; import { configureRequest } from '../shared/operators'; @@ -22,9 +22,8 @@ import { ResponseParsingService } from './parsing.service'; import { DSpaceObject } from '../shared/dspace-object.model'; import { DSOResponseParsingService } from './dso-response-parsing.service'; import { IndexName, IndexState } from '../index/index.reducer'; -import { RemoveFromIndexBySubstringAction } from '../index/index.actions'; -import { pathSelector } from '../shared/selectors'; -import { RequestState } from './request.reducer'; +import { IndexActionTypes, RemoveFromIndexBySubstringAction } from '../index/index.actions'; +import { Actions, ofType } from '@ngrx/effects'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -50,6 +49,8 @@ export class CollectionDataService extends ComColDataService>> { + const requestUuid = this.requestService.generateRequestId(); + const href$ = this.getMappingItemsEndpoint(collectionId).pipe( isNotEmptyOperator(), distinctUntilChanged(), @@ -58,7 +59,7 @@ export class CollectionDataService extends ComColDataService { - const request = new GetRequest(this.requestService.generateRequestId(), endpoint); + const request = new GetRequest(requestUuid, endpoint); return Object.assign(request, { getResponseParser(): GenericConstructor { return DSOResponseParsingService; @@ -74,7 +75,6 @@ export class CollectionDataService extends ComColDataService { this.requestService.removeByHrefSubstring(href); - this.indexStore.dispatch(new RemoveFromIndexBySubstringAction(IndexName.REQUEST, href)); }); } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 15dc01d03a..c1a46cde9a 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -1,5 +1,5 @@ -import { distinctUntilChanged, map, filter, switchMap } from 'rxjs/operators'; +import { distinctUntilChanged, map, filter, switchMap, tap } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -21,6 +21,7 @@ import { configureRequest, filterSuccessfulResponses, getResponseFromEntry } fro import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; import { Collection } from '../shared/collection.model'; +import { RequestEntry } from './request.reducer'; @Injectable() export class ItemDataService extends DataService { @@ -66,7 +67,7 @@ export class ItemDataService extends DataService { distinctUntilChanged(), map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)), configureRequest(this.requestService), - switchMap((request: RestRequest) => this.requestService.getByHref(request.href)), + switchMap((request: RestRequest) => this.requestService.getByUUID(request.uuid)), getResponseFromEntry() ); } @@ -77,7 +78,7 @@ export class ItemDataService extends DataService { distinctUntilChanged(), map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)), configureRequest(this.requestService), - switchMap((request: RestRequest) => this.requestService.getByHref(request.href)), + switchMap((request: RestRequest) => this.requestService.getByUUID(request.uuid)), getResponseFromEntry() ); } diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index a447df3051..9e8f28e1b9 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -31,7 +31,7 @@ import { RequestEntry } from './request.reducer'; import { CommitSSBAction } from '../cache/server-sync-buffer.actions'; import { RestRequestMethod } from './rest-request-method'; import { getResponseFromEntry } from '../shared/operators'; -import { AddToIndexAction } from '../index/index.actions'; +import { AddToIndexAction, RemoveFromIndexBySubstringAction } from '../index/index.actions'; @Injectable() export class RequestService { @@ -39,7 +39,8 @@ export class RequestService { constructor(private objectCache: ObjectCacheService, private uuidService: UUIDService, - private store: Store) { + private store: Store, + private indexStore: Store) { } private entryFromUUIDSelector(uuid: string): MemoizedSelector { @@ -138,22 +139,17 @@ export class RequestService { } } - removeByHref(href: string) { - this.store.pipe( - select(this.uuidFromHrefSelector(href)) - ).subscribe((uuid: string) => { - this.removeByUuid(uuid); - }); - } - removeByHrefSubstring(href: string) { this.store.pipe( - select(this.uuidsFromHrefSubstringSelector(pathSelector(coreSelector, 'index'), IndexName.REQUEST, href)) + select(this.uuidsFromHrefSubstringSelector(pathSelector(coreSelector, 'index'), IndexName.REQUEST, href)), + take(1) ).subscribe((uuids: string[]) => { for (const uuid of uuids) { this.removeByUuid(uuid); } }); + this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((reqHref: string) => reqHref.indexOf(href) < 0); + this.indexStore.dispatch(new RemoveFromIndexBySubstringAction(IndexName.REQUEST, href)); } removeByUuid(uuid: string) { diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index 4e46aeaeab..0018c55c7f 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -77,7 +77,6 @@ export class ObjectCollectionComponent implements OnChanges, OnInit { this.currentMode = params.view; } }); - console.log(this.objects); } /** From 2053078a16171ff46f210140ec3932a4ee746747 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 27 Nov 2018 14:54:53 +0100 Subject: [PATCH 049/142] 55946: Dynamic reloading of item mapper on item-level --- .../collection-item-mapper.component.ts | 20 ++++++++++++ .../item-collection-mapper.component.ts | 32 +++++++++++++++++-- .../search-service/search.service.ts | 3 ++ src/app/core/data/collection-data.service.ts | 8 ++--- src/app/core/data/item-data.service.ts | 8 ++++- src/app/core/data/request.service.ts | 9 ++++++ 6 files changed, 70 insertions(+), 10 deletions(-) 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 8058398884..4d30a761e9 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 @@ -68,6 +68,10 @@ export class CollectionItemMapperComponent implements OnInit { */ defaultSortOptions: SortOptions = new SortOptions('dc.title', SortDirection.ASC); + /** + * Firing this observable (shouldUpdate$.next(true)) forces the two lists to reload themselves + * Usually fired after the lists their cache is cleared (to force a new request to the REST API) + */ shouldUpdate$: BehaviorSubject; constructor(private route: ActivatedRoute, @@ -138,6 +142,16 @@ export class CollectionItemMapperComponent implements OnInit { ) ); + this.showNotifications(responses$, remove); + this.clearRequestCache(); + } + + /** + * Display notifications + * @param {Observable} responses$ The responses after adding/removing a mapping + * @param {boolean} remove Whether or not the goal was to remove mappings + */ + private showNotifications(responses$: Observable, remove?: boolean) { const messageInsertion = remove ? 'unmap' : 'map'; responses$.subscribe((responses: RestResponse[]) => { @@ -163,9 +177,15 @@ export class CollectionItemMapperComponent implements OnInit { this.notificationsService.error(head, content); }); } + // Force an update on all lists this.shouldUpdate$.next(true); }); + } + /** + * Clear all previous requests from cache in preparation of refreshing all lists + */ + private clearRequestCache() { this.collectionRD$.pipe(take(1)).subscribe((collectionRD: RemoteData) => { this.collectionDataService.clearMappingItemsRequests(collectionRD.payload.id); this.searchService.clearDiscoveryRequests(); 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 633cb7037b..0b68864bce 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 @@ -11,13 +11,14 @@ import { getSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shar 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 { map, switchMap, take } from 'rxjs/operators'; import { ItemDataService } from '../../../core/data/item-data.service'; import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; import { isNotEmpty } from '../../../shared/empty.util'; import { RestResponse } from '../../../core/cache/response.models'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; @Component({ selector: 'ds-item-collection-mapper', @@ -55,6 +56,12 @@ export class ItemCollectionMapperComponent implements OnInit { */ mappingCollectionsRD$: Observable>>; + /** + * Firing this observable (shouldUpdate$.next(true)) forces the two lists to reload themselves + * Usually fired after the lists their cache is cleared (to force a new request to the REST API) + */ + shouldUpdate$: BehaviorSubject; + constructor(private route: ActivatedRoute, private router: Router, private searchConfigService: SearchConfigurationService, @@ -76,8 +83,13 @@ export class ItemCollectionMapperComponent implements OnInit { * 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), + this.shouldUpdate$ = new BehaviorSubject(true); + this.itemCollectionsRD$ = observableCombineLatest(this.itemRD$, this.shouldUpdate$).pipe( + map(([itemRD, shouldUpdate]) => { + if (shouldUpdate) { + return itemRD.payload + } + }), switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id)) ); @@ -120,6 +132,7 @@ export class ItemCollectionMapperComponent implements OnInit { ); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add'); + this.clearRequestCache(); } /** @@ -135,6 +148,7 @@ export class ItemCollectionMapperComponent implements OnInit { ); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove'); + this.clearRequestCache(); } /** @@ -176,6 +190,18 @@ export class ItemCollectionMapperComponent implements OnInit { this.notificationsService.error(head, content); }); } + // Force an update on all lists + this.shouldUpdate$.next(true); + }); + } + + /** + * 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(); }); } diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 049e4b1542..194adfa22f 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -322,6 +322,9 @@ export class SearchService implements OnDestroy { return '/' + g.toString(); } + /** + * Clear all request cache related to discovery objects + */ clearDiscoveryRequests() { this.halService.getEndpoint(this.searchLinkPath).pipe(take(1)).subscribe((href: string) => { this.requestService.removeByHrefSubstring(href); diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index e9ae5d38a5..3882f8e37c 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Action, Store } from '@ngrx/store'; +import { Store } from '@ngrx/store'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedCollection } from '../cache/models/normalized-collection.model'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -21,9 +21,6 @@ import { GenericConstructor } from '../shared/generic-constructor'; import { ResponseParsingService } from './parsing.service'; import { DSpaceObject } from '../shared/dspace-object.model'; import { DSOResponseParsingService } from './dso-response-parsing.service'; -import { IndexName, IndexState } from '../index/index.reducer'; -import { IndexActionTypes, RemoveFromIndexBySubstringAction } from '../index/index.actions'; -import { Actions, ofType } from '@ngrx/effects'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -35,8 +32,7 @@ export class CollectionDataService extends ComColDataService, protected cds: CommunityDataService, protected halService: HALEndpointService, - protected objectCache: ObjectCacheService, - protected indexStore: Store + protected objectCache: ObjectCacheService ) { super(); } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index c1a46cde9a..df36bad0bf 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -1,5 +1,5 @@ -import { distinctUntilChanged, map, filter, switchMap, tap } from 'rxjs/operators'; +import { distinctUntilChanged, map, filter, switchMap, tap, take } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -102,4 +102,10 @@ export class ItemDataService extends DataService { return this.rdbService.toRemoteDataObservable(requestEntry$, payload$); } + public clearMappedCollectionsRequests(itemId: string) { + this.getMappingCollectionsEndpoint(itemId).pipe(take(1)).subscribe((href: string) => { + this.requestService.removeByHrefSubstring(href); + }); + } + } diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 9e8f28e1b9..922f035139 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -139,6 +139,11 @@ export class RequestService { } } + /** + * Remove all request cache providing (part of) the href + * This also includes href-to-uuid index cache + * @param href A substring of the request(s) href + */ removeByHrefSubstring(href: string) { this.store.pipe( select(this.uuidsFromHrefSubstringSelector(pathSelector(coreSelector, 'index'), IndexName.REQUEST, href)), @@ -152,6 +157,10 @@ export class RequestService { this.indexStore.dispatch(new RemoveFromIndexBySubstringAction(IndexName.REQUEST, href)); } + /** + * Remove request cache using the request's UUID + * @param uuid + */ removeByUuid(uuid: string) { this.store.dispatch(new RequestRemoveAction(uuid)); } From 930af49030878ae19ec4bcce98f593abf12daa78 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 27 Nov 2018 16:45:53 +0100 Subject: [PATCH 050/142] 55946: Fixed tests and added JSDocs --- .../collection-item-mapper.component.spec.ts | 23 ++++++++++----- .../item-collection-mapper.component.spec.ts | 29 +++++++++++-------- src/app/core/data/collection-data.service.ts | 13 +++++++++ src/app/core/data/item-data.service.ts | 24 +++++++++++++++ .../collection-select.component.html | 2 +- 5 files changed, 70 insertions(+), 21 deletions(-) diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index 0007a96195..9d4f6e8f7b 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -20,7 +20,6 @@ import { FormsModule } from '@angular/forms'; import { SharedModule } from '../../shared/shared.module'; import { Collection } from '../../core/shared/collection.model'; import { RemoteData } from '../../core/data/remote-data'; -import { Observable } from 'rxjs/Observable'; import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; @@ -28,7 +27,6 @@ import { EventEmitter, NgModule } from '@angular/core'; import { HostWindowService } from '../../shared/host-window.service'; import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub'; import { By } from '@angular/platform-browser'; -import { RestResponse } from '../../core/cache/response-cache.models'; import { PaginatedList } from '../../core/data/paginated-list'; import { PageInfo } from '../../core/shared/page-info.model'; import { CollectionDataService } from '../../core/data/collection-data.service'; @@ -38,6 +36,9 @@ import { ItemSelectComponent } from '../../shared/object-select/item-select/item import { ObjectSelectService } from '../../shared/object-select/object-select.service'; import { ObjectSelectServiceStub } from '../../shared/testing/object-select-service-stub'; import { VarDirective } from '../../shared/utils/var.directive'; +import { Observable } from 'rxjs/internal/Observable'; +import { of } from 'rxjs/internal/observable/of'; +import { RestResponse } from '../../core/cache/response.models'; describe('CollectionItemMapperComponent', () => { let comp: CollectionItemMapperComponent; @@ -55,7 +56,7 @@ describe('CollectionItemMapperComponent', () => { name: 'test-collection' }); const mockCollectionRD: RemoteData = new RemoteData(false, false, true, null, mockCollection); - const mockSearchOptions = Observable.of(new PaginatedSearchOptions({ + const mockSearchOptions = of(new PaginatedSearchOptions({ pagination: Object.assign(new PaginationComponentOptions(), { id: 'search-page-configuration', pageSize: 10, @@ -71,21 +72,27 @@ describe('CollectionItemMapperComponent', () => { paginatedSearchOptions: mockSearchOptions }; const itemDataServiceStub = { - mapToCollection: () => Observable.of(new RestResponse(true, '200')) + mapToCollection: () => of(new RestResponse(true, '200')) }; const activatedRouteStub = new ActivatedRouteStub({}, { collection: mockCollectionRD }); const translateServiceStub = { - get: () => Observable.of('test-message of collection ' + mockCollection.name), + get: () => of('test-message of collection ' + mockCollection.name), onLangChange: new EventEmitter(), onTranslationChange: new EventEmitter(), onDefaultLangChange: new EventEmitter() }; const emptyList = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [])); const searchServiceStub = Object.assign(new SearchServiceStub(), { - search: () => Observable.of(emptyList) + search: () => of(emptyList), + /* tslint:disable:no-empty */ + clearDiscoveryRequests: () => {} + /* tslint:enable:no-empty */ }); const collectionDataServiceStub = { - getMappedItems: () => Observable.of(emptyList) + getMappedItems: () => of(emptyList), + /* tslint:disable:no-empty */ + clearMappingItemsRequests: () => {} + /* tslint:enable:no-empty */ }; beforeEach(async(() => { @@ -139,7 +146,7 @@ describe('CollectionItemMapperComponent', () => { }); it('should display an error message if at least one mapping was unsuccessful', () => { - spyOn(itemDataService, 'mapToCollection').and.returnValue(Observable.of(new RestResponse(false, '404'))); + spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, '404'))); comp.mapItems(ids); expect(notificationsService.success).not.toHaveBeenCalled(); expect(notificationsService.error).toHaveBeenCalled(); diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts index fcd9a18d49..79bcffe166 100644 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts @@ -8,14 +8,11 @@ import { SearchConfigurationService } from '../../../+search-page/search-service import { SearchService } from '../../../+search-page/search-service/search.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { ItemDataService } from '../../../core/data/item-data.service'; -import { Collection } from '../../../core/shared/collection.model'; import { RemoteData } from '../../../core/data/remote-data'; -import { Observable } from 'rxjs/Observable'; import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { RouterStub } from '../../../shared/testing/router-stub'; -import { RestResponse } from '../../../core/cache/response-cache.models'; import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub'; import { EventEmitter } from '@angular/core'; import { SearchServiceStub } from '../../../shared/testing/search-service-stub'; @@ -29,9 +26,11 @@ import { HostWindowService } from '../../../shared/host-window.service'; import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub'; import { By } from '@angular/platform-browser'; import { Item } from '../../../core/shared/item.model'; -import { CollectionDataService } from '../../../core/data/collection-data.service'; import { ObjectSelectService } from '../../../shared/object-select/object-select.service'; import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service-stub'; +import { Observable } from 'rxjs/internal/Observable'; +import { of } from 'rxjs/internal/observable/of'; +import { RestResponse } from '../../../core/cache/response.models'; describe('ItemCollectionMapperComponent', () => { let comp: ItemCollectionMapperComponent; @@ -49,7 +48,7 @@ describe('ItemCollectionMapperComponent', () => { name: 'test-item' }); const mockItemRD: RemoteData = new RemoteData(false, false, true, null, mockItem); - const mockSearchOptions = Observable.of(new PaginatedSearchOptions({ + const mockSearchOptions = of(new PaginatedSearchOptions({ pagination: Object.assign(new PaginationComponentOptions(), { id: 'search-page-configuration', pageSize: 10, @@ -65,16 +64,22 @@ describe('ItemCollectionMapperComponent', () => { }; const mockCollectionsRD = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [])); const itemDataServiceStub = { - mapToCollection: () => Observable.of(new RestResponse(true, '200')), - removeMappingFromCollection: () => Observable.of(new RestResponse(true, '200')), - getMappedCollections: () => Observable.of(mockCollectionsRD) + mapToCollection: () => of(new RestResponse(true, '200')), + removeMappingFromCollection: () => of(new RestResponse(true, '200')), + getMappedCollections: () => of(mockCollectionsRD), + /* tslint:disable:no-empty */ + clearMappedCollectionsRequests: () => {} + /* tslint:enable:no-empty */ }; const searchServiceStub = Object.assign(new SearchServiceStub(), { - search: () => Observable.of(mockCollectionsRD) + search: () => of(mockCollectionsRD), + /* tslint:disable:no-empty */ + clearDiscoveryRequests: () => {} + /* tslint:enable:no-empty */ }); const activatedRouteStub = new ActivatedRouteStub({}, { item: mockItemRD }); const translateServiceStub = { - get: () => Observable.of('test-message of item ' + mockItem.name), + get: () => of('test-message of item ' + mockItem.name), onLangChange: new EventEmitter(), onTranslationChange: new EventEmitter(), onDefaultLangChange: new EventEmitter() @@ -130,7 +135,7 @@ describe('ItemCollectionMapperComponent', () => { }); it('should display an error message if at least one mapping was unsuccessful', () => { - spyOn(itemDataService, 'mapToCollection').and.returnValue(Observable.of(new RestResponse(false, '404'))); + spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, '404'))); comp.mapCollections(ids); expect(notificationsService.success).not.toHaveBeenCalled(); expect(notificationsService.error).toHaveBeenCalled(); @@ -152,7 +157,7 @@ describe('ItemCollectionMapperComponent', () => { }); it('should display an error message if the removal of at least one mapping was unsuccessful', () => { - spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(Observable.of(new RestResponse(false, '404'))); + spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(of(new RestResponse(false, '404'))); comp.removeMappings(ids); expect(notificationsService.success).not.toHaveBeenCalled(); expect(notificationsService.error).toHaveBeenCalled(); diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 3882f8e37c..43a66b41bd 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -37,6 +37,10 @@ export class CollectionDataService extends ComColDataService { return this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getFindByIDHref(endpoint, collectionId)), @@ -44,6 +48,11 @@ export class CollectionDataService extends ComColDataService>> { const requestUuid = this.requestService.generateRequestId(); @@ -68,6 +77,10 @@ export class CollectionDataService extends ComColDataService { 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 df36bad0bf..50f2ebe45e 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -54,6 +54,12 @@ export class ItemDataService extends DataService { distinctUntilChanged(),); } + /** + * Fetches the endpoint used for mapping an item to a collection, + * or for fetching all collections the item is mapped to if no collection is provided + * @param itemId The item's id + * @param collectionId The collection's id (optional) + */ public getMappingCollectionsEndpoint(itemId: string, collectionId?: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getFindByIDHref(endpoint, itemId)), @@ -61,6 +67,11 @@ export class ItemDataService extends DataService { ); } + /** + * Removes the mapping of an item from a collection + * @param itemId The item's id + * @param collectionId The collection's id + */ public removeMappingFromCollection(itemId: string, collectionId: string): Observable { return this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( isNotEmptyOperator(), @@ -72,6 +83,11 @@ export class ItemDataService extends DataService { ); } + /** + * Maps an item to a collection + * @param itemId The item's id + * @param collectionId The collection's id + */ public mapToCollection(itemId: string, collectionId: string): Observable { return this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( isNotEmptyOperator(), @@ -83,6 +99,10 @@ export class ItemDataService extends DataService { ); } + /** + * Fetches all collections the item is mapped to + * @param itemId The item's id + */ public getMappedCollections(itemId: string): Observable>> { const request$ = this.getMappingCollectionsEndpoint(itemId).pipe( isNotEmptyOperator(), @@ -102,6 +122,10 @@ export class ItemDataService extends DataService { return this.rdbService.toRemoteDataObservable(requestEntry$, payload$); } + /** + * Clears all requests (from cache) connected to the mappingCollections endpoint + * @param itemId + */ public clearMappedCollectionsRequests(itemId: string) { this.getMappingCollectionsEndpoint(itemId).pipe(take(1)).subscribe((href: string) => { this.requestService.removeByHrefSubstring(href); 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 551d33ba3b..d53a030baf 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 @@ -17,7 +17,7 @@ - {{collection.name}} + {{collection.name}} From 32db97e67d61ecb031f75e84aa65a5a6cb9b3479 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 30 Nov 2018 17:31:09 +0100 Subject: [PATCH 051/142] 55946: Spec file fixes --- src/app/core/data/request.service.spec.ts | 8 ++- .../core/metadata/metadata.service.spec.ts | 5 +- .../collection-select.component.spec.ts | 4 +- .../item-select/item-select.component.spec.ts | 8 +-- .../object-select.reducer.spec.ts | 53 +++++++++++-------- .../object-select.service.spec.ts | 42 +++++++++------ 6 files changed, 71 insertions(+), 49 deletions(-) diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index 90d2edfc84..e150d3c458 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -21,6 +21,7 @@ import { RequestService } from './request.service'; import { ActionsSubject, Store } from '@ngrx/store'; import { TestScheduler } from 'rxjs/testing'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { IndexState } from '../index/index.reducer'; describe('RequestService', () => { let scheduler: TestScheduler; @@ -29,6 +30,7 @@ describe('RequestService', () => { let objectCache: ObjectCacheService; let uuidService: UUIDService; let store: Store; + let indexStore: Store; const testUUID = '5f2a0d2a-effa-4d54-bd54-5663b960f9eb'; const testHref = 'https://rest.api/endpoint/selfLink'; @@ -48,7 +50,8 @@ describe('RequestService', () => { uuidService = getMockUUIDService(); - store = new Store(new BehaviorSubject({}), new ActionsSubject(), null); + store = new Store(undefined, new ActionsSubject(), null); + indexStore = new Store(undefined, new ActionsSubject(), null); selectSpy = spyOnProperty(ngrx, 'select'); selectSpy.and.callFake(() => { return () => { @@ -59,7 +62,8 @@ describe('RequestService', () => { service = new RequestService( objectCache, uuidService, - store + store, + indexStore ); serviceAsAny = service as any; }); diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 3be50a7450..78b204249b 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -32,6 +32,7 @@ import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; import { BrowseService } from '../browse/browse.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { EmptyError } from 'rxjs/internal-compatibility'; +import { IndexState } from '../index/index.reducer'; /* tslint:disable:max-classes-per-file */ @Component({ @@ -60,6 +61,7 @@ describe('MetadataService', () => { let title: Title; let store: Store; + let indexStore: Store; let objectCacheService: ObjectCacheService; let requestService: RequestService; @@ -78,11 +80,12 @@ describe('MetadataService', () => { beforeEach(() => { store = new Store(undefined, undefined, undefined); + indexStore = new Store(undefined, undefined, undefined); spyOn(store, 'dispatch'); objectCacheService = new ObjectCacheService(store); uuidService = new UUIDService(); - requestService = new RequestService(objectCacheService, uuidService, store); + requestService = new RequestService(objectCacheService, uuidService, store, indexStore); remoteDataBuildService = new RemoteDataBuildService(objectCacheService, requestService); TestBed.configureTestingModule({ 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 477f823928..bc83c3d52a 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 @@ -1,6 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { Observable } from 'rxjs/Observable'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; import { PageInfo } from '../../../core/shared/page-info.model'; @@ -15,6 +14,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; import { CollectionSelectComponent } from './collection-select.component'; import { Collection } from '../../../core/shared/collection.model'; +import { of } from 'rxjs/internal/observable/of'; describe('ItemSelectComponent', () => { let comp: CollectionSelectComponent; @@ -31,7 +31,7 @@ describe('ItemSelectComponent', () => { name: 'name2' }) ]; - const mockCollections = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), mockCollectionList))); + const mockCollections = of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), mockCollectionList))); const mockPaginationOptions = Object.assign(new PaginationComponentOptions(), { id: 'search-page-configuration', pageSize: 10, 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 e07858360e..be7c315c45 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 @@ -2,7 +2,6 @@ import { ItemSelectComponent } from './item-select.component'; import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { Item } from '../../../core/shared/item.model'; -import { Observable } from 'rxjs/Observable'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; import { PageInfo } from '../../../core/shared/page-info.model'; @@ -15,6 +14,7 @@ import { HostWindowService } from '../../host-window.service'; import { HostWindowServiceStub } from '../../testing/host-window-service-stub'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; +import { of } from 'rxjs/internal/observable/of'; describe('ItemSelectComponent', () => { let comp: ItemSelectComponent; @@ -24,7 +24,7 @@ describe('ItemSelectComponent', () => { const mockItemList = [ Object.assign(new Item(), { id: 'id1', - bitstreams: Observable.of({}), + bitstreams: of({}), metadata: [ { key: 'dc.title', @@ -39,7 +39,7 @@ describe('ItemSelectComponent', () => { }), Object.assign(new Item(), { id: 'id2', - bitstreams: Observable.of({}), + bitstreams: of({}), metadata: [ { key: 'dc.title', @@ -53,7 +53,7 @@ describe('ItemSelectComponent', () => { }] }) ]; - const mockItems = Observable.of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), mockItemList))); + const mockItems = of(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), mockItemList))); const mockPaginationOptions = Object.assign(new PaginationComponentOptions(), { id: 'search-page-configuration', pageSize: 10, diff --git a/src/app/shared/object-select/object-select.reducer.spec.ts b/src/app/shared/object-select/object-select.reducer.spec.ts index 696df97d39..197cbed510 100644 --- a/src/app/shared/object-select/object-select.reducer.spec.ts +++ b/src/app/shared/object-select/object-select.reducer.spec.ts @@ -5,6 +5,7 @@ import { } from './object-select.actions'; import { objectSelectionReducer } from './object-select.reducer'; +const key = 'key'; const objectId1 = 'id1'; const objectId2 = 'id2'; @@ -12,7 +13,7 @@ class NullAction extends ObjectSelectionSelectAction { type = null; constructor() { - super(undefined); + super(undefined, undefined); } } @@ -20,7 +21,8 @@ describe('objectSelectionReducer', () => { it('should return the current state when no valid actions have been made', () => { const state = {}; - state[objectId1] = { checked: true }; + state[key] = {}; + state[key][objectId1] = { checked: true }; const action = new NullAction(); const newState = objectSelectionReducer(state, action); @@ -36,63 +38,68 @@ describe('objectSelectionReducer', () => { }); it('should set checked to true in response to the INITIAL_SELECT action', () => { - const action = new ObjectSelectionInitialSelectAction(objectId1); + const action = new ObjectSelectionInitialSelectAction(key, objectId1); const newState = objectSelectionReducer(undefined, action); - expect(newState[objectId1].checked).toBeTruthy(); + expect(newState[key][objectId1].checked).toBeTruthy(); }); it('should set checked to true in response to the INITIAL_DESELECT action', () => { - const action = new ObjectSelectionInitialDeselectAction(objectId1); + const action = new ObjectSelectionInitialDeselectAction(key, objectId1); const newState = objectSelectionReducer(undefined, action); - expect(newState[objectId1].checked).toBeFalsy(); + expect(newState[key][objectId1].checked).toBeFalsy(); }); it('should set checked to true in response to the SELECT action', () => { const state = {}; - state[objectId1] = { checked: false }; - const action = new ObjectSelectionSelectAction(objectId1); + state[key] = {}; + state[key][objectId1] = { checked: false }; + const action = new ObjectSelectionSelectAction(key, objectId1); const newState = objectSelectionReducer(state, action); - expect(newState[objectId1].checked).toBeTruthy(); + expect(newState[key][objectId1].checked).toBeTruthy(); }); it('should set checked to false in response to the DESELECT action', () => { const state = {}; - state[objectId1] = { checked: true }; - const action = new ObjectSelectionDeselectAction(objectId1); + state[key] = {}; + state[key][objectId1] = { checked: true }; + const action = new ObjectSelectionDeselectAction(key, objectId1); const newState = objectSelectionReducer(state, action); - expect(newState[objectId1].checked).toBeFalsy(); + expect(newState[key][objectId1].checked).toBeFalsy(); }); it('should set checked from false to true in response to the SWITCH action', () => { const state = {}; - state[objectId1] = { checked: false }; - const action = new ObjectSelectionSwitchAction(objectId1); + state[key] = {}; + state[key][objectId1] = { checked: false }; + const action = new ObjectSelectionSwitchAction(key, objectId1); const newState = objectSelectionReducer(state, action); - expect(newState[objectId1].checked).toBeTruthy(); + expect(newState[key][objectId1].checked).toBeTruthy(); }); it('should set checked from true to false in response to the SWITCH action', () => { const state = {}; - state[objectId1] = { checked: true }; - const action = new ObjectSelectionSwitchAction(objectId1); + state[key] = {}; + state[key][objectId1] = { checked: true }; + const action = new ObjectSelectionSwitchAction(key, objectId1); const newState = objectSelectionReducer(state, action); - expect(newState[objectId1].checked).toBeFalsy(); + expect(newState[key][objectId1].checked).toBeFalsy(); }); - it('should set reset the state in response to the RESET action', () => { + it('should reset the state in response to the RESET action', () => { const state = {}; - state[objectId1] = { checked: true }; - state[objectId2] = { checked: false }; - const action = new ObjectSelectionResetAction(undefined); + state[key] = {}; + state[key][objectId1] = { checked: true }; + state[key][objectId2] = { checked: false }; + const action = new ObjectSelectionResetAction(key, undefined); const newState = objectSelectionReducer(state, action); - expect(newState).toEqual({}); + expect(newState[key]).toEqual({}); }); }); diff --git a/src/app/shared/object-select/object-select.service.spec.ts b/src/app/shared/object-select/object-select.service.spec.ts index 3b5bcec06f..ea4b99c419 100644 --- a/src/app/shared/object-select/object-select.service.spec.ts +++ b/src/app/shared/object-select/object-select.service.spec.ts @@ -1,7 +1,6 @@ import { ObjectSelectService } from './object-select.service'; import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs/Observable'; -import { ObjectSelectionsState } from './object-select.reducer'; +import { ObjectSelectionListState, ObjectSelectionsState } from './object-select.reducer'; import { AppState } from '../../app.reducer'; import { ObjectSelectionDeselectAction, @@ -9,87 +8,96 @@ import { ObjectSelectionInitialSelectAction, ObjectSelectionResetAction, ObjectSelectionSelectAction, ObjectSelectionSwitchAction } from './object-select.actions'; +import { of } from 'rxjs/internal/observable/of'; describe('ObjectSelectService', () => { let service: ObjectSelectService; + const mockKey = 'key'; const mockObjectId = 'id1'; + const selectionStore: Store = jasmine.createSpyObj('selectionStore', { + /* tslint:disable:no-empty */ + dispatch: {}, + /* tslint:enable:no-empty */ + select: of(true) + }); + const store: Store = jasmine.createSpyObj('store', { /* tslint:disable:no-empty */ dispatch: {}, /* tslint:enable:no-empty */ - select: Observable.of(true) + select: of(true) }); const appStore: Store = jasmine.createSpyObj('appStore', { /* tslint:disable:no-empty */ dispatch: {}, /* tslint:enable:no-empty */ - select: Observable.of(true) + select: of(true) }); beforeEach(() => { - service = new ObjectSelectService(store, appStore); + service = new ObjectSelectService(selectionStore, appStore); }); describe('when the initialSelect method is triggered', () => { beforeEach(() => { - service.initialSelect(mockObjectId); + service.initialSelect(mockKey, mockObjectId); }); it('ObjectSelectionInitialSelectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionInitialSelectAction(mockObjectId)); + expect(selectionStore.dispatch).toHaveBeenCalledWith(new ObjectSelectionInitialSelectAction(mockKey, mockObjectId)); }); }); describe('when the initialDeselect method is triggered', () => { beforeEach(() => { - service.initialDeselect(mockObjectId); + service.initialDeselect(mockKey, mockObjectId); }); it('ObjectSelectionInitialDeselectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionInitialDeselectAction(mockObjectId)); + expect(selectionStore.dispatch).toHaveBeenCalledWith(new ObjectSelectionInitialDeselectAction(mockKey, mockObjectId)); }); }); describe('when the select method is triggered', () => { beforeEach(() => { - service.select(mockObjectId); + service.select(mockKey, mockObjectId); }); it('ObjectSelectionSelectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionSelectAction(mockObjectId)); + expect(selectionStore.dispatch).toHaveBeenCalledWith(new ObjectSelectionSelectAction(mockKey, mockObjectId)); }); }); describe('when the deselect method is triggered', () => { beforeEach(() => { - service.deselect(mockObjectId); + service.deselect(mockKey, mockObjectId); }); it('ObjectSelectionDeselectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionDeselectAction(mockObjectId)); + expect(selectionStore.dispatch).toHaveBeenCalledWith(new ObjectSelectionDeselectAction(mockKey, mockObjectId)); }); }); describe('when the switch method is triggered', () => { beforeEach(() => { - service.switch(mockObjectId); + service.switch(mockKey, mockObjectId); }); it('ObjectSelectionSwitchAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionSwitchAction(mockObjectId)); + expect(selectionStore.dispatch).toHaveBeenCalledWith(new ObjectSelectionSwitchAction(mockKey, mockObjectId)); }); }); describe('when the reset method is triggered', () => { beforeEach(() => { - service.reset(); + service.reset(mockKey); }); it('ObjectSelectionInitialSelectAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new ObjectSelectionResetAction(null)); + expect(selectionStore.dispatch).toHaveBeenCalledWith(new ObjectSelectionResetAction(mockKey, null)); }); }); From 791325f584d90802e76acbd64c08d65e5f558a81 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 7 Dec 2018 17:17:54 +0100 Subject: [PATCH 052/142] 55946: TSLint fixes --- src/app/+community-page/community-page.component.ts | 5 ----- src/app/core/config/config.service.spec.ts | 1 - src/app/core/integration/integration.service.spec.ts | 2 +- src/app/shared/testing/query-params-directive-stub.ts | 2 +- src/app/shared/testing/utils.ts | 2 +- 5 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index ce260aefc0..09b3a3b62b 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -25,7 +25,6 @@ export class CommunityPageComponent implements OnInit, OnDestroy { communityRD$: Observable>; logoRD$: Observable>; - private subs: Subscription[] = []; constructor( @@ -42,14 +41,10 @@ export class CommunityPageComponent implements OnInit, OnDestroy { map((rd: RemoteData) => rd.payload), filter((community: Community) => hasValue(community)), mergeMap((community: Community) => community.logo)); - - } ngOnDestroy(): void { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } - - } diff --git a/src/app/core/config/config.service.spec.ts b/src/app/core/config/config.service.spec.ts index 8e9f7db27a..44cfdee358 100644 --- a/src/app/core/config/config.service.spec.ts +++ b/src/app/core/config/config.service.spec.ts @@ -36,7 +36,6 @@ describe('ConfigService', () => { const scopedEndpoint = `${serviceEndpoint}/${scopeName}`; const searchEndpoint = `${serviceEndpoint}/${BROWSE}?uuid=${scopeID}`; - function initTestService(): TestService { return new TestService( requestService, diff --git a/src/app/core/integration/integration.service.spec.ts b/src/app/core/integration/integration.service.spec.ts index 158f4b0680..152d7ab165 100644 --- a/src/app/core/integration/integration.service.spec.ts +++ b/src/app/core/integration/integration.service.spec.ts @@ -40,7 +40,7 @@ describe('IntegrationService', () => { findOptions = new IntegrationSearchOptions(uuid, name, metadata); - function initTestService(): TestService { + function initTestService(): TestService { return new TestService( requestService, halService diff --git a/src/app/shared/testing/query-params-directive-stub.ts b/src/app/shared/testing/query-params-directive-stub.ts index c19c5e6a5f..34216bb53c 100644 --- a/src/app/shared/testing/query-params-directive-stub.ts +++ b/src/app/shared/testing/query-params-directive-stub.ts @@ -6,5 +6,5 @@ import { Directive, Input } from '@angular/core'; selector: '[queryParams]', }) export class QueryParamsDirectiveStub { - @Input('queryParams') queryParams: any; + @Input() queryParams: any; } diff --git a/src/app/shared/testing/utils.ts b/src/app/shared/testing/utils.ts index 8714358100..cd17a1b1f5 100644 --- a/src/app/shared/testing/utils.ts +++ b/src/app/shared/testing/utils.ts @@ -41,4 +41,4 @@ export function spyOnOperator(obj: any, prop: string): any { }); return spyOn(obj, prop); -} \ No newline at end of file +} From 90b4a0bf2d7d763ace36c7185e0646309950f645 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 20 Dec 2018 14:59:31 +0100 Subject: [PATCH 053/142] 55946: Fixed imports and declarations on ItemCollectionMapperComponent tests --- .../item-collection-mapper.component.spec.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts index 79bcffe166..5e2b7c4420 100644 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts @@ -31,8 +31,13 @@ import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-s import { Observable } from 'rxjs/internal/Observable'; import { of } from 'rxjs/internal/observable/of'; import { RestResponse } from '../../../core/cache/response.models'; +import { CollectionSelectComponent } from '../../../shared/object-select/collection-select/collection-select.component'; +import { PaginationComponent } from '../../../shared/pagination/pagination.component'; +import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; +import { VarDirective } from '../../../shared/utils/var.directive'; +import { SearchFormComponent } from '../../../shared/search-form/search-form.component'; -describe('ItemCollectionMapperComponent', () => { +fdescribe('ItemCollectionMapperComponent', () => { let comp: ItemCollectionMapperComponent; let fixture: ComponentFixture; @@ -87,8 +92,8 @@ describe('ItemCollectionMapperComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [CommonModule, FormsModule, SharedModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], - declarations: [ItemCollectionMapperComponent], + imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [ItemCollectionMapperComponent, CollectionSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe, VarDirective], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: Router, useValue: routerStub }, From 1f19324ff6d225f7e08c3ffbd2fa25e7fb9bb881 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 20 Dec 2018 15:00:18 +0100 Subject: [PATCH 054/142] 55946: fdescribe to describe --- .../item-collection-mapper.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts index 5e2b7c4420..dcc65a41c6 100644 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts @@ -37,7 +37,7 @@ import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; import { VarDirective } from '../../../shared/utils/var.directive'; import { SearchFormComponent } from '../../../shared/search-form/search-form.component'; -fdescribe('ItemCollectionMapperComponent', () => { +describe('ItemCollectionMapperComponent', () => { let comp: ItemCollectionMapperComponent; let fixture: ComponentFixture; From f2bfdbcf84216a1a309a2f785b4fb8229ee65929 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 20 Dec 2018 16:11:25 +0100 Subject: [PATCH 055/142] 55946: Prevent list update from calling mapper over and over --- .../item-collection-mapper/item-collection-mapper.component.ts | 3 +++ 1 file changed, 3 insertions(+) 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 0b68864bce..53cba34cfb 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 @@ -116,11 +116,13 @@ export class ItemCollectionMapperComponent implements OnInit { const itemIdAndExcludingIds$ = observableCombineLatest( this.itemRD$.pipe( getSucceededRemoteData(), + take(1), map((rd: RemoteData) => rd.payload), map((item: Item) => item.id) ), this.itemCollectionsRD$.pipe( getSucceededRemoteData(), + take(1), map((rd: RemoteData>) => rd.payload.page), map((collections: Collection[]) => collections.map((collection: Collection) => collection.id)) ) @@ -168,6 +170,7 @@ export class ItemCollectionMapperComponent implements OnInit { */ private showNotifications(responses$: Observable, messagePrefix: string) { responses$.subscribe((responses: RestResponse[]) => { + console.log('message ' + messagePrefix + ' for ' + responses.length + ' responses...'); const successful = responses.filter((response: RestResponse) => response.isSuccessful); const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); if (successful.length > 0) { From 69f484640748793c0178f12fa1efe731ef8a7a50 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 20 Dec 2018 16:13:26 +0100 Subject: [PATCH 056/142] 55946: Removed console log --- .../item-collection-mapper/item-collection-mapper.component.ts | 1 - 1 file changed, 1 deletion(-) 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 53cba34cfb..97fbb3a5be 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 @@ -170,7 +170,6 @@ export class ItemCollectionMapperComponent implements OnInit { */ private showNotifications(responses$: Observable, messagePrefix: string) { responses$.subscribe((responses: RestResponse[]) => { - console.log('message ' + messagePrefix + ' for ' + responses.length + ' responses...'); const successful = responses.filter((response: RestResponse) => response.isSuccessful); const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); if (successful.length > 0) { From 13c1a553a1161af52e20f05aa416051fed7bb183 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 21 Dec 2018 13:12:33 +0100 Subject: [PATCH 057/142] 55946: Remove TODOs (fixed) --- .../collection-item-mapper.component.ts | 6 ------ .../item-collection-mapper.component.ts | 4 ---- 2 files changed, 10 deletions(-) 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 4d30a761e9..bc205111a4 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 @@ -21,9 +21,6 @@ import { CollectionDataService } from '../../core/data/collection-data.service'; import { isNotEmpty } from '../../shared/empty.util'; import { RestResponse } from '../../core/cache/response.models'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; -import { Actions, ofType } from '@ngrx/effects'; -import { IndexActionTypes } from '../../core/index/index.actions'; -import { RequestActionTypes } from '../../core/data/request.actions'; @Component({ selector: 'ds-collection-item-mapper', @@ -93,7 +90,6 @@ export class CollectionItemMapperComponent implements OnInit { /** * Load collectionItemsRD$ with a fixed scope to only obtain the items this collection owns * Load mappingItemsRD$ to only obtain items this collection doesn't own - * TODO: When the API support it, fetch items excluding the collection's scope (currently fetches all items) */ loadItemLists() { this.shouldUpdate$ = new BehaviorSubject(true); @@ -197,8 +193,6 @@ export class CollectionItemMapperComponent implements OnInit { * @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()); } 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 97fbb3a5be..0eadad860e 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 @@ -80,7 +80,6 @@ export class ItemCollectionMapperComponent implements OnInit { /** * 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.shouldUpdate$ = new BehaviorSubject(true); @@ -142,7 +141,6 @@ export class ItemCollectionMapperComponent implements OnInit { * @param {string[]} ids The list of collection UUID's to remove the mapping of the item for */ removeMappings(ids: string[]) { - // TODO: When the API supports fetching collections excluding the item's scope, make sure to exclude ids from mappingCollectionsRD$ here const responses$ = this.itemRD$.pipe( getSucceededRemoteData(), map((itemRD: RemoteData) => itemRD.payload.id), @@ -212,8 +210,6 @@ export class ItemCollectionMapperComponent implements OnInit { * @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()); } From aeba5d683da09f9a469e76b9d99373dda63ae901 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 31 Dec 2018 10:18:32 +0100 Subject: [PATCH 058/142] 55946: Destroy on hide to prevent pagination issues --- .../collection-item-mapper.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d094c8e058..29ff2c4e25 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 @@ -5,7 +5,7 @@

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

- +
From eb7b4cbf0e6b282230e5d4fbe9f544541cc657b1 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 31 Dec 2018 10:19:15 +0100 Subject: [PATCH 059/142] 55946: Destroy on hide to prevent pagination issues 2 --- .../item-collection-mapper.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fab7a16d79..7386eed98c 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 @@ -5,7 +5,7 @@

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

- +
From 0b164b33b9bd80dc0a5499785e98d22e994f1698 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 10 Jan 2019 13:14:20 +0100 Subject: [PATCH 060/142] remove unused 'first' imports --- .../search-facet-filter/search-facet-filter.component.ts | 2 +- .../search-filters/search-filter/search-filter.component.ts | 3 +-- src/app/app.component.ts | 2 +- src/app/core/auth/auth.effects.ts | 2 +- src/app/core/auth/auth.service.ts | 1 - src/app/core/auth/server-auth.service.ts | 2 +- src/app/core/cache/builders/remote-data-build.service.ts | 1 - src/app/core/cache/object-cache.service.ts | 2 +- src/app/core/cache/server-sync-buffer.effects.ts | 2 +- src/app/core/data/data.service.ts | 3 +-- src/app/core/metadata/metadata.service.ts | 1 - src/app/core/shared/operators.ts | 2 +- 12 files changed, 9 insertions(+), 14 deletions(-) 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 faaf3b9fb5..fd5a75e7d1 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 @@ -6,7 +6,7 @@ import { Subject, Subscription } from 'rxjs'; -import { switchMap, distinctUntilChanged, first, map, take } from 'rxjs/operators'; +import { switchMap, distinctUntilChanged, map, take } from 'rxjs/operators'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts index ec239e3628..289b5da143 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts @@ -1,5 +1,4 @@ - -import { first, take } from 'rxjs/operators'; +import { take } from 'rxjs/operators'; import { Component, Input, OnInit } from '@angular/core'; import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; import { SearchFilterService } from './search-filter.service'; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 98e0d614ae..30a8f01251 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { filter, first, map, take } from 'rxjs/operators'; +import { filter, map, take } from 'rxjs/operators'; import { AfterViewInit, ChangeDetectionStrategy, diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts index 56a5411ef2..1e68802af8 100644 --- a/src/app/core/auth/auth.effects.ts +++ b/src/app/core/auth/auth.effects.ts @@ -1,6 +1,6 @@ import { of as observableOf, Observable } from 'rxjs'; -import { filter, debounceTime, switchMap, take, tap, catchError, map, first } from 'rxjs/operators'; +import { filter, debounceTime, switchMap, take, tap, catchError, map } from 'rxjs/operators'; import { Injectable } from '@angular/core'; // import @ngrx diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 66fe65a22e..6a2b4afa6e 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -2,7 +2,6 @@ import { Observable, of as observableOf } from 'rxjs'; import { distinctUntilChanged, filter, - first, map, startWith, switchMap, diff --git a/src/app/core/auth/server-auth.service.ts b/src/app/core/auth/server-auth.service.ts index 903926fbcf..25ec1156ee 100644 --- a/src/app/core/auth/server-auth.service.ts +++ b/src/app/core/auth/server-auth.service.ts @@ -1,4 +1,4 @@ -import { first, map, switchMap, take } from 'rxjs/operators'; +import { map, switchMap, take } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 62a4992787..7561fe3aff 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -7,7 +7,6 @@ import { import { Injectable } from '@angular/core'; import { distinctUntilChanged, - first, flatMap, map, startWith, diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts index 40f41be14d..af30646f53 100644 --- a/src/app/core/cache/object-cache.service.ts +++ b/src/app/core/cache/object-cache.service.ts @@ -1,6 +1,6 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { distinctUntilChanged, filter, first, map, mergeMap, take, } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, mergeMap, take, } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { MemoizedSelector, select, Store } from '@ngrx/store'; import { IndexName } from '../index/index.reducer'; diff --git a/src/app/core/cache/server-sync-buffer.effects.ts b/src/app/core/cache/server-sync-buffer.effects.ts index d0a194705b..0d7392e555 100644 --- a/src/app/core/cache/server-sync-buffer.effects.ts +++ b/src/app/core/cache/server-sync-buffer.effects.ts @@ -1,4 +1,4 @@ -import { delay, exhaustMap, first, map, switchMap, take, tap } from 'rxjs/operators'; +import { delay, exhaustMap, map, switchMap, take } from 'rxjs/operators'; import { Inject, Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 6afc84df5a..0921592a83 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,4 +1,4 @@ -import { delay, distinctUntilChanged, filter, find, first, map, take, tap } from 'rxjs/operators'; +import { filter, find, map, take } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; @@ -14,7 +14,6 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { compare, Operation } from 'fast-json-patch'; import { ObjectCacheService } from '../cache/object-cache.service'; import { DSpaceObject } from '../shared/dspace-object.model'; -import { of } from 'rxjs/internal/observable/of'; export abstract class DataService { protected abstract requestService: RequestService; diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 9a74de992e..136bfe8f3e 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -2,7 +2,6 @@ import { catchError, distinctUntilKeyChanged, filter, - find, first, map, take diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index d9b41ebd73..550ef09163 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -1,5 +1,5 @@ import { Observable } from 'rxjs'; -import { filter, find, first, flatMap, map, tap } from 'rxjs/operators'; +import { filter, find, flatMap, map, tap } from 'rxjs/operators'; import { hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util'; import { DSOSuccessResponse, RestResponse } from '../cache/response.models'; import { RemoteData } from '../data/remote-data'; From 04ad79e4169dd5cffded55a39cb2fee8b4477299 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 10 Jan 2019 13:16:27 +0100 Subject: [PATCH 061/142] remove exportToZip config property that was added accidentally --- config/environment.default.js | 1 - src/config/cache-config.interface.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/config/environment.default.js b/config/environment.default.js index 527e12936e..3c1144fc6f 100644 --- a/config/environment.default.js +++ b/config/environment.default.js @@ -20,7 +20,6 @@ module.exports = { // NOTE: how long should objects be cached for by default msToLive: { default: 15 * 60 * 1000, // 15 minutes - exportToZip: 5 * 1000 // 5 seconds }, // msToLive: 1000, // 15 minutes control: 'max-age=60', // revalidate browser diff --git a/src/config/cache-config.interface.ts b/src/config/cache-config.interface.ts index a52eca60e2..ef2d19e76e 100644 --- a/src/config/cache-config.interface.ts +++ b/src/config/cache-config.interface.ts @@ -4,7 +4,6 @@ import { AutoSyncConfig } from './auto-sync-config.interface'; export interface CacheConfig extends Config { msToLive: { default: number; - exportToZip: number; }, control: string, autoSync: AutoSyncConfig From ff924abcfb74482054f3fc19613273a6b3837a3c Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 10 Jan 2019 17:06:49 +0100 Subject: [PATCH 062/142] fix a typo in a comment --- .../collection-item-mapper/collection-item-mapper.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bc205111a4..fb08cfc122 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 @@ -33,7 +33,7 @@ import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; ] }) /** - * Collection used to map items to a collection + * Component used to map items to a collection */ export class CollectionItemMapperComponent implements OnInit { From 2b1fd76365a53f39d8a7493e2f18cae130c76f42 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 28 May 2019 16:42:02 +0200 Subject: [PATCH 063/142] 62589: Fixed order of imports messing with routes --- src/app/+item-page/item-page.module.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts index 123e3ea143..7d947d8805 100644 --- a/src/app/+item-page/item-page.module.ts +++ b/src/app/+item-page/item-page.module.ts @@ -33,11 +33,11 @@ import { RelatedEntitiesSearchComponent } from './simple/related-entities/relate @NgModule({ imports: [ + ItemPageRoutingModule, CommonModule, SharedModule, - EditItemPageModule, - ItemPageRoutingModule, - SearchPageModule + SearchPageModule, + EditItemPageModule ], declarations: [ ItemPageComponent, From 045b87c1c8e41e365d900612178100cd75fcfbdb Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 28 May 2019 17:03:57 +0200 Subject: [PATCH 064/142] 62589: Post-Merge Tests and error fixes --- .../collection-item-mapper.component.spec.ts | 9 ++------- .../item-collection-mapper.component.spec.ts | 18 ++++-------------- .../item-select/item-select.component.html | 4 ++-- 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index 9d4f6e8f7b..d6014f9c3a 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -72,7 +72,7 @@ describe('CollectionItemMapperComponent', () => { paginatedSearchOptions: mockSearchOptions }; const itemDataServiceStub = { - mapToCollection: () => of(new RestResponse(true, '200')) + mapToCollection: () => of(new RestResponse(true, 200, 'OK')) }; const activatedRouteStub = new ActivatedRouteStub({}, { collection: mockCollectionRD }); const translateServiceStub = { @@ -134,11 +134,6 @@ describe('CollectionItemMapperComponent', () => { describe('mapItems', () => { const ids = ['id1', 'id2', 'id3', 'id4']; - beforeEach(() => { - spyOn(notificationsService, 'success').and.callThrough(); - spyOn(notificationsService, 'error').and.callThrough(); - }); - it('should display a success message if at least one mapping was successful', () => { comp.mapItems(ids); expect(notificationsService.success).toHaveBeenCalled(); @@ -146,7 +141,7 @@ describe('CollectionItemMapperComponent', () => { }); it('should display an error message if at least one mapping was unsuccessful', () => { - spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, '404'))); + spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found'))); comp.mapItems(ids); expect(notificationsService.success).not.toHaveBeenCalled(); expect(notificationsService.error).toHaveBeenCalled(); diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts index dcc65a41c6..2f04126711 100644 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts @@ -69,8 +69,8 @@ describe('ItemCollectionMapperComponent', () => { }; const mockCollectionsRD = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [])); const itemDataServiceStub = { - mapToCollection: () => of(new RestResponse(true, '200')), - removeMappingFromCollection: () => of(new RestResponse(true, '200')), + mapToCollection: () => of(new RestResponse(true, 200, 'OK')), + removeMappingFromCollection: () => of(new RestResponse(true, 200, 'OK')), getMappedCollections: () => of(mockCollectionsRD), /* tslint:disable:no-empty */ clearMappedCollectionsRequests: () => {} @@ -128,11 +128,6 @@ describe('ItemCollectionMapperComponent', () => { describe('mapCollections', () => { const ids = ['id1', 'id2', 'id3', 'id4']; - beforeEach(() => { - spyOn(notificationsService, 'success').and.callThrough(); - spyOn(notificationsService, 'error').and.callThrough(); - }); - it('should display a success message if at least one mapping was successful', () => { comp.mapCollections(ids); expect(notificationsService.success).toHaveBeenCalled(); @@ -140,7 +135,7 @@ describe('ItemCollectionMapperComponent', () => { }); it('should display an error message if at least one mapping was unsuccessful', () => { - spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, '404'))); + spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found'))); comp.mapCollections(ids); expect(notificationsService.success).not.toHaveBeenCalled(); expect(notificationsService.error).toHaveBeenCalled(); @@ -150,11 +145,6 @@ describe('ItemCollectionMapperComponent', () => { describe('removeMappings', () => { const ids = ['id1', 'id2', 'id3', 'id4']; - beforeEach(() => { - spyOn(notificationsService, 'success').and.callThrough(); - spyOn(notificationsService, 'error').and.callThrough(); - }); - it('should display a success message if the removal of at least one mapping was successful', () => { comp.removeMappings(ids); expect(notificationsService.success).toHaveBeenCalled(); @@ -162,7 +152,7 @@ describe('ItemCollectionMapperComponent', () => { }); it('should display an error message if the removal of at least one mapping was unsuccessful', () => { - spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(of(new RestResponse(false, '404'))); + spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found'))); comp.removeMappings(ids); expect(notificationsService.success).not.toHaveBeenCalled(); expect(notificationsService.error).toHaveBeenCalled(); 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 522536f86c..51883186e1 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 @@ -20,8 +20,8 @@ {{(item.owningCollection | async)?.payload?.name}} - {{item.filterMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])[0].value}} - {{item.findMetadata("dc.title")}} + {{item.firstMetadataValue(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])}} + {{item.firstMetadataValue("dc.title")}} From 28fe62f9186d51752102915a4046a50066e47a99 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 29 May 2019 10:01:47 +0200 Subject: [PATCH 065/142] 62589: Fix mappedCollections endpoint --- src/app/core/data/item-data.service.ts | 2 +- .../mapping-collections-reponse-parsing.service.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 71991da780..bf01ce8df8 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -82,7 +82,7 @@ export class ItemDataService extends DataService { public getMappingCollectionsEndpoint(itemId: string, collectionId?: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getIDHref(endpoint, itemId)), - map((endpoint: string) => `${endpoint}/mappingCollections${collectionId ? `/${collectionId}` : ''}`) + map((endpoint: string) => `${endpoint}/mappedCollections${collectionId ? `/${collectionId}` : ''}`) ); } diff --git a/src/app/core/data/mapping-collections-reponse-parsing.service.ts b/src/app/core/data/mapping-collections-reponse-parsing.service.ts index 9272d3d470..afe9678c7e 100644 --- a/src/app/core/data/mapping-collections-reponse-parsing.service.ts +++ b/src/app/core/data/mapping-collections-reponse-parsing.service.ts @@ -11,16 +11,16 @@ export class MappingCollectionsReponseParsingService implements ResponseParsingS parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { const payload = data.payload; - if (payload._embedded && payload._embedded.mappingCollections) { - const mappingCollections = payload._embedded.mappingCollections; + if (payload._embedded && payload._embedded.mappedCollections) { + const mappedCollections = payload._embedded.mappedCollections; // TODO: When the API supports it, change this to fetch a paginated list, instead of creating static one // Reason: Pagination is currently not supported on the mappingCollections endpoint const paginatedMappingCollections = new PaginatedList(Object.assign(new PageInfo(), { - elementsPerPage: mappingCollections.length, - totalElements: mappingCollections.length, + elementsPerPage: mappedCollections.length, + totalElements: mappedCollections.length, totalPages: 1, currentPage: 1 - }), mappingCollections); + }), mappedCollections); return new GenericSuccessResponse(paginatedMappingCollections, data.statusCode, data.statusText); } else { return new ErrorResponse( From df730efbd0b2f2ce53e2c87824e99489fb8a93ec Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 29 May 2019 11:44:34 +0200 Subject: [PATCH 066/142] 62589: Provider and POST item-collection mapping fix --- .../collection-item-mapper.component.ts | 17 ++++++++++++----- .../collection-page.module.ts | 4 +++- src/app/core/data/item-data.service.ts | 19 +++++++++++++------ 3 files changed, 28 insertions(+), 12 deletions(-) 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 fb08cfc122..520c446d8e 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 @@ -1,6 +1,6 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; import { fadeIn, fadeInOut } from '../../shared/animations/fade'; import { ActivatedRoute, Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; @@ -21,6 +21,7 @@ import { CollectionDataService } from '../../core/data/collection-data.service'; import { isNotEmpty } from '../../shared/empty.util'; import { RestResponse } from '../../core/cache/response.models'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; @Component({ selector: 'ds-collection-item-mapper', @@ -30,6 +31,12 @@ import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; animations: [ fadeIn, fadeInOut + ], + providers: [ + { + provide: SEARCH_CONFIG_SERVICE, + useClass: SearchConfigurationService + } ] }) /** @@ -73,7 +80,7 @@ export class CollectionItemMapperComponent implements OnInit { constructor(private route: ActivatedRoute, private router: Router, - private searchConfigService: SearchConfigurationService, + @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService, private searchService: SearchService, private notificationsService: NotificationsService, private itemDataService: ItemDataService, @@ -130,10 +137,10 @@ export class CollectionItemMapperComponent implements OnInit { mapItems(ids: string[], remove?: boolean) { const responses$ = this.collectionRD$.pipe( getSucceededRemoteData(), - map((collectionRD: RemoteData) => collectionRD.payload.id), - switchMap((collectionId: string) => + map((collectionRD: RemoteData) => collectionRD.payload), + switchMap((collection: Collection) => observableCombineLatest(ids.map((id: string) => - remove ? this.itemDataService.removeMappingFromCollection(id, collectionId) : this.itemDataService.mapToCollection(id, collectionId) + remove ? this.itemDataService.removeMappingFromCollection(id, collection.id) : this.itemDataService.mapToCollection(id, collection.self) )) ) ); diff --git a/src/app/+collection-page/collection-page.module.ts b/src/app/+collection-page/collection-page.module.ts index 86afb37170..0eaeca8ca7 100644 --- a/src/app/+collection-page/collection-page.module.ts +++ b/src/app/+collection-page/collection-page.module.ts @@ -11,6 +11,7 @@ import { EditCollectionPageComponent } from './edit-collection-page/edit-collect import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; import { SearchService } from '../+search-page/search-service/search.service'; import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component'; +import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service'; @NgModule({ imports: [ @@ -27,7 +28,8 @@ import { CollectionItemMapperComponent } from './collection-item-mapper/collecti CollectionItemMapperComponent ], providers: [ - SearchService + SearchService, + SearchFixedFilterService ] }) export class CollectionPageModule { diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index bf01ce8df8..f26f0574f7 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -23,7 +23,7 @@ import { 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, @@ -36,6 +36,7 @@ import { GenericSuccessResponse, RestResponse } from '../cache/response.models'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; import { Collection } from '../shared/collection.model'; +import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; @Injectable() export class ItemDataService extends DataService { @@ -104,14 +105,20 @@ export class ItemDataService extends DataService { /** * Maps an item to a collection - * @param itemId The item's id - * @param collectionId The collection's id + * @param itemId The item's id + * @param collectionHref The collection's self link */ - public mapToCollection(itemId: string, collectionId: string): Observable { - return this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( + public mapToCollection(itemId: string, collectionHref: string): Observable { + return this.getMappingCollectionsEndpoint(itemId).pipe( isNotEmptyOperator(), distinctUntilChanged(), - map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)), + map((endpointURL: string) => { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + return new PostRequest(this.requestService.generateRequestId(), endpointURL, collectionHref, options); + }), configureRequest(this.requestService), switchMap((request: RestRequest) => this.requestService.getByUUID(request.uuid)), getResponseFromEntry() From acf83f62618180d7c0214c093324f4e38cd600c2 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 29 May 2019 12:03:27 +0200 Subject: [PATCH 067/142] 62589: Test provider fix --- .../collection-item-mapper.component.spec.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index d6014f9c3a..4100c49d9e 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -37,8 +37,10 @@ import { ObjectSelectService } from '../../shared/object-select/object-select.se import { ObjectSelectServiceStub } from '../../shared/testing/object-select-service-stub'; import { VarDirective } from '../../shared/utils/var.directive'; import { Observable } from 'rxjs/internal/Observable'; -import { of } from 'rxjs/internal/observable/of'; +import { of as observableOf, of } from 'rxjs/internal/observable/of'; import { RestResponse } from '../../core/cache/response.models'; +import { RouteService } from '../../shared/services/route.service'; +import { SearchFixedFilterService } from '../../+search-page/search-filters/search-filter/search-fixed-filter.service'; describe('CollectionItemMapperComponent', () => { let comp: CollectionItemMapperComponent; @@ -94,6 +96,22 @@ describe('CollectionItemMapperComponent', () => { clearMappingItemsRequests: () => {} /* tslint:enable:no-empty */ }; + const routeServiceStub = { + getRouteParameterValue: () => { + return observableOf(''); + }, + getQueryParameterValue: () => { + return observableOf('') + }, + getQueryParamsWithPrefix: () => { + return observableOf('') + } + }; + const fixedFilterServiceStub = { + getQueryByFilterName: () => { + return observableOf('') + } + }; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -109,7 +127,9 @@ describe('CollectionItemMapperComponent', () => { { provide: CollectionDataService, useValue: collectionDataServiceStub }, { provide: TranslateService, useValue: translateServiceStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, - { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() } + { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, + { provide: RouteService, useValue: routeServiceStub }, + { provide: SearchFixedFilterService, useValue: fixedFilterServiceStub } ] }).compileComponents(); })); From 26e25069ad46c70ed5e8383f198c255c59886de1 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 12 Aug 2019 16:41:56 +0200 Subject: [PATCH 068/142] 62589: PR Feedback --- src/app/core/core.module.ts | 4 ++-- src/app/core/data/item-data.service.ts | 4 ++-- ....ts => mapped-collections-reponse-parsing.service.ts} | 6 +++++- src/app/core/data/request.models.ts | 9 ++++++--- 4 files changed, 15 insertions(+), 8 deletions(-) rename src/app/core/data/{mapping-collections-reponse-parsing.service.ts => mapped-collections-reponse-parsing.service.ts} (84%) diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 00f160d5ef..08f09d99cc 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -117,7 +117,7 @@ import { MetadatafieldParsingService } from './data/metadatafield-parsing.servic import { NormalizedSubmissionUploadsModel } from './config/models/normalized-config-submission-uploads.model'; import { NormalizedBrowseEntry } from './shared/normalized-browse-entry.model'; import { BrowseDefinition } from './shared/browse-definition.model'; -import { MappingCollectionsReponseParsingService } from './data/mapping-collections-reponse-parsing.service'; +import { MappedCollectionsReponseParsingService } from './data/mapped-collections-reponse-parsing.service'; import { ObjectSelectService } from '../shared/object-select/object-select.service'; const IMPORTS = [ @@ -165,7 +165,7 @@ const PROVIDERS = [ RegistryMetadataschemasResponseParsingService, RegistryMetadatafieldsResponseParsingService, RegistryBitstreamformatsResponseParsingService, - MappingCollectionsReponseParsingService, + MappedCollectionsReponseParsingService, DebugResponseParsingService, SearchResponseParsingService, MyDSpaceResponseParsingService, diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index f26f0574f7..94c77664d3 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -15,7 +15,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { DeleteRequest, FindAllOptions, - MappingCollectionsRequest, + MappedCollectionsRequest, PatchRequest, PostRequest, RestRequest @@ -133,7 +133,7 @@ export class ItemDataService extends DataService { const request$ = this.getMappingCollectionsEndpoint(itemId).pipe( isNotEmptyOperator(), distinctUntilChanged(), - map((endpointURL: string) => new MappingCollectionsRequest(this.requestService.generateRequestId(), endpointURL)), + map((endpointURL: string) => new MappedCollectionsRequest(this.requestService.generateRequestId(), endpointURL)), configureRequest(this.requestService) ); diff --git a/src/app/core/data/mapping-collections-reponse-parsing.service.ts b/src/app/core/data/mapped-collections-reponse-parsing.service.ts similarity index 84% rename from src/app/core/data/mapping-collections-reponse-parsing.service.ts rename to src/app/core/data/mapped-collections-reponse-parsing.service.ts index afe9678c7e..45dd361b23 100644 --- a/src/app/core/data/mapping-collections-reponse-parsing.service.ts +++ b/src/app/core/data/mapped-collections-reponse-parsing.service.ts @@ -7,7 +7,11 @@ import { PageInfo } from '../shared/page-info.model'; import { ErrorResponse, GenericSuccessResponse, RestResponse } from '../cache/response.models'; @Injectable() -export class MappingCollectionsReponseParsingService implements ResponseParsingService { +/** + * A ResponseParsingService used to parse DSpaceRESTV2Response coming from the REST API to a GenericSuccessResponse + * containing a PaginatedList of mapped collections + */ +export class MappedCollectionsReponseParsingService implements ResponseParsingService { parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { const payload = data.payload; diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index ac2e17a727..b327306fcb 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -18,7 +18,7 @@ import { MetadataschemaParsingService } from './metadataschema-parsing.service'; import { MetadatafieldParsingService } from './metadatafield-parsing.service'; import { URLCombiner } from '../url-combiner/url-combiner'; import { TaskResponseParsingService } from '../tasks/task-response-parsing.service'; -import { MappingCollectionsReponseParsingService } from './mapping-collections-reponse-parsing.service'; +import { MappedCollectionsReponseParsingService } from './mapped-collections-reponse-parsing.service'; /* tslint:disable:max-classes-per-file */ @@ -186,9 +186,12 @@ export class BrowseItemsRequest extends GetRequest { } } -export class MappingCollectionsRequest extends GetRequest { +/** + * Request to fetch the mapped collections of an item + */ +export class MappedCollectionsRequest extends GetRequest { getResponseParser(): GenericConstructor { - return MappingCollectionsReponseParsingService; + return MappedCollectionsReponseParsingService; } } From c95fa8fb96182fe8146523f07193cb655b1492b1 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 19 Aug 2019 14:18:10 +0200 Subject: [PATCH 069/142] 62589: Refactoring mapping endpoint and methods to mapped + collection-list sorting bugfix --- .../collection-item-mapper.component.html | 2 +- .../collection-item-mapper.component.spec.ts | 2 +- .../collection-item-mapper.component.ts | 8 ++++---- .../item-collection-mapper.component.html | 3 ++- .../item-collection-mapper.component.ts | 6 +++--- src/app/core/data/collection-data.service.ts | 12 ++++++------ src/app/core/data/item-data.service.ts | 12 ++++++------ .../mapped-collections-reponse-parsing.service.ts | 8 ++++---- .../collection-select.component.html | 1 + .../item-select/item-select.component.html | 1 + .../object-select/object-select.component.ts | 7 +++++++ 11 files changed, 36 insertions(+), 26 deletions(-) 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 29ff2c4e25..9b7216c92a 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 @@ -34,7 +34,7 @@
diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index 4100c49d9e..046127da4c 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -93,7 +93,7 @@ describe('CollectionItemMapperComponent', () => { const collectionDataServiceStub = { getMappedItems: () => of(emptyList), /* tslint:disable:no-empty */ - clearMappingItemsRequests: () => {} + clearMappedItemsRequests: () => {} /* tslint:enable:no-empty */ }; const routeServiceStub = { 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 520c446d8e..554696c63f 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 @@ -64,7 +64,7 @@ export class CollectionItemMapperComponent implements OnInit { * List of items to show under the "Map" tab * Items outside the collection */ - mappingItemsRD$: Observable>>; + mappedItemsRD$: Observable>>; /** * Sort on title ASC by default @@ -96,7 +96,7 @@ export class CollectionItemMapperComponent implements OnInit { /** * Load collectionItemsRD$ with a fixed scope to only obtain the items this collection owns - * Load mappingItemsRD$ to only obtain items this collection doesn't own + * Load mappedItemsRD$ to only obtain items this collection doesn't own */ loadItemLists() { this.shouldUpdate$ = new BehaviorSubject(true); @@ -114,7 +114,7 @@ export class CollectionItemMapperComponent implements OnInit { } }) ); - this.mappingItemsRD$ = collectionAndOptions$.pipe( + this.mappedItemsRD$ = collectionAndOptions$.pipe( switchMap(([collectionRD, options, shouldUpdate]) => { if (shouldUpdate) { return this.searchService.search(Object.assign(new PaginatedSearchOptions(options), { @@ -190,7 +190,7 @@ export class CollectionItemMapperComponent implements OnInit { */ private clearRequestCache() { this.collectionRD$.pipe(take(1)).subscribe((collectionRD: RemoteData) => { - this.collectionDataService.clearMappingItemsRequests(collectionRD.payload.id); + this.collectionDataService.clearMappedItemsRequests(collectionRD.payload.id); this.searchService.clearDiscoveryRequests(); }); } 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 7386eed98c..55619bdc96 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 @@ -32,8 +32,9 @@
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 0eadad860e..803083c428 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 @@ -54,7 +54,7 @@ export class ItemCollectionMapperComponent implements OnInit { * List of collections to show under the "Map" tab * Collections that are not mapped to the item */ - mappingCollectionsRD$: Observable>>; + mappedCollectionsRD$: Observable>>; /** * Firing this observable (shouldUpdate$.next(true)) forces the two lists to reload themselves @@ -79,7 +79,7 @@ export class ItemCollectionMapperComponent implements OnInit { /** * 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 + * Load mappedCollectionsRD$ to only obtain collections that don't own this item */ loadCollectionLists() { this.shouldUpdate$ = new BehaviorSubject(true); @@ -96,7 +96,7 @@ export class ItemCollectionMapperComponent implements OnInit { this.itemCollectionsRD$, this.searchOptions$ ); - this.mappingCollectionsRD$ = itemCollectionsAndOptions$.pipe( + this.mappedCollectionsRD$ = itemCollectionsAndOptions$.pipe( switchMap(([itemCollectionsRD, searchOptions]) => { return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), { query: this.buildQuery(itemCollectionsRD.payload.page, searchOptions.query), diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index e4b8442718..38b86b3817 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -69,10 +69,10 @@ export class CollectionDataService extends ComColDataService { * Fetches the endpoint used for mapping items to a collection * @param collectionId The id of the collection to map items to */ - getMappingItemsEndpoint(collectionId): Observable { + getMappedItemsEndpoint(collectionId): Observable { return this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getIDHref(endpoint, collectionId)), - map((endpoint: string) => `${endpoint}/mappingItems`) + map((endpoint: string) => `${endpoint}/mappedItems`) ); } @@ -84,7 +84,7 @@ export class CollectionDataService extends ComColDataService { getMappedItems(collectionId: string, searchOptions?: PaginatedSearchOptions): Observable>> { const requestUuid = this.requestService.generateRequestId(); - const href$ = this.getMappingItemsEndpoint(collectionId).pipe( + const href$ = this.getMappedItemsEndpoint(collectionId).pipe( isNotEmptyOperator(), distinctUntilChanged(), map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint) @@ -106,11 +106,11 @@ export class CollectionDataService extends ComColDataService { } /** - * Clears all requests (from cache) connected to the mappingItems endpoint + * Clears all requests (from cache) connected to the mappedItems endpoint * @param collectionId */ - clearMappingItemsRequests(collectionId: string) { - this.getMappingItemsEndpoint(collectionId).pipe(take(1)).subscribe((href: string) => { + 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 94c77664d3..9b59307f34 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -80,7 +80,7 @@ export class ItemDataService extends DataService { * @param itemId The item's id * @param collectionId The collection's id (optional) */ - public getMappingCollectionsEndpoint(itemId: string, collectionId?: string): Observable { + public getMappedCollectionsEndpoint(itemId: string, collectionId?: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getIDHref(endpoint, itemId)), map((endpoint: string) => `${endpoint}/mappedCollections${collectionId ? `/${collectionId}` : ''}`) @@ -93,7 +93,7 @@ export class ItemDataService extends DataService { * @param collectionId The collection's id */ public removeMappingFromCollection(itemId: string, collectionId: string): Observable { - return this.getMappingCollectionsEndpoint(itemId, collectionId).pipe( + return this.getMappedCollectionsEndpoint(itemId, collectionId).pipe( isNotEmptyOperator(), distinctUntilChanged(), map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)), @@ -109,7 +109,7 @@ export class ItemDataService extends DataService { * @param collectionHref The collection's self link */ public mapToCollection(itemId: string, collectionHref: string): Observable { - return this.getMappingCollectionsEndpoint(itemId).pipe( + return this.getMappedCollectionsEndpoint(itemId).pipe( isNotEmptyOperator(), distinctUntilChanged(), map((endpointURL: string) => { @@ -130,7 +130,7 @@ export class ItemDataService extends DataService { * @param itemId The item's id */ public getMappedCollections(itemId: string): Observable>> { - const request$ = this.getMappingCollectionsEndpoint(itemId).pipe( + const request$ = this.getMappedCollectionsEndpoint(itemId).pipe( isNotEmptyOperator(), distinctUntilChanged(), map((endpointURL: string) => new MappedCollectionsRequest(this.requestService.generateRequestId(), endpointURL)), @@ -149,11 +149,11 @@ export class ItemDataService extends DataService { } /** - * Clears all requests (from cache) connected to the mappingCollections endpoint + * Clears all requests (from cache) connected to the mappedCollections endpoint * @param itemId */ public clearMappedCollectionsRequests(itemId: string) { - this.getMappingCollectionsEndpoint(itemId).pipe(take(1)).subscribe((href: string) => { + this.getMappedCollectionsEndpoint(itemId).pipe(take(1)).subscribe((href: string) => { this.requestService.removeByHrefSubstring(href); }); } diff --git a/src/app/core/data/mapped-collections-reponse-parsing.service.ts b/src/app/core/data/mapped-collections-reponse-parsing.service.ts index 45dd361b23..bf8ed036e3 100644 --- a/src/app/core/data/mapped-collections-reponse-parsing.service.ts +++ b/src/app/core/data/mapped-collections-reponse-parsing.service.ts @@ -18,18 +18,18 @@ export class MappedCollectionsReponseParsingService implements ResponseParsingSe if (payload._embedded && payload._embedded.mappedCollections) { const mappedCollections = payload._embedded.mappedCollections; // TODO: When the API supports it, change this to fetch a paginated list, instead of creating static one - // Reason: Pagination is currently not supported on the mappingCollections endpoint - const paginatedMappingCollections = new PaginatedList(Object.assign(new PageInfo(), { + // Reason: Pagination is currently not supported on the mappedCollections endpoint + const paginatedMappedCollections = new PaginatedList(Object.assign(new PageInfo(), { elementsPerPage: mappedCollections.length, totalElements: mappedCollections.length, totalPages: 1, currentPage: 1 }), mappedCollections); - return new GenericSuccessResponse(paginatedMappingCollections, data.statusCode, data.statusText); + return new GenericSuccessResponse(paginatedMappedCollections, data.statusCode, data.statusText); } else { return new ErrorResponse( Object.assign( - new Error('Unexpected response from mappingCollections endpoint'), data + new Error('Unexpected response from mappedCollections endpoint'), data ) ); } 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 d53a030baf..5a1c98fcf5 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 @@ -2,6 +2,7 @@ implements OnInit, OnDestro @Input() paginationOptions: PaginationComponentOptions; + /** + * The sorting options used to display the DSpaceObjects + */ + @Input() + sortOptions: SortOptions; + /** * The message key used for the confirm button * @type {string} From 897e303035a864c4895e76929a66d5266d2b727b Mon Sep 17 00:00:00 2001 From: lhenze Date: Wed, 21 Aug 2019 11:00:56 -0400 Subject: [PATCH 070/142] Moving Logo --- .../+collection-page/collection-page.component.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 91239de17c..75e507508a 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -3,17 +3,19 @@ *ngVar="(collectionRD$ | async) as collectionRD">
+ + + + - - - + Date: Wed, 21 Aug 2019 12:51:27 -0400 Subject: [PATCH 071/142] Label need not be an H3 --- .../comcol-page-browse-by/comcol-page-browse-by.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html index 1c73fbb3df..dd9e219afe 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -1,4 +1,4 @@ -

{{'browse.comcol.head' | translate}}

+
{{'browse.comcol.head' | translate}}
  • {{'browse.comcol.by.' + config.id | translate}} From b9baecd57378cc5286069bfb3130c180c6df60e8 Mon Sep 17 00:00:00 2001 From: lhenze Date: Wed, 21 Aug 2019 13:27:52 -0400 Subject: [PATCH 072/142] Bootstrap -- list group for links https://getbootstrap.com/docs/4.3/components/list-group/ --- .../comcol-page-browse-by.component.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html index dd9e219afe..6a39e24e5f 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -1,6 +1,4 @@
    {{'browse.comcol.head' | translate}}
    - + \ No newline at end of file From 16b3e8af35e3bc173d98a797698df98b1c6d8a4d Mon Sep 17 00:00:00 2001 From: lhenze Date: Wed, 21 Aug 2019 13:59:54 -0400 Subject: [PATCH 073/142] Comcol component - adding styles --- .../comcol-page-browse-by.component.html | 8 +++++--- .../comcol-page-browse-by.component.scss | 3 +++ .../comcol-page-browse-by.component.ts | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.scss diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html index 6a39e24e5f..e510d4df72 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -1,4 +1,6 @@ -
    {{'browse.comcol.head' | translate}}
    -
    - {{'browse.comcol.by.' + config.id | translate}} + \ No newline at end of file diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.scss b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.scss new file mode 100644 index 0000000000..7d3994681e --- /dev/null +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.scss @@ -0,0 +1,3 @@ +.comcol-browse { + margin: 2rem 0 ; +} \ No newline at end of file diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts index dcc7840bb4..8031d6373b 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -8,6 +8,7 @@ import { BrowseByTypeConfig } from '../../../config/browse-by-type-config.interf */ @Component({ selector: 'ds-comcol-page-browse-by', + styleUrls: ['./comcol-page-browse-by.component.scss'], templateUrl: './comcol-page-browse-by.component.html', }) export class ComcolPageBrowseByComponent implements OnInit { From 2a6e3c08cc3eb97a16505a943528ac906162cae0 Mon Sep 17 00:00:00 2001 From: lhenze Date: Wed, 21 Aug 2019 14:00:45 -0400 Subject: [PATCH 074/142] HTML Structure: Header and Footer tags --- .../collection-page.component.html | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 75e507508a..7654f93697 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -3,42 +3,49 @@ *ngVar="(collectionRD$ | async) as collectionRD">
    +
    - - - - - - - - - - - - - + + + + + + + + + + + + + + +
    + +
    + - + +
    -
    +

    {{'collection.page.browse.recent.head' | translate}}

    From 165dc2ccda3eb14d518eef15c3ee2901a6df9dde Mon Sep 17 00:00:00 2001 From: lhenze Date: Wed, 21 Aug 2019 14:03:34 -0400 Subject: [PATCH 075/142] Browse by pages: Adding Parent's Title and Description --- .../browse-by-metadata-page.component.html | 21 +++++++++++++++++++ .../browse-by-metadata-page.component.scss | 3 +++ 2 files changed, 24 insertions(+) diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html index c589c543d4..27bf8502c1 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html @@ -1,4 +1,25 @@
    + +
    +
    +
    + + + + + + + + + + +
    +
    +
    + +
    From 8f93d723a54e2568cebbac172cbc939cdaa0cf9f Mon Sep 17 00:00:00 2001 From: lhenze Date: Wed, 21 Aug 2019 16:07:34 -0400 Subject: [PATCH 078/142] Problem: Attempt to extend comcol-page-browse-by Cannot pass a URL. it is getting sanitized --- src/app/+collection-page/collection-page.component.html | 3 ++- .../comcol-page-browse-by/comcol-page-browse-by.component.html | 1 + .../comcol-page-browse-by/comcol-page-browse-by.component.ts | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 7ac0bb4959..c9bdbf21c8 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -16,7 +16,8 @@ - + +
    diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html index e510d4df72..07629ef791 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -1,6 +1,7 @@ \ No newline at end of file diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts index 8031d6373b..165f1aea97 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -16,6 +16,7 @@ export class ComcolPageBrowseByComponent implements OnInit { * The ID of the Community or Collection */ @Input() id: string; + @Input() url: string; /** * List of currently active browse configurations From b73ad7a8f09f9c5a08a91f4314722a490dcd0e73 Mon Sep 17 00:00:00 2001 From: lhenze Date: Thu, 22 Aug 2019 12:47:33 -0400 Subject: [PATCH 079/142] Managed to pass URL value and not have it be sanitized --- src/app/+collection-page/collection-page.component.html | 6 ++---- src/app/+collection-page/collection-page.component.ts | 2 ++ .../comcol-page-browse-by.component.html | 2 +- .../comcol-page-browse-by.component.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index c9bdbf21c8..895c030b48 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -17,7 +17,7 @@ - +
    @@ -35,9 +35,7 @@ - - - +
    diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index 41afbf2115..32908dd566 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -41,6 +41,8 @@ export class CollectionPageComponent implements OnInit { logoRD$: Observable>; paginationConfig: PaginationComponentOptions; sortConfig: SortOptions; + thisurl = "http://localhost:3000/collections/07a39181-b4bf-43cf-9975-5a1a01de7ac8"; + private paginationChanges$: Subject<{ paginationConfig: PaginationComponentOptions, sortConfig: SortOptions diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html index 07629ef791..38bbb2e2c1 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts index 165f1aea97..950d122425 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -16,8 +16,8 @@ export class ComcolPageBrowseByComponent implements OnInit { * The ID of the Community or Collection */ @Input() id: string; - @Input() url: string; - + @Input() thisurl: string; + /** * List of currently active browse configurations */ From 86d33894382029b8cab73c894491329063fdaecd Mon Sep 17 00:00:00 2001 From: lhenze Date: Fri, 23 Aug 2019 17:52:29 -0400 Subject: [PATCH 080/142] One approach --- .../comcol-page-browse-by.component.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts index 950d122425..7f49b767db 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -16,7 +16,21 @@ export class ComcolPageBrowseByComponent implements OnInit { * The ID of the Community or Collection */ @Input() id: string; - @Input() thisurl: string; + @Input() contentType: string; + + /** + * getPathfromType + */ + getPathfromType(t) { + if (t === "collection") { + t = "/collections/"; + } else if (t === "community") { + t = "/communities/"; + } else { + t = "/"; + } + return t; + } /** * List of currently active browse configurations @@ -28,6 +42,7 @@ export class ComcolPageBrowseByComponent implements OnInit { ngOnInit(): void { this.types = this.config.browseBy.types; + this.contentTypePath = this.getPathfromType(this.contentType) ; } } From 72c382d5c3ac29a70770c83dd2033522e56980d3 Mon Sep 17 00:00:00 2001 From: lhenze Date: Fri, 23 Aug 2019 17:59:08 -0400 Subject: [PATCH 081/142] Using "type" value to determine path & innerHTML of first button --- .../browse-by-metadata-page.component.html | 8 ++++++-- .../collection-page.component.html | 4 ++-- .../+community-page/community-page.component.html | 2 +- .../comcol-page-browse-by.component.html | 5 ++++- .../comcol-page-browse-by.component.ts | 14 -------------- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html index 27bf8502c1..3251550d84 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html @@ -13,9 +13,13 @@ [content]="parentContext.introductoryText" [hasInnerHtml]="true"> - + + + + + - +
    diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 895c030b48..c8bb75ceed 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -16,8 +16,8 @@ - - + +
    diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html index e429d224f2..05912f676d 100644 --- a/src/app/+community-page/community-page.component.html +++ b/src/app/+community-page/community-page.component.html @@ -4,7 +4,7 @@ - +
    {{'browse.comcol.head' | translate}}
    \ No newline at end of file diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts index 7f49b767db..9190bbc687 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -18,19 +18,6 @@ export class ComcolPageBrowseByComponent implements OnInit { @Input() id: string; @Input() contentType: string; - /** - * getPathfromType - */ - getPathfromType(t) { - if (t === "collection") { - t = "/collections/"; - } else if (t === "community") { - t = "/communities/"; - } else { - t = "/"; - } - return t; - } /** * List of currently active browse configurations @@ -42,7 +29,6 @@ export class ComcolPageBrowseByComponent implements OnInit { ngOnInit(): void { this.types = this.config.browseBy.types; - this.contentTypePath = this.getPathfromType(this.contentType) ; } } From 6f3a5448c525a1e6f08a53bd046defdda50a92c7 Mon Sep 17 00:00:00 2001 From: lhenze Date: Sat, 24 Aug 2019 11:08:22 -0400 Subject: [PATCH 082/142] Formatting only --- .../browse-by-metadata-page.component.html | 44 ++++++------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html index 3251550d84..a58e6d6eee 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html @@ -1,42 +1,26 @@
    -
    - - - - - - - - - - - - - - + + + + + + + + + + +
    - From 5963c6cd959990b0df29f06ef7b9915be8ab0604 Mon Sep 17 00:00:00 2001 From: lhenze Date: Sat, 24 Aug 2019 13:35:59 -0400 Subject: [PATCH 083/142] Handle added to header and copyright added to footer --- .../browse-by-metadata-page.component.html | 15 +++++++++++++-- .../collection-page.component.html | 7 ++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html index a58e6d6eee..a7b78cd428 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html @@ -2,15 +2,18 @@
    - + - + + + +
    @@ -24,4 +27,12 @@
    +
    +
    + + + + +
    +
    diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index c8bb75ceed..681b46839c 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -15,8 +15,10 @@ + + + - @@ -35,11 +37,10 @@ -
-
\ No newline at end of file +
From a35523f78d8981025813e07b6dfd895afb0a6c5b Mon Sep 17 00:00:00 2001 From: lhenze Date: Sat, 24 Aug 2019 13:36:29 -0400 Subject: [PATCH 084/142] Pagination -- Hide "Now Showing" when there are no results --- src/app/shared/pagination/pagination.component.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index 22a58dd7fc..2d01d651ff 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -2,8 +2,10 @@
- {{ 'pagination.showing.label' | translate }} - {{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}} + + {{ 'pagination.showing.label' | translate }} + {{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}} +
From 5d7a36e1e34f692e7ebde66bddc660a92d25ffbf Mon Sep 17 00:00:00 2001 From: lhenze Date: Mon, 26 Aug 2019 08:08:40 -0400 Subject: [PATCH 085/142] formatting only --- src/app/shared/pagination/pagination.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index c16a153026..d9c3033230 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -2,8 +2,8 @@
- {{ 'pagination.showing.label' | translate }} - {{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}} + {{ 'pagination.showing.label' | translate }} + {{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}}
@@ -16,7 +16,7 @@
-
+
From ba97ca9f3a9b794ef50105cdb58a96eb75bb4392 Mon Sep 17 00:00:00 2001 From: lhenze Date: Mon, 26 Aug 2019 08:46:32 -0400 Subject: [PATCH 086/142] Exploring using same approach on Community pages --- src/app/+community-page/community-page.component.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html index 05912f676d..452995d44b 100644 --- a/src/app/+community-page/community-page.component.html +++ b/src/app/+community-page/community-page.component.html @@ -3,8 +3,6 @@
- - + + + + + +
From 94050b655b1b14b6051accf9307dbd9fcacd0e11 Mon Sep 17 00:00:00 2001 From: lhenze Date: Mon, 26 Aug 2019 09:40:17 -0400 Subject: [PATCH 087/142] Fixing typo --- .../comcol-page-browse-by.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html index f6734a1ee3..54ec0faadf 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -4,7 +4,7 @@ Submission date -Subcommunties and collections +Subcommunities and collections {{'browse.comcol.by.' + config.id | translate}}
-
\ No newline at end of file +
From ba3778acaeae2490d01c3300377a8548dcc506e0 Mon Sep 17 00:00:00 2001 From: lhenze Date: Mon, 26 Aug 2019 10:36:08 -0400 Subject: [PATCH 088/142] Ally improvements --- .../comcol-page-browse-by.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html index 54ec0faadf..1607910c68 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -1,4 +1,4 @@ - + From f6094762708b84967f03af405add2df206273693 Mon Sep 17 00:00:00 2001 From: lhenze Date: Tue, 3 Sep 2019 17:19:13 -0400 Subject: [PATCH 089/142] New key --- resources/i18n/en.json | 1 + src/app/+collection-page/collection-page.component.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index f3236251cf..9262e9bbc4 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -132,6 +132,7 @@ "collection.form.tableofcontents": "News (HTML)", "collection.form.title": "Name", "collection.page.browse.recent.head": "Recent Submissions", + "collection.page.browse.recent.empty": "No recent items to show", "collection.page.license": "License", "collection.page.news": "News", "community.create.head": "Create a Community", diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 681b46839c..9b98fe81b5 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -30,7 +30,7 @@
From d6bcdda8a07b4c8c6da2d7f940f570e997b0ea2f Mon Sep 17 00:00:00 2001 From: lhenze Date: Wed, 4 Sep 2019 14:57:26 -0400 Subject: [PATCH 090/142] Adding i18n keys for the labels for handles --- resources/i18n/en.json | 2 ++ src/app/+collection-page/collection-page.component.html | 4 ++-- src/app/+community-page/community-page.component.html | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 234967231e..2d2eac5a8b 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -133,6 +133,7 @@ "collection.form.title": "Name", "collection.page.browse.recent.head": "Recent Submissions", "collection.page.browse.recent.empty": "No items to show", + "collection.page.handle": "Handle", "collection.page.license": "License", "collection.page.news": "News", "community.create.head": "Create a Community", @@ -151,6 +152,7 @@ "community.form.rights": "Copyright text (HTML)", "community.form.tableofcontents": "News (HTML)", "community.form.title": "Name", + "community.page.handle": "Handle", "community.page.license": "License", "community.page.news": "News", "community.sub-collection-list.head": "Collections of this Community", diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 9b98fe81b5..450fea7370 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -13,10 +13,10 @@ - + - + diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html index 452995d44b..622b6f10bf 100644 --- a/src/app/+community-page/community-page.component.html +++ b/src/app/+community-page/community-page.component.html @@ -25,7 +25,7 @@ [hasInnerHtml]="true"> - + From 2845578d070607346afd4e988df4c804c7b09186 Mon Sep 17 00:00:00 2001 From: lhenze Date: Wed, 4 Sep 2019 16:13:39 -0400 Subject: [PATCH 091/142] Tidying --- src/app/+collection-page/collection-page.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index 32908dd566..41afbf2115 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -41,8 +41,6 @@ export class CollectionPageComponent implements OnInit { logoRD$: Observable>; paginationConfig: PaginationComponentOptions; sortConfig: SortOptions; - thisurl = "http://localhost:3000/collections/07a39181-b4bf-43cf-9975-5a1a01de7ac8"; - private paginationChanges$: Subject<{ paginationConfig: PaginationComponentOptions, sortConfig: SortOptions From 20bc4d1c2d1c37913d21cc6f05df7c3c2808a863 Mon Sep 17 00:00:00 2001 From: lhenze Date: Wed, 4 Sep 2019 17:12:52 -0400 Subject: [PATCH 092/142] tidying - whitespace only --- .../browse-by-metadata-page.component.html | 14 ++- .../collection-page.component.html | 105 ++++++++++++------ .../pagination/pagination.component.html | 6 +- 3 files changed, 82 insertions(+), 43 deletions(-) diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html index a7b78cd428..2539b60afc 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html @@ -21,9 +21,17 @@
diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 450fea7370..2b91c8ec3a 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -1,46 +1,77 @@
-
-
-
-
- - - - - - - - - - - - - - - - - -
- -
-

{{'collection.page.browse.recent.head' | translate}}

- - -
- - - -
+
+
+
+
+ + + + + + + + + + + + + + + + + + +
+ +
+

{{'collection.page.browse.recent.head' | translate}}

+ + +
+ + + +
+ + + + - +
- - + +
diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index d9c3033230..c16a153026 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -2,8 +2,8 @@
- {{ 'pagination.showing.label' | translate }} - {{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}} + {{ 'pagination.showing.label' | translate }} + {{ 'pagination.showing.detail' | translate:getShowingDetails(collectionSize)}}
@@ -16,7 +16,7 @@
-
+
From f443a2bcce6c6b7d8ed5e02f2cf91104f49959cc Mon Sep 17 00:00:00 2001 From: "L. Henze" Date: Thu, 5 Sep 2019 10:52:05 -0400 Subject: [PATCH 093/142] Excess whitespace removed --- .../comcol-page-browse-by/comcol-page-browse-by.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts index 9190bbc687..6821b52d63 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -17,8 +17,6 @@ export class ComcolPageBrowseByComponent implements OnInit { */ @Input() id: string; @Input() contentType: string; - - /** * List of currently active browse configurations */ From 9e8dd6f3496a0b5f9a63c881f8c9803524e769f0 Mon Sep 17 00:00:00 2001 From: lhenze Date: Sun, 8 Sep 2019 21:38:08 -0400 Subject: [PATCH 094/142] Adding Var Directive to tests as needed --- .../+browse-by-date-page/browse-by-date-page.component.spec.ts | 3 ++- .../browse-by-metadata-page.component.spec.ts | 3 ++- .../browse-by-title-page.component.spec.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts index 78f5d52511..a507e8e585 100644 --- a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts +++ b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.spec.ts @@ -19,6 +19,7 @@ import { ENV_CONFIG, GLOBAL_CONFIG } from '../../../config'; import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model'; import { toRemoteData } from '../+browse-by-metadata-page/browse-by-metadata-page.component.spec'; import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; +import { VarDirective } from '../../shared/utils/var.directive'; describe('BrowseByDatePageComponent', () => { let comp: BrowseByDatePageComponent; @@ -69,7 +70,7 @@ describe('BrowseByDatePageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], - declarations: [BrowseByDatePageComponent, EnumKeysPipe], + declarations: [BrowseByDatePageComponent, EnumKeysPipe, VarDirective], providers: [ { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, { provide: ActivatedRoute, useValue: activatedRouteStub }, diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts index 927effd303..553bd00f56 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.spec.ts @@ -23,6 +23,7 @@ import { MockRouter } from '../../shared/mocks/mock-router'; import { ResourceType } from '../../core/shared/resource-type'; import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; import { BrowseEntry } from '../../core/shared/browse-entry.model'; +import { VarDirective } from '../../shared/utils/var.directive'; describe('BrowseByMetadataPageComponent', () => { let comp: BrowseByMetadataPageComponent; @@ -86,7 +87,7 @@ describe('BrowseByMetadataPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], - declarations: [BrowseByMetadataPageComponent, EnumKeysPipe], + declarations: [BrowseByMetadataPageComponent, EnumKeysPipe, VarDirective], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: BrowseService, useValue: mockBrowseService }, diff --git a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts index 3bc69e5fcb..90623eb3c7 100644 --- a/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts +++ b/src/app/+browse-by/+browse-by-title-page/browse-by-title-page.component.spec.ts @@ -18,6 +18,7 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { BrowseService } from '../../core/browse/browse.service'; import { MockRouter } from '../../shared/mocks/mock-router'; import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; +import { VarDirective } from '../../shared/utils/var.directive'; describe('BrowseByTitlePageComponent', () => { let comp: BrowseByTitlePageComponent; @@ -64,7 +65,7 @@ describe('BrowseByTitlePageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], - declarations: [BrowseByTitlePageComponent, EnumKeysPipe], + declarations: [BrowseByTitlePageComponent, EnumKeysPipe, VarDirective], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: BrowseService, useValue: mockBrowseService }, From 02b007a0f6d246fee831f851d8feb78934e322ed Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 10 Sep 2019 16:29:15 +0200 Subject: [PATCH 095/142] 62589: Exclude owning collection + redirect to first tab after mapping + page reload fix --- .../collection-item-mapper.component.html | 6 ++--- .../collection-item-mapper.component.ts | 18 +++++++++++-- .../item-collection-mapper.component.html | 6 ++--- .../item-collection-mapper.component.ts | 27 ++++++++++++++++--- .../collection-select.component.html | 2 +- 5 files changed, 46 insertions(+), 13 deletions(-) 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 9b7216c92a..9e9f9fd8e1 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 @@ -5,8 +5,8 @@

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

- - + +
- +
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 554696c63f..059bd098d4 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 @@ -1,6 +1,6 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, OnInit, ViewChild } from '@angular/core'; import { fadeIn, fadeInOut } from '../../shared/animations/fade'; import { ActivatedRoute, Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; @@ -44,6 +44,12 @@ import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.comp */ export class CollectionItemMapperComponent implements OnInit { + /** + * A view on the tabset element + * Used to switch tabs programmatically + */ + @ViewChild('tabs') tabs; + /** * The collection to map items to */ @@ -180,8 +186,9 @@ export class CollectionItemMapperComponent implements OnInit { this.notificationsService.error(head, content); }); } - // Force an update on all lists + // Force an update on all lists and switch back to the first tab this.shouldUpdate$.next(true); + this.switchToFirstTab(); }); } @@ -228,4 +235,11 @@ export class CollectionItemMapperComponent implements OnInit { } } + /** + * Switch the view to focus on the first tab + */ + switchToFirstTab() { + this.tabs.select('browseTab'); + } + } 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 55619bdc96..97cbb27871 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 @@ -5,8 +5,8 @@

{{'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 803083c428..3a85a75659 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,6 +1,6 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core'; import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model'; import { RemoteData } from '../../../core/data/remote-data'; @@ -34,6 +34,13 @@ import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; * Component for mapping collections to an item */ export class ItemCollectionMapperComponent implements OnInit { + + /** + * A view on the tabset element + * Used to switch tabs programmatically + */ + @ViewChild('tabs') tabs; + /** * The item to map to collections */ @@ -92,14 +99,18 @@ export class ItemCollectionMapperComponent implements OnInit { switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id)) ); + const owningCollectionRD$ = this.itemRD$.pipe( + switchMap((itemRD: RemoteData) => itemRD.payload.owningCollection) + ); const itemCollectionsAndOptions$ = observableCombineLatest( this.itemCollectionsRD$, + owningCollectionRD$, this.searchOptions$ ); this.mappedCollectionsRD$ = itemCollectionsAndOptions$.pipe( - switchMap(([itemCollectionsRD, searchOptions]) => { + switchMap(([itemCollectionsRD, owningCollectionRD, searchOptions]) => { return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), { - query: this.buildQuery(itemCollectionsRD.payload.page, searchOptions.query), + query: this.buildQuery([...itemCollectionsRD.payload.page, owningCollectionRD.payload], searchOptions.query), dsoType: DSpaceObjectType.COLLECTION })); }), @@ -190,8 +201,9 @@ export class ItemCollectionMapperComponent implements OnInit { this.notificationsService.error(head, content); }); } - // Force an update on all lists + // Force an update on all lists and switch back to the first tab this.shouldUpdate$.next(true); + this.switchToFirstTab(); }); } @@ -251,4 +263,11 @@ export class ItemCollectionMapperComponent implements OnInit { } } + /** + * Switch the view to focus on the first tab + */ + switchToFirstTab() { + this.tabs.select('browseTab'); + } + } 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 5a1c98fcf5..e7c20d268c 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 @@ -1,6 +1,6 @@ Date: Wed, 11 Sep 2019 10:44:03 +0200 Subject: [PATCH 096/142] 62589: Fixed import --- .../collection-item-mapper.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts index 046127da4c..dcbce697c6 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts @@ -39,8 +39,8 @@ import { VarDirective } from '../../shared/utils/var.directive'; import { Observable } from 'rxjs/internal/Observable'; import { of as observableOf, of } from 'rxjs/internal/observable/of'; import { RestResponse } from '../../core/cache/response.models'; -import { RouteService } from '../../shared/services/route.service'; import { SearchFixedFilterService } from '../../+search-page/search-filters/search-filter/search-fixed-filter.service'; +import { RouteService } from '../../core/services/route.service'; describe('CollectionItemMapperComponent', () => { let comp: CollectionItemMapperComponent; From 647bfb4e0d859a54344fcf1cc5d2475203e2cf06 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 16 Sep 2019 14:06:54 +0200 Subject: [PATCH 097/142] 62589: Import fix --- src/app/core/data/item-data.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 9f2621d124..cec60f3305 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -1,4 +1,4 @@ -import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators'; +import { distinctUntilChanged, filter, find, map, switchMap, take } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -17,7 +17,7 @@ import { FindAllOptions, MappedCollectionsRequest, PatchRequest, - PostRequest, + PostRequest, PutRequest, RestRequest } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; From 01212750bf15c59cb7a6a6979efe67039fb66b50 Mon Sep 17 00:00:00 2001 From: "L. Henze" Date: Tue, 17 Sep 2019 17:08:09 -0400 Subject: [PATCH 098/142] handle component --- .../comcol-page-handle.component.html | 4 ++++ .../comcol-page-handle.component.scss | 0 .../comcol-page-handle.component.ts | 20 +++++++++++++++++++ src/app/shared/shared.module.ts | 2 ++ 4 files changed, 26 insertions(+) create mode 100644 src/app/shared/comcol-page-handle/comcol-page-handle.component.html create mode 100644 src/app/shared/comcol-page-handle/comcol-page-handle.component.scss create mode 100644 src/app/shared/comcol-page-handle/comcol-page-handle.component.ts diff --git a/src/app/shared/comcol-page-handle/comcol-page-handle.component.html b/src/app/shared/comcol-page-handle/comcol-page-handle.component.html new file mode 100644 index 0000000000..52d6b4dd16 --- /dev/null +++ b/src/app/shared/comcol-page-handle/comcol-page-handle.component.html @@ -0,0 +1,4 @@ +
+

{{ title | translate }}

+ +
diff --git a/src/app/shared/comcol-page-handle/comcol-page-handle.component.scss b/src/app/shared/comcol-page-handle/comcol-page-handle.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/comcol-page-handle/comcol-page-handle.component.ts b/src/app/shared/comcol-page-handle/comcol-page-handle.component.ts new file mode 100644 index 0000000000..61ab083246 --- /dev/null +++ b/src/app/shared/comcol-page-handle/comcol-page-handle.component.ts @@ -0,0 +1,20 @@ +import { Component, Input } from '@angular/core'; + +/** + * This component renders the value of "handle" + */ + +@Component({ + selector: 'ds-comcol-page-handle', + styleUrls: ['./comcol-page-handle.component.scss'], + templateUrl: './comcol-page-handle.component.html' +}) +export class ComcolPageHandleComponent { + + // Optional title + @Input() title: string; + + // The content to render. Might be html + @Input() content: string; + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2367652dd3..d18fbd2708 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -36,6 +36,7 @@ import { WrapperGridElementComponent } from './object-grid/wrapper-grid-element/ import { ObjectGridComponent } from './object-grid/object-grid.component'; import { ObjectCollectionComponent } from './object-collection/object-collection.component'; import { ComcolPageContentComponent } from './comcol-page-content/comcol-page-content.component'; +import { ComcolPageHandleComponent } from './comcol-page-handle/comcol-page-handle.component'; import { ComcolPageHeaderComponent } from './comcol-page-header/comcol-page-header.component'; import { ComcolPageLogoComponent } from './comcol-page-logo/comcol-page-logo.component'; import { ErrorComponent } from './error/error.component'; @@ -192,6 +193,7 @@ const COMPONENTS = [ UserMenuComponent, ChipsComponent, ComcolPageContentComponent, + ComcolPageHandleComponent, ComcolPageHeaderComponent, ComcolPageLogoComponent, ComColFormComponent, From af3005c863a65957d6695ccc8a8eb126adedc86d Mon Sep 17 00:00:00 2001 From: "L. Henze" Date: Tue, 17 Sep 2019 17:19:34 -0400 Subject: [PATCH 099/142] wip --- resources/i18n/en.json5 | 4 ++ .../browse-by-metadata-page.component.html | 22 +++++++ .../collection-page.component.html | 60 ++++++++++++------- .../community-page.component.html | 8 +++ .../comcol-page-browse-by.component.html | 14 +++-- 5 files changed, 79 insertions(+), 29 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 6570d5bf3a..72dee36c23 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -140,8 +140,10 @@ "collection.form.title": "Name", "collection.page.browse.recent.head": "Recent Submissions", "collection.page.browse.recent.empty": "No items to show", + "collection.page.handle": "Collection Handle", "collection.page.license": "License", "collection.page.news": "News", + "collection.landing.head": "Submission date", "community.create.head": "Create a Community", "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}", @@ -159,8 +161,10 @@ "community.form.rights": "Copyright text (HTML)", "community.form.tableofcontents": "News (HTML)", "community.form.title": "Name", + "community.page.handle": "Community Handle", "community.page.license": "License", "community.page.news": "News", + "community.landing.head": "Subcommunities and Collections", "community.sub-collection-list.head": "Collections of this Community", "community.sub-community-list.head": "Communities of this Community", diff --git a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html index c589c543d4..60e8446200 100644 --- a/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/+browse-by/+browse-by-metadata-page/browse-by-metadata-page.component.html @@ -1,4 +1,26 @@
+
+
+
+ + + + + + + + + + + + + + +
+
+
+ + +
diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html index e429d224f2..3518c06017 100644 --- a/src/app/+community-page/community-page.component.html +++ b/src/app/+community-page/community-page.component.html @@ -26,6 +26,14 @@ [content]="communityPayload.copyrightText" [hasInnerHtml]="true"> + + + + + +
diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html index 1c73fbb3df..7f29ab58c7 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -1,6 +1,8 @@ -

{{'browse.comcol.head' | translate}}

- +

{{'browse.comcol.head' | translate}}

+ From 6b55f944058ba56e48a17720c0bb2288145a5f7f Mon Sep 17 00:00:00 2001 From: "L. Henze" Date: Tue, 17 Sep 2019 17:29:37 -0400 Subject: [PATCH 100/142] Update comcol-page-browse-by.component.ts --- .../comcol-page-browse-by/comcol-page-browse-by.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts index dcc7840bb4..ef9b97e4d2 100644 --- a/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts +++ b/src/app/shared/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -15,7 +15,7 @@ export class ComcolPageBrowseByComponent implements OnInit { * The ID of the Community or Collection */ @Input() id: string; - + @Input() contentType: string; /** * List of currently active browse configurations */ From 4a77dec8709e66c918cb38cec6cbbfcf84320070 Mon Sep 17 00:00:00 2001 From: "L. Henze" Date: Tue, 17 Sep 2019 17:38:46 -0400 Subject: [PATCH 101/142] Fixing some work that was rolled back --- .../+collection-page/collection-page.component.html | 4 +--- .../+community-page/community-page.component.html | 12 ++++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index af3c5defc9..de1b1d4795 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -13,8 +13,6 @@ - -