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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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/115] 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 02b007a0f6d246fee831f851d8feb78934e322ed Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 10 Sep 2019 16:29:15 +0200 Subject: [PATCH 070/115] 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 071/115] 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 072/115] 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 05d85c2d18be99d16fcd14410e46c21e2c1ae8c0 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Mon, 23 Sep 2019 14:32:57 -0700 Subject: [PATCH 073/115] create docker compose files in code base --- docker-compose-travis.yml | 53 +++++++++++++++++++ docker-compose.yml | 80 +++++++++++++++++++++++++++++ to_be_determined/environment.dev.js | 16 ++++++ to_be_determined/local.cfg | 6 +++ 4 files changed, 155 insertions(+) create mode 100644 docker-compose-travis.yml create mode 100644 docker-compose.yml create mode 100644 to_be_determined/environment.dev.js create mode 100644 to_be_determined/local.cfg diff --git a/docker-compose-travis.yml b/docker-compose-travis.yml new file mode 100644 index 0000000000..5a9ea9c3d6 --- /dev/null +++ b/docker-compose-travis.yml @@ -0,0 +1,53 @@ +networks: + dspacenet: {} +services: + dspace: + container_name: dspace + depends_on: + - dspacedb + image: dspace/dspace:dspace-7_x-jdk8-test + networks: + dspacenet: {} + ports: + - published: 8080 + target: 8080 + stdin_open: true + tty: true + volumes: + - assetstore:/dspace/assetstore + - ./to_be_determined:/dspace/config/local.cfg + dspacedb: + container_name: dspacedb + environment: + LOADSQL: https://www.dropbox.com/s/xh3ack0vg0922p2/configurable-entities-2019-05-08.sql?dl=1 + PGDATA: /pgdata + image: dspace/dspace-postgres-pgcrypto:loadsql + networks: + dspacenet: {} + stdin_open: true + tty: true + volumes: + - pgdata:/pgdata + dspacesolr: + container_name: dspacesolr + image: dspace/dspace-solr + networks: + dspacenet: {} + ports: + - published: 8983 + target: 8983 + stdin_open: true + tty: true + volumes: + - solr_authority:/opt/solr/server/solr/authority/data + - solr_oai:/opt/solr/server/solr/oai/data + - solr_search:/opt/solr/server/solr/search/data + - solr_statistics:/opt/solr/server/solr/statistics/data +version: '3.7' +volumes: + assetstore: {} + pgdata: {} + solr_authority: {} + solr_oai: {} + solr_search: {} + solr_statistics: {} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..b83b1b1e66 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,80 @@ +version: '3.7' +networks: + dspacenet: +services: + dspace: + container_name: dspace + image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-jdk8-test}" + depends_on: + - dspacedb + networks: + dspacenet: + ports: + - published: 8080 + target: 8080 + stdin_open: true + tty: true + volumes: + - assetstore:/dspace/assetstore + - ./to_be_determined/local.cfg:/dspace/config/local.cfg + dspace-angular: + container_name: dspace-angular + depends_on: + - dspace + environment: + DSPACE_HOST: dspace-angular + DSPACE_NAMESPACE: / + DSPACE_PORT: '3000' + DSPACE_REST_HOST: dspace + DSPACE_REST_NAMESPACE: / + DSPACE_REST_PORT: '8080' + DSPACE_REST_SSL: "false" + DSPACE_SSL: "false" + image: dspace/dspace-angular:latest + build: + context: . + dockerfile: Dockerfile + networks: + dspacenet: {} + ports: + - published: 3000 + target: 3000 + - published: 9876 + target: 9876 + stdin_open: true + tty: true + volumes: + - ./to_be_determined/environment.dev.js:/app/config/environment.dev.js + dspacedb: + container_name: dspacedb + environment: + PGDATA: /pgdata + image: dspace/dspace-postgres-pgcrypto + networks: + dspacenet: + stdin_open: true + tty: true + volumes: + - pgdata:/pgdata + dspacesolr: + container_name: dspacesolr + image: dspace/dspace-solr + networks: + dspacenet: + ports: + - published: 8983 + target: 8983 + stdin_open: true + tty: true + volumes: + - solr_authority:/opt/solr/server/solr/authority/data + - solr_oai:/opt/solr/server/solr/oai/data + - solr_search:/opt/solr/server/solr/search/data + - solr_statistics:/opt/solr/server/solr/statistics/data +volumes: + assetstore: + pgdata: + solr_authority: + solr_oai: + solr_search: + solr_statistics: diff --git a/to_be_determined/environment.dev.js b/to_be_determined/environment.dev.js new file mode 100644 index 0000000000..f88506012f --- /dev/null +++ b/to_be_determined/environment.dev.js @@ -0,0 +1,16 @@ +/* + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +module.exports = { + rest: { + ssl: false, + host: 'localhost', + port: 8080, + // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript + nameSpace: '/server/api' + } +}; diff --git a/to_be_determined/local.cfg b/to_be_determined/local.cfg new file mode 100644 index 0000000000..6692b13658 --- /dev/null +++ b/to_be_determined/local.cfg @@ -0,0 +1,6 @@ +dspace.dir=/dspace +db.url=jdbc:postgresql://dspacedb:5432/dspace +dspace.hostname=dspace +dspace.baseUrl=http://localhost:8080 +dspace.name=DSpace Started with Docker Compose +solr.server=http://dspacesolr:8983/solr From 314c33b011d9fc881b036a0c474bfe65ffa15759 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Mon, 23 Sep 2019 16:56:07 -0700 Subject: [PATCH 074/115] reference compose files in travis.yml --- .travis.yml | 7 +++---- to_be_determined/cli.assetstore.yml | 20 ++++++++++++++++++++ to_be_determined/cli.yml | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 to_be_determined/cli.assetstore.yml create mode 100644 to_be_determined/cli.yml diff --git a/.travis.yml b/.travis.yml index 3abd52c25f..8a86848f1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,13 +17,12 @@ before_install: - curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose - chmod +x docker-compose - sudo mv docker-compose /usr/local/bin - - git clone https://github.com/DSpace-Labs/DSpace-Docker-Images.git install: # Start up DSpace 7 using the entities database dump - - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose-v2/d7.travis.ci.yml up -d + - docker-compose -f docker-compose-travis.yml up -d # Use the dspace-cli image to populate the assetstore. Trigger a discovery and oai update - - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose-v2/d7.cli.yml -f DSpace-Docker-Images/docker-compose-files/dspace-compose-v2/d7.cli.assetstore.yml run --rm dspace-cli + - docker-compose -f to_be_determined/cli.yml -f to_be_determined/cli.assetstore.yml run --rm dspace-cli - travis_retry yarn install before_script: @@ -32,7 +31,7 @@ before_script: #- curl http://localhost:8080/ after_script: - - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose-v2/d7.travis.ci.yml down + - docker-compose -f docker-compose-travis.yml down addons: apt: diff --git a/to_be_determined/cli.assetstore.yml b/to_be_determined/cli.assetstore.yml new file mode 100644 index 0000000000..0c6fcb2030 --- /dev/null +++ b/to_be_determined/cli.assetstore.yml @@ -0,0 +1,20 @@ +version: "3.7" + +services: + dspace-cli: + environment: + - LOADASSETS=https://www.dropbox.com/s/zv7lj8j2lp3egjs/assetstore.tar.gz?dl=1 + entrypoint: + - /bin/bash + - '-c' + - | + if [ ! -z $${LOADASSETS} ] + then + curl $${LOADASSETS} -L -s --output /tmp/assetstore.tar.gz + cd /dspace + tar xvfz /tmp/assetstore.tar.gz + fi + + /dspace/bin/dspace index-discovery + /dspace/bin/dspace oai import + /dspace/bin/dspace oai clean-cache diff --git a/to_be_determined/cli.yml b/to_be_determined/cli.yml new file mode 100644 index 0000000000..ea5e3e0595 --- /dev/null +++ b/to_be_determined/cli.yml @@ -0,0 +1,22 @@ +version: "3.7" + +services: + dspace-cli: + image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}" + container_name: dspace-cli + #environment: + volumes: + - "assetstore:/dspace/assetstore" + - "./local.cfg:/dspace/config/local.cfg" + entrypoint: /dspace/bin/dspace + command: help + networks: + - dspacenet + tty: true + stdin_open: true + +volumes: + assetstore: + +networks: + dspacenet: From dfcb5abd493b8fb7115728b0701cf5c9e29050b7 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 25 Sep 2019 14:56:57 -0700 Subject: [PATCH 075/115] change path to component compose files --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8a86848f1b..12627a632e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ install: # Start up DSpace 7 using the entities database dump - docker-compose -f docker-compose-travis.yml up -d # Use the dspace-cli image to populate the assetstore. Trigger a discovery and oai update - - docker-compose -f to_be_determined/cli.yml -f to_be_determined/cli.assetstore.yml run --rm dspace-cli + - docker-compose -f ./to_be_determined/cli.yml -f ./to_be_determined/cli.assetstore.yml run --rm dspace-cli - travis_retry yarn install before_script: From 71287b6b41f32b0f27b9467583dca7a610b43c95 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 25 Sep 2019 15:03:46 -0700 Subject: [PATCH 076/115] travis troubleshooting --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 12627a632e..2a47199dd1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ install: # Start up DSpace 7 using the entities database dump - docker-compose -f docker-compose-travis.yml up -d # Use the dspace-cli image to populate the assetstore. Trigger a discovery and oai update - - docker-compose -f ./to_be_determined/cli.yml -f ./to_be_determined/cli.assetstore.yml run --rm dspace-cli + #- docker-compose -f ./to_be_determined/cli.yml -f ./to_be_determined/cli.assetstore.yml run --rm dspace-cli - travis_retry yarn install before_script: From 5dfc2fffcaec60d6d1a48713c4a905bc75892510 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Wed, 25 Sep 2019 15:09:36 -0700 Subject: [PATCH 077/115] fix compose file --- .travis.yml | 2 +- docker-compose-travis.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2a47199dd1..12627a632e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ install: # Start up DSpace 7 using the entities database dump - docker-compose -f docker-compose-travis.yml up -d # Use the dspace-cli image to populate the assetstore. Trigger a discovery and oai update - #- docker-compose -f ./to_be_determined/cli.yml -f ./to_be_determined/cli.assetstore.yml run --rm dspace-cli + - docker-compose -f ./to_be_determined/cli.yml -f ./to_be_determined/cli.assetstore.yml run --rm dspace-cli - travis_retry yarn install before_script: diff --git a/docker-compose-travis.yml b/docker-compose-travis.yml index 5a9ea9c3d6..de1278d90d 100644 --- a/docker-compose-travis.yml +++ b/docker-compose-travis.yml @@ -15,7 +15,7 @@ services: tty: true volumes: - assetstore:/dspace/assetstore - - ./to_be_determined:/dspace/config/local.cfg + - ./to_be_determined/local.cfg:/dspace/config/local.cfg dspacedb: container_name: dspacedb environment: From 145ed346c85c4ae13b26e834f9c17febc5c6cea7 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 27 Sep 2019 16:40:47 +0200 Subject: [PATCH 078/115] 62589: Feedback improvements and fixes --- resources/i18n/en.json5 | 4 +-- .../collection-item-mapper.component.html | 14 +++++---- .../collection-item-mapper.component.ts | 15 +++++++++- .../item-collection-mapper.component.html | 14 +++++---- .../item-collection-mapper.component.ts | 15 +++++++++- src/app/+item-page/item-page.module.ts | 2 +- .../collection-select.component.html | 6 +++- .../item-select/item-select.component.html | 6 +++- .../item-select/item-select.component.ts | 8 ++++- .../object-select/object-select.component.ts | 29 ++++++++++++++++++- 10 files changed, 94 insertions(+), 19 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index d9e380d198..ba3d1549c1 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -139,6 +139,7 @@ "collection.form.tableofcontents": "News (HTML)", "collection.form.title": "Name", + "collection.item-mapper.cancel": "Cancel", "collection.item-mapper.collection": "Collection: \"{{name}}\"", "collection.item-mapper.confirm": "Map selected items", "collection.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.", @@ -152,7 +153,6 @@ "collection.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.", "collection.item-mapper.notifications.unmap.success.head": "Remove mapping completed", "collection.item-mapper.remove": "Remove selected item mappings", - "collection.item-mapper.return": "Return", "collection.item-mapper.tabs.browse": "Browse", "collection.item-mapper.tabs.map": "Map", @@ -252,6 +252,7 @@ "item.edit.item-mapper.buttons.add": "Map item to selected collections", "item.edit.item-mapper.buttons.remove": "Remove item's mapping for selected collections", + "item.edit.item-mapper.cancel": "Cancel", "item.edit.item-mapper.description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.", "item.edit.item-mapper.head": "Item Mapper - Map Item to Collections", "item.edit.item-mapper.item": "Item: \"{{name}}\"", @@ -263,7 +264,6 @@ "item.edit.item-mapper.notifications.remove.error.head": "Removal of mapping errors", "item.edit.item-mapper.notifications.remove.success.content": "Successfully removed mapping of item to {{amount}} collections.", "item.edit.item-mapper.notifications.remove.success.head": "Removal of mapping completed", - "item.edit.item-mapper.return": "Return", "item.edit.item-mapper.tabs.browse": "Browse", "item.edit.item-mapper.tabs.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 9e9f9fd8e1..23e23b5c25 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,8 +14,11 @@ [dsoRD$]="collectionItemsRD$" [paginationOptions]="(searchOptions$ | async)?.pagination" [confirmButton]="'collection.item-mapper.remove'" + [cancelButton]="'collection.item-mapper.cancel'" + [dangerConfirm]="true" [hideCollection]="true" - (confirm)="mapItems($event, true)"> + (confirm)="mapItems($event, true)" + (cancel)="onCancel()">
@@ -26,7 +29,8 @@ + [currentUrl]="'./'" + [inPlaceSearch]="true">
@@ -37,13 +41,13 @@ [dsoRD$]="mappedItemsRD$" [paginationOptions]="(searchOptions$ | async)?.pagination" [confirmButton]="'collection.item-mapper.confirm'" - (confirm)="mapItems($event)"> + [cancelButton]="'collection.item-mapper.cancel'" + (confirm)="mapItems($event)" + (cancel)="onCancel()">
- -
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 059bd098d4..417b044277 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,7 +9,7 @@ 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 { map, switchMap, take, tap } from 'rxjs/operators'; -import { getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; +import { getRemoteDataPayload, 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'; @@ -242,4 +242,17 @@ export class CollectionItemMapperComponent implements OnInit { this.tabs.select('browseTab'); } + /** + * When a cancel event is fired, return to the collection page + */ + onCancel() { + this.collectionRD$.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + take(1) + ).subscribe((collection: Collection) => { + this.router.navigate(['/collections/', collection.id]) + }); + } + } 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 97cbb27871..d4433cd4a2 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 @@ -14,7 +14,10 @@ [dsoRD$]="itemCollectionsRD$" [paginationOptions]="(searchOptions$ | async)?.pagination" [confirmButton]="'item.edit.item-mapper.buttons.remove'" - (confirm)="removeMappings($event)"> + [cancelButton]="'item.edit.item-mapper.cancel'" + [dangerConfirm]="true" + (confirm)="removeMappings($event)" + (cancel)="onCancel()">
@@ -24,7 +27,8 @@
+ [currentUrl]="'./'" + [inPlaceSearch]="true">
@@ -36,13 +40,13 @@ [paginationOptions]="(searchOptions$ | async)?.pagination" [sortOptions]="(searchOptions$ | async)?.sort" [confirmButton]="'item.edit.item-mapper.buttons.add'" - (confirm)="mapCollections($event)">
+ [cancelButton]="'item.edit.item-mapper.cancel'" + (confirm)="mapCollections($event)" + (cancel)="onCancel()">
- -
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 3a85a75659..b8073edb39 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 @@ -7,7 +7,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, toDSpaceObjectListRD } from '../../../core/shared/operators'; +import { getRemoteDataPayload, 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'; @@ -270,4 +270,17 @@ export class ItemCollectionMapperComponent implements OnInit { this.tabs.select('browseTab'); } + /** + * When a cancel event is fired, return to the item page + */ + onCancel() { + this.itemRD$.pipe( + getSucceededRemoteData(), + getRemoteDataPayload(), + take(1) + ).subscribe((item: Item) => { + this.router.navigate(['/items/', item.id]) + }); + } + } diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts index f510ccf19b..2a5d0b6da7 100644 --- a/src/app/+item-page/item-page.module.ts +++ b/src/app/+item-page/item-page.module.ts @@ -31,8 +31,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field imports: [ CommonModule, SharedModule, - EditItemPageModule, ItemPageRoutingModule, + EditItemPageModule, SearchPageModule ], declarations: [ 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 e7c20d268c..3e2ecdaebf 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 @@ -24,5 +24,9 @@
- +
+ + + +
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 a54afed643..5dbc88e55d 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 @@ -28,5 +28,9 @@
- +
+ + + +
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 2cd5b502df..7dd8239960 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,4 +1,4 @@ -import { Component} from '@angular/core'; +import { Component, Input } from '@angular/core'; import { Item } from '../../../core/shared/item.model'; import { ObjectSelectService } from '../object-select.service'; import { ObjectSelectComponent } from '../object-select/object-select.component'; @@ -14,6 +14,12 @@ import { isNotEmpty } from '../../empty.util'; */ export class ItemSelectComponent extends ObjectSelectComponent { + /** + * Whether or not to hide the collection column + */ + @Input() + hideCollection = false; + constructor(protected objectSelectService: ObjectSelectService) { super(objectSelectService); } 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 70099e7328..e3a50b8024 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 @@ -12,6 +12,9 @@ import { SortOptions } from '../../../core/cache/models/sort-options.model'; */ export abstract class ObjectSelectComponent implements OnInit, OnDestroy { + /** + * A unique key used for the object select service + */ @Input() key: string; @@ -40,8 +43,18 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro @Input() confirmButton: string; + /** + * The message key used for the cancel button + * @type {string} + */ @Input() - hideCollection = false; + cancelButton: string; + + /** + * An event fired when the cancel button is clicked + */ + @Output() + cancel = new EventEmitter(); /** * EventEmitter to return the selected UUIDs when the confirm button is pressed @@ -50,6 +63,13 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro @Output() confirm: EventEmitter = new EventEmitter(); + /** + * Whether or not to render the confirm button as danger (for example if confirm deletes objects) + * Defaults to false + */ + @Input() + dangerConfirm = false; + /** * The list of selected UUIDs */ @@ -96,4 +116,11 @@ export abstract class ObjectSelectComponent implements OnInit, OnDestro }); } + /** + * Fire a cancel event + */ + onCancel() { + this.cancel.emit(); + } + } From c060099ff8636ad6ab95d262eb2c18e3cd644f9e Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Sun, 29 Sep 2019 10:44:42 -0700 Subject: [PATCH 079/115] run angular on its own --- docker-compose.yml | 47 ---------------------------------------------- 1 file changed, 47 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b83b1b1e66..3573076724 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,21 +2,6 @@ version: '3.7' networks: dspacenet: services: - dspace: - container_name: dspace - image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-dspace-7_x-jdk8-test}" - depends_on: - - dspacedb - networks: - dspacenet: - ports: - - published: 8080 - target: 8080 - stdin_open: true - tty: true - volumes: - - assetstore:/dspace/assetstore - - ./to_be_determined/local.cfg:/dspace/config/local.cfg dspace-angular: container_name: dspace-angular depends_on: @@ -45,36 +30,4 @@ services: tty: true volumes: - ./to_be_determined/environment.dev.js:/app/config/environment.dev.js - dspacedb: - container_name: dspacedb - environment: - PGDATA: /pgdata - image: dspace/dspace-postgres-pgcrypto - networks: - dspacenet: - stdin_open: true - tty: true - volumes: - - pgdata:/pgdata - dspacesolr: - container_name: dspacesolr - image: dspace/dspace-solr - networks: - dspacenet: - ports: - - published: 8983 - target: 8983 - stdin_open: true - tty: true - volumes: - - solr_authority:/opt/solr/server/solr/authority/data - - solr_oai:/opt/solr/server/solr/oai/data - - solr_search:/opt/solr/server/solr/search/data - - solr_statistics:/opt/solr/server/solr/statistics/data volumes: - assetstore: - pgdata: - solr_authority: - solr_oai: - solr_search: - solr_statistics: From 824289cde13744f0cdb9ee4d39b0e2dee5e5fee9 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Sun, 29 Sep 2019 11:19:56 -0700 Subject: [PATCH 080/115] fix compose file --- docker-compose.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3573076724..5af000ee68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,6 @@ networks: services: dspace-angular: container_name: dspace-angular - depends_on: - - dspace environment: DSPACE_HOST: dspace-angular DSPACE_NAMESPACE: / @@ -30,4 +28,3 @@ services: tty: true volumes: - ./to_be_determined/environment.dev.js:/app/config/environment.dev.js -volumes: From eca35851d3bd128aec16bbee81146542865b6c50 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Sun, 29 Sep 2019 11:58:50 -0700 Subject: [PATCH 081/115] match network name --- to_be_determined/cli.assetstore.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/to_be_determined/cli.assetstore.yml b/to_be_determined/cli.assetstore.yml index 0c6fcb2030..ad1fdd8455 100644 --- a/to_be_determined/cli.assetstore.yml +++ b/to_be_determined/cli.assetstore.yml @@ -1,7 +1,12 @@ version: "3.7" +networks: + dspacenet: + services: dspace-cli: + networks: + dspacenet: {} environment: - LOADASSETS=https://www.dropbox.com/s/zv7lj8j2lp3egjs/assetstore.tar.gz?dl=1 entrypoint: From a2fb8a316b3c47b85a995db892839c54907738c0 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 30 Sep 2019 10:35:52 +0200 Subject: [PATCH 082/115] 62589: Added tests for more coverage --- .../collection-item-mapper.component.spec.ts | 43 ++++++++++++++- .../item-collection-mapper.component.spec.ts | 45 +++++++++++++++- .../core/data/collection-data.service.spec.ts | 54 +++++++++++++++++++ src/app/core/data/item-data.service.spec.ts | 49 +++++++++++++++-- .../collection-select.component.spec.ts | 14 +++++ .../item-select/item-select.component.spec.ts | 14 +++++ 6 files changed, 213 insertions(+), 6 deletions(-) create mode 100644 src/app/core/data/collection-data.service.spec.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 dcbce697c6..8332d20cea 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 @@ -67,8 +67,12 @@ describe('CollectionItemMapperComponent', () => { sort: new SortOptions('dc.title', SortDirection.ASC), scope: mockCollection.id })); + const url = 'http://test.url'; + const urlWithParam = url + '?param=value'; const routerStub = Object.assign(new RouterStub(), { - url: 'http://test.url' + url: urlWithParam, + navigateByUrl: {}, + navigate: {} }); const searchConfigServiceStub = { paginatedSearchOptions: mockSearchOptions @@ -168,4 +172,41 @@ describe('CollectionItemMapperComponent', () => { }); }); + describe('tabChange', () => { + beforeEach(() => { + spyOn(routerStub, 'navigateByUrl'); + comp.tabChange({}); + }); + + it('should navigate to the same page to remove parameters', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith(url); + }); + }); + + describe('buildQuery', () => { + const query = 'query'; + const expected = `-location.coll:\"${mockCollection.id}\" AND ${query}`; + + let result; + + beforeEach(() => { + result = comp.buildQuery(mockCollection.id, query); + }); + + it('should build a solr query to exclude the provided collection', () => { + expect(result).toEqual(expected); + }) + }); + + describe('onCancel', () => { + beforeEach(() => { + spyOn(routerStub, 'navigate'); + comp.onCancel(); + }); + + it('should navigate to the collection page', () => { + expect(router.navigate).toHaveBeenCalledWith(['/collections/', mockCollection.id]); + }); + }); + }); 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 2f04126711..018ed3f2ac 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 @@ -36,6 +36,7 @@ import { PaginationComponent } from '../../../shared/pagination/pagination.compo import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; import { VarDirective } from '../../../shared/utils/var.directive'; import { SearchFormComponent } from '../../../shared/search-form/search-form.component'; +import { Collection } from '../../../core/shared/collection.model'; describe('ItemCollectionMapperComponent', () => { let comp: ItemCollectionMapperComponent; @@ -48,6 +49,7 @@ describe('ItemCollectionMapperComponent', () => { let notificationsService: NotificationsService; let itemDataService: ItemDataService; + const mockCollection = Object.assign(new Collection(), { id: 'collection1' }); const mockItem: Item = Object.assign(new Item(), { id: '932c7d50-d85a-44cb-b9dc-b427b12877bd', name: 'test-item' @@ -61,8 +63,12 @@ describe('ItemCollectionMapperComponent', () => { }), sort: new SortOptions('dc.title', SortDirection.ASC) })); + const url = 'http://test.url'; + const urlWithParam = url + '?param=value'; const routerStub = Object.assign(new RouterStub(), { - url: 'http://test.url' + url: urlWithParam, + navigateByUrl: {}, + navigate: {} }); const searchConfigServiceStub = { paginatedSearchOptions: mockSearchOptions @@ -159,4 +165,41 @@ describe('ItemCollectionMapperComponent', () => { }); }); + describe('tabChange', () => { + beforeEach(() => { + spyOn(routerStub, 'navigateByUrl'); + comp.tabChange({}); + }); + + it('should navigate to the same page to remove parameters', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith(url); + }); + }); + + describe('buildQuery', () => { + const query = 'query'; + const expected = `${query} AND -search.resourceid:${mockCollection.id}`; + + let result; + + beforeEach(() => { + result = comp.buildQuery([mockCollection], query); + }); + + it('should build a solr query to exclude the provided collection', () => { + expect(result).toEqual(expected); + }) + }); + + describe('onCancel', () => { + beforeEach(() => { + spyOn(routerStub, 'navigate'); + comp.onCancel(); + }); + + it('should navigate to the item page', () => { + expect(router.navigate).toHaveBeenCalledWith(['/items/', mockItem.id]); + }); + }); + }); diff --git a/src/app/core/data/collection-data.service.spec.ts b/src/app/core/data/collection-data.service.spec.ts new file mode 100644 index 0000000000..b0b3889c9c --- /dev/null +++ b/src/app/core/data/collection-data.service.spec.ts @@ -0,0 +1,54 @@ +import { CollectionDataService } from './collection-data.service'; +import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub'; +import { getMockRequestService } from '../../shared/mocks/mock-request.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestService } from './request.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { GetRequest } from './request.models'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; + +describe('CollectionDataService', () => { + let service: CollectionDataService; + let objectCache: ObjectCacheService; + let requestService: RequestService; + let halService: HALEndpointService; + let rdbService: RemoteDataBuildService; + + const url = 'fake-collections-url'; + + beforeEach(() => { + objectCache = jasmine.createSpyObj('objectCache', { + remove: jasmine.createSpy('remove') + }); + requestService = getMockRequestService(); + halService = Object.assign(new HALEndpointServiceStub(url)); + rdbService = jasmine.createSpyObj('rdbService', { + buildList: jasmine.createSpy('buildList') + }); + + service = new CollectionDataService(requestService, rdbService, null, null, null, objectCache, halService, null, null, null); + }); + + describe('getMappedItems', () => { + let result; + + beforeEach(() => { + result = service.getMappedItems('collection-id'); + }); + + it('should configure a GET request', () => { + expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest), undefined); + }); + }); + + describe('clearMappedItemsRequests', () => { + beforeEach(() => { + service.clearMappedItemsRequests('collection-id'); + }); + + it('should remote request cache', () => { + expect(requestService.removeByHrefSubstring).toHaveBeenCalled(); + }); + }); + +}); diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts index 3553a63af4..36b8e6b3c5 100644 --- a/src/app/core/data/item-data.service.spec.ts +++ b/src/app/core/data/item-data.service.spec.ts @@ -7,7 +7,14 @@ import { CoreState } from '../core.reducers'; import { ItemDataService } from './item-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindAllOptions, RestRequest } from './request.models'; +import { + DeleteRequest, + FindAllOptions, + GetRequest, + MappedCollectionsRequest, + PostRequest, + RestRequest +} from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { Observable } from 'rxjs'; import { RestResponse } from '../cache/response.models'; @@ -16,12 +23,13 @@ import { NormalizedObjectBuildService } from '../cache/builders/normalized-objec import { HttpClient } from '@angular/common/http'; import { RequestEntry } from './request.reducer'; import { of as observableOf } from 'rxjs'; +import { getMockRequestService } from '../../shared/mocks/mock-request.service'; describe('ItemDataService', () => { let scheduler: TestScheduler; let service: ItemDataService; let bs: BrowseService; - const requestService = { + const requestService = Object.assign(getMockRequestService(), { generateRequestId(): string { return scopeID; }, @@ -32,9 +40,14 @@ describe('ItemDataService', () => { const responseCacheEntry = new RequestEntry(); responseCacheEntry.response = new RestResponse(true, 200, 'OK'); return observableOf(responseCacheEntry); + }, + removeByHrefSubstring(href: string) { + // Do nothing } - } as RequestService; - const rdbService = {} as RemoteDataBuildService; + }) as RequestService; + const rdbService = jasmine.createSpyObj('rdbService', { + toRemoteDataObservable: observableOf({}) + }); const store = {} as Store; const objectCache = {} as ObjectCacheService; @@ -162,4 +175,32 @@ describe('ItemDataService', () => { }); }); + describe('removeMappingFromCollection', () => { + let result; + + beforeEach(() => { + service = initTestService(); + spyOn(requestService, 'configure'); + result = service.removeMappingFromCollection('item-id', 'collection-id'); + }); + + it('should configure a DELETE request', () => { + result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(DeleteRequest), undefined)); + }); + }); + + describe('mapToCollection', () => { + let result; + + beforeEach(() => { + service = initTestService(); + spyOn(requestService, 'configure'); + result = service.mapToCollection('item-id', 'collection-href'); + }); + + it('should configure a POST request', () => { + result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest), undefined)); + }); + }); + }); 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 bc83c3d52a..c9f79f6af5 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 @@ -101,4 +101,18 @@ describe('ItemSelectComponent', () => { expect(comp.confirm.emit).toHaveBeenCalled(); }); }); + + describe('when cancel is clicked', () => { + let cancelButton: HTMLButtonElement; + + beforeEach(() => { + cancelButton = fixture.debugElement.query(By.css('button.collection-cancel')).nativeElement; + spyOn(comp.cancel, 'emit').and.callThrough(); + }); + + it('should emit a cancel event',() => { + cancelButton.click(); + expect(comp.cancel.emit).toHaveBeenCalled(); + }); + }); }); 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 be7c315c45..33fa4dcd7e 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 @@ -123,4 +123,18 @@ describe('ItemSelectComponent', () => { expect(comp.confirm.emit).toHaveBeenCalled(); }); }); + + describe('when cancel is clicked', () => { + let cancelButton: HTMLButtonElement; + + beforeEach(() => { + cancelButton = fixture.debugElement.query(By.css('button.item-cancel')).nativeElement; + spyOn(comp.cancel, 'emit').and.callThrough(); + }); + + it('should emit a cancel event',() => { + cancelButton.click(); + expect(comp.cancel.emit).toHaveBeenCalled(); + }); + }); }); From b8a466d12b5f6cdf8e60b425667a923f0d847ba8 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 30 Sep 2019 16:17:18 +0200 Subject: [PATCH 083/115] 62589: Loading components for item and collection select lists --- resources/i18n/en.json5 | 4 ++++ .../collection-item-mapper.component.ts | 10 ++++++---- .../item-collection-mapper.component.ts | 10 ++++++---- .../collection-select/collection-select.component.html | 2 ++ .../item-select/item-select.component.html | 2 ++ 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index ba3d1549c1..82e372ea56 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -198,9 +198,11 @@ "error.browse-by": "Error fetching items", "error.collection": "Error fetching collection", + "error.collections": "Error fetching collections", "error.community": "Error fetching community", "error.default": "Error", "error.item": "Error fetching item", + "error.items": "Error fetching items", "error.objects": "Error fetching objects", "error.recent-submissions": "Error fetching recent submissions", "error.search-results": "Error fetching search results", @@ -430,9 +432,11 @@ "loading.browse-by": "Loading items...", "loading.browse-by-page": "Loading page...", "loading.collection": "Loading collection...", + "loading.collections": "Loading collections...", "loading.community": "Loading community...", "loading.default": "Loading...", "loading.item": "Loading item...", + "loading.items": "Loading items...", "loading.mydspace-results": "Loading items...", "loading.objects": "Loading...", "loading.recent-submissions": "Loading recent submissions...", 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 417b044277..8f46a82077 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, tap } from 'rxjs/operators'; +import { map, startWith, switchMap, take, tap } from 'rxjs/operators'; import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators'; import { SearchService } from '../../+search-page/search-service/search.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; @@ -128,10 +128,12 @@ export class CollectionItemMapperComponent implements OnInit { scope: undefined, dsoType: DSpaceObjectType.ITEM, sort: this.defaultSortOptions - })); + })).pipe( + toDSpaceObjectListRD(), + startWith(undefined) + ); } - }), - toDSpaceObjectListRD() + }) ); } 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 b8073edb39..a7a090f691 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,7 +11,7 @@ import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } fr 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, take } from 'rxjs/operators'; +import { map, startWith, 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'; @@ -112,9 +112,11 @@ export class ItemCollectionMapperComponent implements OnInit { return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), { query: this.buildQuery([...itemCollectionsRD.payload.page, owningCollectionRD.payload], searchOptions.query), dsoType: DSpaceObjectType.COLLECTION - })); - }), - toDSpaceObjectListRD() + })).pipe( + toDSpaceObjectListRD(), + startWith(undefined) + ); + }) ) as Observable>>; } 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 3e2ecdaebf..44307859ad 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 @@ -24,6 +24,8 @@
+ +
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 5dbc88e55d..6691be3584 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 @@ -28,6 +28,8 @@
+ +
From 8652c4bce8cd04869b9efe72ccfc603ccd8ca1bb Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 30 Sep 2019 16:37:44 +0200 Subject: [PATCH 084/115] 62589: Fixed test imports --- .../collection-item-mapper.component.spec.ts | 4 +++- .../item-collection-mapper.component.spec.ts | 4 +++- 2 files changed, 6 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 8332d20cea..0bbfb30821 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 @@ -41,6 +41,8 @@ import { of as observableOf, of } from 'rxjs/internal/observable/of'; import { RestResponse } from '../../core/cache/response.models'; import { SearchFixedFilterService } from '../../+search-page/search-filters/search-filter/search-fixed-filter.service'; import { RouteService } from '../../core/services/route.service'; +import { ErrorComponent } from '../../shared/error/error.component'; +import { LoadingComponent } from '../../shared/loading/loading.component'; describe('CollectionItemMapperComponent', () => { let comp: CollectionItemMapperComponent; @@ -120,7 +122,7 @@ describe('CollectionItemMapperComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], - declarations: [CollectionItemMapperComponent, ItemSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe, VarDirective], + declarations: [CollectionItemMapperComponent, ItemSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe, VarDirective, ErrorComponent, LoadingComponent], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: Router, useValue: routerStub }, 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 018ed3f2ac..ed9351d5d2 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,6 +37,8 @@ import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe'; import { VarDirective } from '../../../shared/utils/var.directive'; import { SearchFormComponent } from '../../../shared/search-form/search-form.component'; import { Collection } from '../../../core/shared/collection.model'; +import { ErrorComponent } from '../../../shared/error/error.component'; +import { LoadingComponent } from '../../../shared/loading/loading.component'; describe('ItemCollectionMapperComponent', () => { let comp: ItemCollectionMapperComponent; @@ -99,7 +101,7 @@ describe('ItemCollectionMapperComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], - declarations: [ItemCollectionMapperComponent, CollectionSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe, VarDirective], + declarations: [ItemCollectionMapperComponent, CollectionSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe, VarDirective, ErrorComponent, LoadingComponent], providers: [ { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: Router, useValue: routerStub }, From c6d2cb66c750c1533041c561ad09a3e1cf759215 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Mon, 30 Sep 2019 10:45:45 -0700 Subject: [PATCH 085/115] refactor docker compse names --- .travis.yml | 6 +-- docker-compose.yml | 2 +- .../cli.assetstore.yml | 0 {to_be_determined => docker}/cli.yml | 0 docker/docker-compose-rest.yml | 50 +++++++++++++++++++ .../docker-compose-travis.yml | 2 +- .../environment.dev.js | 0 {to_be_determined => docker}/local.cfg | 0 8 files changed, 55 insertions(+), 5 deletions(-) rename {to_be_determined => docker}/cli.assetstore.yml (100%) rename {to_be_determined => docker}/cli.yml (100%) create mode 100644 docker/docker-compose-rest.yml rename docker-compose-travis.yml => docker/docker-compose-travis.yml (95%) rename {to_be_determined => docker}/environment.dev.js (100%) rename {to_be_determined => docker}/local.cfg (100%) diff --git a/.travis.yml b/.travis.yml index 12627a632e..901dee8186 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,9 +20,9 @@ before_install: install: # Start up DSpace 7 using the entities database dump - - docker-compose -f docker-compose-travis.yml up -d + - docker-compose -f ./docker/docker-compose-travis.yml up -d # Use the dspace-cli image to populate the assetstore. Trigger a discovery and oai update - - docker-compose -f ./to_be_determined/cli.yml -f ./to_be_determined/cli.assetstore.yml run --rm dspace-cli + - docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli - travis_retry yarn install before_script: @@ -31,7 +31,7 @@ before_script: #- curl http://localhost:8080/ after_script: - - docker-compose -f docker-compose-travis.yml down + - docker-compose -f ./docker/docker-compose-travis.yml down addons: apt: diff --git a/docker-compose.yml b/docker-compose.yml index 5af000ee68..95cc98c4ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,4 +27,4 @@ services: stdin_open: true tty: true volumes: - - ./to_be_determined/environment.dev.js:/app/config/environment.dev.js + - ./docker/environment.dev.js:/app/config/environment.dev.js diff --git a/to_be_determined/cli.assetstore.yml b/docker/cli.assetstore.yml similarity index 100% rename from to_be_determined/cli.assetstore.yml rename to docker/cli.assetstore.yml diff --git a/to_be_determined/cli.yml b/docker/cli.yml similarity index 100% rename from to_be_determined/cli.yml rename to docker/cli.yml diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml new file mode 100644 index 0000000000..051a82a382 --- /dev/null +++ b/docker/docker-compose-rest.yml @@ -0,0 +1,50 @@ +networks: + dspacenet: {} +services: + dspace: + container_name: dspace + depends_on: + - dspacedb + image: dspace/dspace:dspace-7_x-jdk8-test + networks: + dspacenet: {} + ports: + - published: 8080 + target: 8080 + stdin_open: true + tty: true + volumes: + - assetstore:/dspace/assetstore + - ./local.cfg:/dspace/config/local.cfg + dspacedb: + container_name: dspacedb + image: dspace/dspace-postgres-pgcrypto + networks: + dspacenet: {} + stdin_open: true + tty: true + volumes: + - pgdata:/pgdata + dspacesolr: + container_name: dspacesolr + image: dspace/dspace-solr + networks: + dspacenet: {} + ports: + - published: 8983 + target: 8983 + stdin_open: true + tty: true + volumes: + - solr_authority:/opt/solr/server/solr/authority/data + - solr_oai:/opt/solr/server/solr/oai/data + - solr_search:/opt/solr/server/solr/search/data + - solr_statistics:/opt/solr/server/solr/statistics/data +version: '3.7' +volumes: + assetstore: {} + pgdata: {} + solr_authority: {} + solr_oai: {} + solr_search: {} + solr_statistics: {} diff --git a/docker-compose-travis.yml b/docker/docker-compose-travis.yml similarity index 95% rename from docker-compose-travis.yml rename to docker/docker-compose-travis.yml index de1278d90d..652043c7ae 100644 --- a/docker-compose-travis.yml +++ b/docker/docker-compose-travis.yml @@ -15,7 +15,7 @@ services: tty: true volumes: - assetstore:/dspace/assetstore - - ./to_be_determined/local.cfg:/dspace/config/local.cfg + - ./local.cfg:/dspace/config/local.cfg dspacedb: container_name: dspacedb environment: diff --git a/to_be_determined/environment.dev.js b/docker/environment.dev.js similarity index 100% rename from to_be_determined/environment.dev.js rename to docker/environment.dev.js diff --git a/to_be_determined/local.cfg b/docker/local.cfg similarity index 100% rename from to_be_determined/local.cfg rename to docker/local.cfg From f85d4fa033e8892aca9a407dd185a2b30d956056 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 1 Oct 2019 10:31:07 -0700 Subject: [PATCH 086/115] Add README for Docker --- README.md | 5 +++++ docker/README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 docker/README.md diff --git a/README.md b/README.md index 1b3ed9b7cb..a9f2b0861b 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,11 @@ yarn run clean:prod yarn run clean:dist ``` +Running the application with Docker +----------------------------------- +See [Docker Runtime Options](docker/README.md) + + Testing ------- diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..645784e0fb --- /dev/null +++ b/docker/README.md @@ -0,0 +1,52 @@ +# Docker Compose files + +## root directory +- docker-compose.yml + - Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker. + +## docker directory +- docker-compose-rest.yml + - Runs a published instance of the DSpace 7 REST API - persists data in Docker volumes +- docker-compose-travis.yml + - Runs a published instance of the DSpace 7 REST API for CI testing. The database is re-populated from a SQL dump on each startup. +- cli.yml + - Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container. +- cli.assetstore.yml + - Docker compose file that will download and install data into a DSpace REST assetstore. This script points to a default dataset that will be utilized for CI testing. +- environment.dev.js + - Environment file for running DSpace Angular in Docker +- local.cfg + - Environment file for running the DSpace 7 REST API in Docker. + + + ## To start DSpace from your branch using a published images for DSpace REST and DSpace Angular. + ``` + docker-compose -p d7 -f docker-compose.yml -f docker/docker-compose-rest.yml up -d + ``` + + ## To build DSpace Angular from your branch using a published image for DSpace REST. + ``` + docker-compose -p d7 -f docker-compose.yml -f docker/docker-compose-rest.yml up --build -d + ``` + + ## To build DSpace REST and DSpace Angular. + _The system will be started in 2 steps. Each step shares the same docker network._ + + From DSpace/DSpace + ``` + docker-compose -p d7 up --build -d + ``` + + + From DSpace/DSpace-angular + ``` + docker-compose -p d7 up --build -d + ``` + + ## End to end testing of the rest api (runs in travis). + _In this instance, only the REST api runs in Docker. Travis will perform CI testing of Angular using Node to drive the tests._ + + + ``` + docker-compose -p d7ci -f docker/docker-compose-travis.yml up -d + ``` From 292f7ddd25ad8da4efc7f6e7e7fea7f2b2283bea Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 1 Oct 2019 15:38:19 -0700 Subject: [PATCH 087/115] Add pull vs build instructions to README --- docker/README.md | 51 +++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/docker/README.md b/docker/README.md index 645784e0fb..1c490ae02c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -19,34 +19,37 @@ - Environment file for running the DSpace 7 REST API in Docker. - ## To start DSpace from your branch using a published images for DSpace REST and DSpace Angular. - ``` - docker-compose -p d7 -f docker-compose.yml -f docker/docker-compose-rest.yml up -d - ``` +## To refresh / pull DSpace images from Dockerhub +``` +docker-compose pull +``` - ## To build DSpace Angular from your branch using a published image for DSpace REST. - ``` - docker-compose -p d7 -f docker-compose.yml -f docker/docker-compose-rest.yml up --build -d - ``` +## To build DSpace images using code in your branch +``` +docker-compose build +``` - ## To build DSpace REST and DSpace Angular. - _The system will be started in 2 steps. Each step shares the same docker network._ +## To start DSpace (REST and Angular) from your branch +``` +docker-compose -p d7 -f docker-compose.yml -f docker/docker-compose-rest.yml up -d +``` - From DSpace/DSpace - ``` - docker-compose -p d7 up --build -d - ``` +## To build DSpace REST and DSpace Angular. +_The system will be started in 2 steps. Each step shares the same docker network._ +From DSpace/DSpace +``` +docker-compose -p d7 up --build -d +``` - From DSpace/DSpace-angular - ``` - docker-compose -p d7 up --build -d - ``` +From DSpace/DSpace-angular +``` +docker-compose -p d7 up --build -d +``` - ## End to end testing of the rest api (runs in travis). - _In this instance, only the REST api runs in Docker. Travis will perform CI testing of Angular using Node to drive the tests._ +## End to end testing of the rest api (runs in travis). +_In this instance, only the REST api runs in Docker. Travis will perform CI testing of Angular using Node to drive the tests._ - - ``` - docker-compose -p d7ci -f docker/docker-compose-travis.yml up -d - ``` +``` +docker-compose -p d7ci -f docker/docker-compose-travis.yml up -d +``` From a74f256ceabc67c470c52baf5be9fbf3206d7bd3 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 1 Oct 2019 22:52:44 -0700 Subject: [PATCH 088/115] refine startup instructions --- docker-compose.yml | 6 +----- docker/README.md | 24 +++++++++++++++++++----- docker/cli.assetstore.yml | 2 -- docker/cli.ingest.yml | 32 ++++++++++++++++++++++++++++++++ docker/db.entities.yml | 16 ++++++++++++++++ docker/docker-compose-rest.yml | 29 ++++++++++++++++++----------- docker/docker-compose-travis.yml | 22 +++++++++++----------- 7 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 docker/cli.ingest.yml create mode 100644 docker/db.entities.yml diff --git a/docker-compose.yml b/docker-compose.yml index 95cc98c4ae..e6b4eb3d6c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,17 +8,13 @@ services: DSPACE_HOST: dspace-angular DSPACE_NAMESPACE: / DSPACE_PORT: '3000' - DSPACE_REST_HOST: dspace - DSPACE_REST_NAMESPACE: / - DSPACE_REST_PORT: '8080' - DSPACE_REST_SSL: "false" DSPACE_SSL: "false" image: dspace/dspace-angular:latest build: context: . dockerfile: Dockerfile networks: - dspacenet: {} + dspacenet: ports: - published: 3000 target: 3000 diff --git a/docker/README.md b/docker/README.md index 1c490ae02c..6a5077303b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -30,23 +30,37 @@ docker-compose build ``` ## To start DSpace (REST and Angular) from your branch + ``` -docker-compose -p d7 -f docker-compose.yml -f docker/docker-compose-rest.yml up -d +docker-compose -p d7 -f docker/docker-compose-rest.yml -f docker-compose.yml up -d ``` -## To build DSpace REST and DSpace Angular. +## Run DSpace REST and DSpace Angular from local branches. _The system will be started in 2 steps. Each step shares the same docker network._ -From DSpace/DSpace +From DSpace/DSpace (build as needed) ``` -docker-compose -p d7 up --build -d +docker-compose -p d7 up -d ``` From DSpace/DSpace-angular ``` -docker-compose -p d7 up --build -d +docker-compose -p d7 up -d ``` +## Ingest test data from AIPDIR + +Create an administrator +``` +docker-compose -p d7 -f docker/cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en +``` + +Load content from AIP files +``` +docker-compose -p d7 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspace-cli +``` + + ## End to end testing of the rest api (runs in travis). _In this instance, only the REST api runs in Docker. Travis will perform CI testing of Angular using Node to drive the tests._ diff --git a/docker/cli.assetstore.yml b/docker/cli.assetstore.yml index ad1fdd8455..075c494a6c 100644 --- a/docker/cli.assetstore.yml +++ b/docker/cli.assetstore.yml @@ -21,5 +21,3 @@ services: fi /dspace/bin/dspace index-discovery - /dspace/bin/dspace oai import - /dspace/bin/dspace oai clean-cache diff --git a/docker/cli.ingest.yml b/docker/cli.ingest.yml new file mode 100644 index 0000000000..f5ec7eb90d --- /dev/null +++ b/docker/cli.ingest.yml @@ -0,0 +1,32 @@ +# +# The contents of this file are subject to the license and copyright +# detailed in the LICENSE and NOTICE files at the root of the source +# tree and available online at +# +# http://www.dspace.org/license/ +# + +version: "3.7" + +services: + dspace-cli: + environment: + - AIPZIP=https://github.com/DSpace-Labs/AIP-Files/raw/master/dogAndReport.zip + - ADMIN_EMAIL=test@test.edu + - AIPDIR=/tmp/aip-dir + entrypoint: + - /bin/bash + - '-c' + - | + rm -rf $${AIPDIR} + mkdir $${AIPDIR} /dspace/upload + cd $${AIPDIR} + pwd + curl $${AIPZIP} -L -s --output aip.zip + unzip aip.zip + cd $${AIPDIR} + + /dspace/bin/dspace packager -r -a -t AIP -e $${ADMIN_EMAIL} -f -u SITE*.zip + /dspace/bin/dspace database update-sequences + + /dspace/bin/dspace index-discovery diff --git a/docker/db.entities.yml b/docker/db.entities.yml new file mode 100644 index 0000000000..91d96bd72b --- /dev/null +++ b/docker/db.entities.yml @@ -0,0 +1,16 @@ +# +# The contents of this file are subject to the license and copyright +# detailed in the LICENSE and NOTICE files at the root of the source +# tree and available online at +# +# http://www.dspace.org/license/ +# + +version: "3.7" + +services: + dspacedb: + image: dspace/dspace-postgres-pgcrypto:loadsql + environment: + # Double underbars in env names will be replaced with periods for apache commons + - LOADSQL=https://www.dropbox.com/s/xh3ack0vg0922p2/configurable-entities-2019-05-08.sql?dl=1 diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index 051a82a382..b861cdcfec 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -1,5 +1,5 @@ networks: - dspacenet: {} + dspacenet: services: dspace: container_name: dspace @@ -7,7 +7,7 @@ services: - dspacedb image: dspace/dspace:dspace-7_x-jdk8-test networks: - dspacenet: {} + dspacenet: ports: - published: 8080 target: 8080 @@ -15,12 +15,19 @@ services: tty: true volumes: - assetstore:/dspace/assetstore - - ./local.cfg:/dspace/config/local.cfg + - "./local.cfg:/dspace/config/local.cfg" + # Ensure that the database is ready before starting tomcat + entrypoint: + - /bin/bash + - '-c' + - | + /dspace/bin/dspace database migrate + catalina.sh run dspacedb: container_name: dspacedb image: dspace/dspace-postgres-pgcrypto networks: - dspacenet: {} + dspacenet: stdin_open: true tty: true volumes: @@ -29,7 +36,7 @@ services: container_name: dspacesolr image: dspace/dspace-solr networks: - dspacenet: {} + dspacenet: ports: - published: 8983 target: 8983 @@ -42,9 +49,9 @@ services: - solr_statistics:/opt/solr/server/solr/statistics/data version: '3.7' volumes: - assetstore: {} - pgdata: {} - solr_authority: {} - solr_oai: {} - solr_search: {} - solr_statistics: {} + assetstore: + pgdata: + solr_authority: + solr_oai: + solr_search: + solr_statistics: diff --git a/docker/docker-compose-travis.yml b/docker/docker-compose-travis.yml index 652043c7ae..6ca44e4e47 100644 --- a/docker/docker-compose-travis.yml +++ b/docker/docker-compose-travis.yml @@ -1,5 +1,5 @@ networks: - dspacenet: {} + dspacenet: services: dspace: container_name: dspace @@ -7,7 +7,7 @@ services: - dspacedb image: dspace/dspace:dspace-7_x-jdk8-test networks: - dspacenet: {} + dspacenet: ports: - published: 8080 target: 8080 @@ -15,7 +15,7 @@ services: tty: true volumes: - assetstore:/dspace/assetstore - - ./local.cfg:/dspace/config/local.cfg + - "./local.cfg:/dspace/config/local.cfg" dspacedb: container_name: dspacedb environment: @@ -23,7 +23,7 @@ services: PGDATA: /pgdata image: dspace/dspace-postgres-pgcrypto:loadsql networks: - dspacenet: {} + dspacenet: stdin_open: true tty: true volumes: @@ -32,7 +32,7 @@ services: container_name: dspacesolr image: dspace/dspace-solr networks: - dspacenet: {} + dspacenet: ports: - published: 8983 target: 8983 @@ -45,9 +45,9 @@ services: - solr_statistics:/opt/solr/server/solr/statistics/data version: '3.7' volumes: - assetstore: {} - pgdata: {} - solr_authority: {} - solr_oai: {} - solr_search: {} - solr_statistics: {} + assetstore: + pgdata: + solr_authority: + solr_oai: + solr_search: + solr_statistics: From dc47d191ecb200cc9a9fb972090eb294206a48b7 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 1 Oct 2019 23:30:26 -0700 Subject: [PATCH 089/115] move all compose files to one dir --- docker/README.md | 16 ++++++++++------ docker/docker-compose-rest.yml | 2 ++ docker-compose.yml => docker/docker-compose.yml | 6 +++--- 3 files changed, 15 insertions(+), 9 deletions(-) rename docker-compose.yml => docker/docker-compose.yml (82%) diff --git a/docker/README.md b/docker/README.md index 6a5077303b..097ab37694 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,10 +1,8 @@ # Docker Compose files -## root directory +## docker directory - docker-compose.yml - Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker. - -## docker directory - docker-compose-rest.yml - Runs a published instance of the DSpace 7 REST API - persists data in Docker volumes - docker-compose-travis.yml @@ -21,18 +19,18 @@ ## To refresh / pull DSpace images from Dockerhub ``` -docker-compose pull +docker-compose -f docker/docker-compose.yml pull ``` ## To build DSpace images using code in your branch ``` -docker-compose build +docker-compose-f docker/docker-compose.yml build ``` ## To start DSpace (REST and Angular) from your branch ``` -docker-compose -p d7 -f docker/docker-compose-rest.yml -f docker-compose.yml up -d +docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d ``` ## Run DSpace REST and DSpace Angular from local branches. @@ -60,6 +58,12 @@ Load content from AIP files docker-compose -p d7 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspace-cli ``` +## Alternative Ingest - Use Entities dataset +_Delete your docker volumes or use a unique project (-p) name_ +``` +docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d +``` + ## End to end testing of the rest api (runs in travis). _In this instance, only the REST api runs in Docker. Travis will perform CI testing of Angular using Node to drive the tests._ diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index b861cdcfec..222557bc81 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -26,6 +26,8 @@ services: dspacedb: container_name: dspacedb image: dspace/dspace-postgres-pgcrypto + environment: + PGDATA: /pgdata networks: dspacenet: stdin_open: true diff --git a/docker-compose.yml b/docker/docker-compose.yml similarity index 82% rename from docker-compose.yml rename to docker/docker-compose.yml index e6b4eb3d6c..23f0615a1f 100644 --- a/docker-compose.yml +++ b/docker/docker-compose.yml @@ -11,10 +11,10 @@ services: DSPACE_SSL: "false" image: dspace/dspace-angular:latest build: - context: . + context: .. dockerfile: Dockerfile networks: - dspacenet: + dspacenet: ports: - published: 3000 target: 3000 @@ -23,4 +23,4 @@ services: stdin_open: true tty: true volumes: - - ./docker/environment.dev.js:/app/config/environment.dev.js + - ./environment.dev.js:/app/config/environment.dev.js From f8961d764749ae27a6df33e33e4b64bf1b54e2c5 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 1 Oct 2019 23:32:45 -0700 Subject: [PATCH 090/115] readme update --- docker/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker/README.md b/docker/README.md index 097ab37694..d1f2c95eeb 100644 --- a/docker/README.md +++ b/docker/README.md @@ -60,10 +60,16 @@ docker-compose -p d7 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspac ## Alternative Ingest - Use Entities dataset _Delete your docker volumes or use a unique project (-p) name_ + +Start DSpace with Database Content from a database dump ``` docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d ``` +Load assetstore content and trigger a re-index of the repository +``` +docker-compose -p d7 -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dspace-cli +``` ## End to end testing of the rest api (runs in travis). _In this instance, only the REST api runs in Docker. Travis will perform CI testing of Angular using Node to drive the tests._ From e4bab45f3cbc144dea50ea184979f7d7e465ff23 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Tue, 1 Oct 2019 23:47:01 -0700 Subject: [PATCH 091/115] correct path in README --- docker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index d1f2c95eeb..17a0dae9c4 100644 --- a/docker/README.md +++ b/docker/README.md @@ -43,7 +43,7 @@ docker-compose -p d7 up -d From DSpace/DSpace-angular ``` -docker-compose -p d7 up -d +docker-compose -p d7 -f docker/docker-compose.yml up -d ``` ## Ingest test data from AIPDIR From f1148976616b636e473ba46b7d37b0ea46fb93e1 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 4 Oct 2019 12:08:30 -0500 Subject: [PATCH 092/115] Minor updates/corrections to README --- docker/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/README.md b/docker/README.md index 17a0dae9c4..f7b4b04848 100644 --- a/docker/README.md +++ b/docker/README.md @@ -24,7 +24,7 @@ docker-compose -f docker/docker-compose.yml pull ## To build DSpace images using code in your branch ``` -docker-compose-f docker/docker-compose.yml build +docker-compose -f docker/docker-compose.yml build ``` ## To start DSpace (REST and Angular) from your branch @@ -72,7 +72,7 @@ docker-compose -p d7 -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dsp ``` ## End to end testing of the rest api (runs in travis). -_In this instance, only the REST api runs in Docker. Travis will perform CI testing of Angular using Node to drive the tests._ +_In this instance, only the REST api runs in Docker using the Entities dataset. Travis will perform CI testing of Angular using Node to drive the tests._ ``` docker-compose -p d7ci -f docker/docker-compose-travis.yml up -d From 5b776b605ab34150cc675e07e502e1a19bb59339 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 8 Oct 2019 13:06:31 +0200 Subject: [PATCH 093/115] 62589: Review 03-10-2019 Changes and fixes --- resources/i18n/en.json5 | 44 +++++++++++-------- .../collection-item-mapper.component.html | 26 ++++++----- .../collection-item-mapper.component.ts | 28 +++++------- .../collection-page-routing.module.ts | 2 +- .../item-collection-mapper.component.html | 8 +++- .../item-collection-mapper.component.ts | 21 ++++----- .../search-service/search.service.ts | 13 ++---- .../core/data/collection-data.service.spec.ts | 10 ----- src/app/core/data/collection-data.service.ts | 11 +---- src/app/core/data/item-data.service.ts | 10 ----- src/app/core/data/request.models.ts | 2 + .../collection-select.component.html | 15 +++++-- .../collection-select.component.spec.ts | 4 +- .../item-select/item-select.component.html | 23 +++++++--- .../item-select/item-select.component.spec.ts | 2 +- .../object-select/object-select.service.ts | 8 +++- .../search-form/search-form.component.ts | 8 +++- 17 files changed, 118 insertions(+), 117 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 82e372ea56..0ad08652b5 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -128,8 +128,28 @@ "collection.delete.notification.fail": "Collection could not be deleted", "collection.delete.notification.success": "Successfully deleted collection", "collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"", + "collection.edit.delete": "Delete this collection", "collection.edit.head": "Edit Collection", + + "collection.edit.item-mapper.cancel": "Cancel", + "collection.edit.item-mapper.collection": "Collection: \"{{name}}\"", + "collection.edit.item-mapper.confirm": "Map selected items", + "collection.edit.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.", + "collection.edit.item-mapper.head": "Item Mapper - Map Items from Other Collections", + "collection.edit.item-mapper.no-search": "Please enter a query to search", + "collection.edit.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.", + "collection.edit.item-mapper.notifications.map.error.head": "Mapping errors", + "collection.edit.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.", + "collection.edit.item-mapper.notifications.map.success.head": "Mapping completed", + "collection.edit.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.", + "collection.edit.item-mapper.notifications.unmap.error.head": "Remove mapping errors", + "collection.edit.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.", + "collection.edit.item-mapper.notifications.unmap.success.head": "Remove mapping completed", + "collection.edit.item-mapper.remove": "Remove selected item mappings", + "collection.edit.item-mapper.tabs.browse": "Browse mapped items", + "collection.edit.item-mapper.tabs.map": "Map new items", + "collection.form.abstract": "Short Description", "collection.form.description": "Introductory text (HTML)", "collection.form.errors.title.required": "Please enter a collection name", @@ -139,29 +159,13 @@ "collection.form.tableofcontents": "News (HTML)", "collection.form.title": "Name", - "collection.item-mapper.cancel": "Cancel", - "collection.item-mapper.collection": "Collection: \"{{name}}\"", - "collection.item-mapper.confirm": "Map selected items", - "collection.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.", - "collection.item-mapper.head": "Item Mapper - Map Items from Other Collections", - "collection.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.", - "collection.item-mapper.notifications.map.error.head": "Mapping errors", - "collection.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.", - "collection.item-mapper.notifications.map.success.head": "Mapping completed", - "collection.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.", - "collection.item-mapper.notifications.unmap.error.head": "Remove mapping errors", - "collection.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.", - "collection.item-mapper.notifications.unmap.success.head": "Remove mapping completed", - "collection.item-mapper.remove": "Remove selected item mappings", - "collection.item-mapper.tabs.browse": "Browse", - "collection.item-mapper.tabs.map": "Map", - "collection.page.browse.recent.head": "Recent Submissions", "collection.page.browse.recent.empty": "No items to show", "collection.page.license": "License", "collection.page.news": "News", "collection.select.confirm": "Confirm selected", + "collection.select.empty": "No collections to show", "collection.select.table.title": "Title", "community.create.head": "Create a Community", @@ -258,6 +262,7 @@ "item.edit.item-mapper.description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.", "item.edit.item-mapper.head": "Item Mapper - Map Item to Collections", "item.edit.item-mapper.item": "Item: \"{{name}}\"", + "item.edit.item-mapper.no-search": "Please enter a query to search", "item.edit.item-mapper.notifications.add.error.content": "Errors occurred for mapping of item to {{amount}} collections.", "item.edit.item-mapper.notifications.add.error.head": "Mapping errors", "item.edit.item-mapper.notifications.add.success.content": "Successfully mapped item to {{amount}} collections.", @@ -266,8 +271,8 @@ "item.edit.item-mapper.notifications.remove.error.head": "Removal of mapping errors", "item.edit.item-mapper.notifications.remove.success.content": "Successfully removed mapping of item to {{amount}} collections.", "item.edit.item-mapper.notifications.remove.success.head": "Removal of mapping completed", - "item.edit.item-mapper.tabs.browse": "Browse", - "item.edit.item-mapper.tabs.map": "Map", + "item.edit.item-mapper.tabs.browse": "Browse mapped collections", + "item.edit.item-mapper.tabs.map": "Map new collections", "item.edit.metadata.add-button": "Add", "item.edit.metadata.discard-button": "Discard", @@ -401,6 +406,7 @@ "item.page.uri": "URI", "item.select.confirm": "Confirm selected", + "item.select.empty": "No items to show", "item.select.table.author": "Author", "item.select.table.collection": "Collection", "item.select.table.title": "Title", diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html index 23e23b5c25..af4153220f 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html @@ -1,20 +1,20 @@
-

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

-

-

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

+

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

+

+

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

- +
- +
@@ -30,21 +30,25 @@ [query]="(searchOptions$ | async)?.query" [scope]="(searchOptions$ | async)?.scope" [currentUrl]="'./'" - [inPlaceSearch]="true"> + [inPlaceSearch]="true" + (submitSearch)="performedSearch = true">
-
+
+ diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts index 8f46a82077..4781e63c95 100644 --- a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts +++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts @@ -84,6 +84,12 @@ export class CollectionItemMapperComponent implements OnInit { */ shouldUpdate$: BehaviorSubject; + /** + * Track whether at least one search has been performed or not + * As soon as at least one search has been performed, we display the search results + */ + performedSearch = false; + constructor(private route: ActivatedRoute, private router: Router, @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService, @@ -128,7 +134,7 @@ export class CollectionItemMapperComponent implements OnInit { scope: undefined, dsoType: DSpaceObjectType.ITEM, sort: this.defaultSortOptions - })).pipe( + }), 1000).pipe( toDSpaceObjectListRD(), startWith(undefined) ); @@ -154,7 +160,6 @@ export class CollectionItemMapperComponent implements OnInit { ); this.showNotifications(responses$, remove); - this.clearRequestCache(); } /** @@ -170,8 +175,8 @@ export class CollectionItemMapperComponent implements OnInit { const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); if (successful.length > 0) { const successMessages = observableCombineLatest( - this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.head`), - this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.content`, { amount: successful.length }) + this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.head`), + this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.content`, { amount: successful.length }) ); successMessages.subscribe(([head, content]) => { @@ -180,8 +185,8 @@ export class CollectionItemMapperComponent implements OnInit { } if (unsuccessful.length > 0) { const unsuccessMessages = observableCombineLatest( - this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.head`), - this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.content`, { amount: unsuccessful.length }) + this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.error.head`), + this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.error.content`, { amount: unsuccessful.length }) ); unsuccessMessages.subscribe(([head, content]) => { @@ -194,21 +199,12 @@ export class CollectionItemMapperComponent implements OnInit { }); } - /** - * Clear all previous requests from cache in preparation of refreshing all lists - */ - private clearRequestCache() { - this.collectionRD$.pipe(take(1)).subscribe((collectionRD: RemoteData) => { - this.collectionDataService.clearMappedItemsRequests(collectionRD.payload.id); - this.searchService.clearDiscoveryRequests(); - }); - } - /** * Clear url parameters on tab change (temporary fix until pagination is improved) * @param event */ tabChange(event) { + this.performedSearch = false; this.router.navigateByUrl(this.getCurrentUrl()); } diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index ae5d04714a..66c623657d 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -64,7 +64,7 @@ const COLLECTION_EDIT_PATH = ':id/edit'; } }, { - path: ':id/mapper', + path: ':id/edit/mapper', component: CollectionItemMapperComponent, pathMatch: 'full', resolve: { diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html index d4433cd4a2..43bf7ecd02 100644 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html @@ -28,12 +28,13 @@ + [inPlaceSearch]="true" + (submitSearch)="performedSearch = true">
-
+
+ diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts index a7a090f691..4ee8498bb7 100644 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts +++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts @@ -69,6 +69,12 @@ export class ItemCollectionMapperComponent implements OnInit { */ shouldUpdate$: BehaviorSubject; + /** + * Track whether at least one search has been performed or not + * As soon as at least one search has been performed, we display the search results + */ + performedSearch = false; + constructor(private route: ActivatedRoute, private router: Router, private searchConfigService: SearchConfigurationService, @@ -112,7 +118,7 @@ export class ItemCollectionMapperComponent implements OnInit { return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), { query: this.buildQuery([...itemCollectionsRD.payload.page, owningCollectionRD.payload], searchOptions.query), dsoType: DSpaceObjectType.COLLECTION - })).pipe( + }), 1000).pipe( toDSpaceObjectListRD(), startWith(undefined) ); @@ -146,7 +152,6 @@ export class ItemCollectionMapperComponent implements OnInit { ); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add'); - this.clearRequestCache(); } /** @@ -161,7 +166,6 @@ export class ItemCollectionMapperComponent implements OnInit { ); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove'); - this.clearRequestCache(); } /** @@ -209,21 +213,12 @@ export class ItemCollectionMapperComponent implements OnInit { }); } - /** - * Clear all previous requests from cache in preparation of refreshing all lists - */ - private clearRequestCache() { - this.itemRD$.pipe(take(1)).subscribe((itemRD: RemoteData) => { - this.itemDataService.clearMappedCollectionsRequests(itemRD.payload.id); - this.searchService.clearDiscoveryRequests(); - }); - } - /** * Clear url parameters on tab change (temporary fix until pagination is improved) * @param event */ tabChange(event) { + this.performedSearch = false; this.router.navigateByUrl(this.getCurrentUrl()); } diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 8374813bc7..bedae84eaa 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -100,9 +100,10 @@ export class SearchService implements OnDestroy { /** * Method to retrieve a paginated list of search results from the server * @param {PaginatedSearchOptions} searchOptions The configuration necessary to perform this search + * @param responseMsToLive The amount of milliseconds for the response to live in cache * @returns {Observable>>>} Emits a paginated list with all search results found */ - search(searchOptions?: PaginatedSearchOptions): Observable>>> { + search(searchOptions?: PaginatedSearchOptions, responseMsToLive?: number): Observable>>> { const hrefObs = this.halService.getEndpoint(this.searchLinkPath).pipe( map((url: string) => { if (hasValue(searchOptions)) { @@ -122,6 +123,7 @@ export class SearchService implements OnDestroy { }; return Object.assign(request, { + responseMsToLive: hasValue(responseMsToLive) ? responseMsToLive : request.responseMsToLive, getResponseParser: getResponseParserFn }); }), @@ -373,15 +375,6 @@ export class SearchService implements OnDestroy { return '/search'; } - /** - * Clear all request cache related to discovery objects - */ - clearDiscoveryRequests() { - this.halService.getEndpoint(this.searchLinkPath).pipe(take(1)).subscribe((href: string) => { - this.requestService.removeByHrefSubstring(href); - }); - } - /** * Unsubscribe from the subscription */ diff --git a/src/app/core/data/collection-data.service.spec.ts b/src/app/core/data/collection-data.service.spec.ts index b0b3889c9c..5cb7fed5e4 100644 --- a/src/app/core/data/collection-data.service.spec.ts +++ b/src/app/core/data/collection-data.service.spec.ts @@ -41,14 +41,4 @@ describe('CollectionDataService', () => { }); }); - describe('clearMappedItemsRequests', () => { - beforeEach(() => { - service.clearMappedItemsRequests('collection-id'); - }); - - it('should remote request cache', () => { - expect(requestService.removeByHrefSubstring).toHaveBeenCalled(); - }); - }); - }); diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index cd59e7ac65..e49267d1f2 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -125,6 +125,7 @@ export class CollectionDataService extends ComColDataService { map((endpoint: string) => { const request = new GetRequest(requestUuid, endpoint); return Object.assign(request, { + responseMsToLive: 0, getResponseParser(): GenericConstructor { return DSOResponseParsingService; } @@ -136,14 +137,4 @@ export class CollectionDataService extends ComColDataService { return this.rdbService.buildList(href$); } - /** - * Clears all requests (from cache) connected to the mappedItems endpoint - * @param collectionId - */ - clearMappedItemsRequests(collectionId: string) { - this.getMappedItemsEndpoint(collectionId).pipe(take(1)).subscribe((href: string) => { - this.requestService.removeByHrefSubstring(href); - }); - } - } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index cec60f3305..de05dad0c1 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -148,16 +148,6 @@ export class ItemDataService extends DataService { return this.rdbService.toRemoteDataObservable(requestEntry$, payload$); } - /** - * Clears all requests (from cache) connected to the mappedCollections endpoint - * @param itemId - */ - public clearMappedCollectionsRequests(itemId: string) { - this.getMappedCollectionsEndpoint(itemId).pipe(take(1)).subscribe((href: string) => { - this.requestService.removeByHrefSubstring(href); - }); - } - /** * Get the endpoint for item withdrawal and reinstatement * @param itemId diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index b327306fcb..b0efc0ce71 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -190,6 +190,8 @@ export class BrowseItemsRequest extends GetRequest { * Request to fetch the mapped collections of an item */ export class MappedCollectionsRequest extends GetRequest { + public responseMsToLive = 0; + getResponseParser(): GenericConstructor { return MappedCollectionsReponseParsingService; } diff --git a/src/app/shared/object-select/collection-select/collection-select.component.html b/src/app/shared/object-select/collection-select/collection-select.component.html index 44307859ad..c8a0c4b879 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.html +++ b/src/app/shared/object-select/collection-select/collection-select.component.html @@ -7,7 +7,7 @@ [collectionSize]="collectionsRD?.payload?.totalElements" [hidePagerWhenSinglePage]="true" [hideGear]="true"> -
+
@@ -24,11 +24,18 @@
+ -
+
- - +
diff --git a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts index c9f79f6af5..af7c01a3c5 100644 --- a/src/app/shared/object-select/collection-select/collection-select.component.spec.ts +++ b/src/app/shared/object-select/collection-select/collection-select.component.spec.ts @@ -16,7 +16,7 @@ import { CollectionSelectComponent } from './collection-select.component'; import { Collection } from '../../../core/shared/collection.model'; import { of } from 'rxjs/internal/observable/of'; -describe('ItemSelectComponent', () => { +describe('CollectionSelectComponent', () => { let comp: CollectionSelectComponent; let fixture: ComponentFixture; let objectSelectService: ObjectSelectService; @@ -43,7 +43,7 @@ describe('ItemSelectComponent', () => { imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], declarations: [], providers: [ - { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, + { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockCollectionList[1].id]) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/object-select/item-select/item-select.component.html b/src/app/shared/object-select/item-select/item-select.component.html index 6691be3584..da31dbac65 100644 --- a/src/app/shared/object-select/item-select/item-select.component.html +++ b/src/app/shared/object-select/item-select/item-select.component.html @@ -7,7 +7,7 @@ [collectionSize]="itemsRD?.payload?.totalElements" [hidePagerWhenSinglePage]="true" [hideGear]="true"> -
+ + -
+
- - +
diff --git a/src/app/shared/object-select/item-select/item-select.component.spec.ts b/src/app/shared/object-select/item-select/item-select.component.spec.ts index 33fa4dcd7e..059e44064e 100644 --- a/src/app/shared/object-select/item-select/item-select.component.spec.ts +++ b/src/app/shared/object-select/item-select/item-select.component.spec.ts @@ -65,7 +65,7 @@ describe('ItemSelectComponent', () => { imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], declarations: [], providers: [ - { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, + { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/object-select/object-select.service.ts b/src/app/shared/object-select/object-select.service.ts index 03ddc0078c..8e30bca24d 100644 --- a/src/app/shared/object-select/object-select.service.ts +++ b/src/app/shared/object-select/object-select.service.ts @@ -51,7 +51,13 @@ export class ObjectSelectService { */ getAllSelected(key: string): Observable { return this.appStore.select(objectSelectionListStateSelector).pipe( - map((state: ObjectSelectionListState) => Object.keys(state[key]).filter((id) => state[key][id].checked)) + map((state: ObjectSelectionListState) => { + if (hasValue(state[key])) { + return Object.keys(state[key]).filter((id) => state[key][id].checked); + } else { + return []; + } + }) ); } diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index 7414dd70e6..6b81b103ca 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { Router } from '@angular/router'; import { hasValue, isNotEmpty } from '../empty.util'; @@ -56,6 +56,11 @@ export class SearchFormComponent { */ @Input() brandColor = 'primary'; + /** + * Output the search data on submit + */ + @Output() submitSearch = new EventEmitter(); + constructor(private router: Router, private searchService: SearchService) { } @@ -65,6 +70,7 @@ export class SearchFormComponent { */ onSubmit(data: any) { this.updateSearch(data); + this.submitSearch.emit(data); } /** From f7bd30cf124debff21314a0c7ba6a7833c92c284 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 10 Oct 2019 13:39:59 +0200 Subject: [PATCH 094/115] 62589: Item-Mapper Requests responseMsToLive to 10s --- .../collection-item-mapper/collection-item-mapper.component.ts | 2 +- .../item-collection-mapper/item-collection-mapper.component.ts | 2 +- src/app/core/data/request.models.ts | 2 +- 3 files changed, 3 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 4781e63c95..750578cc35 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 @@ -134,7 +134,7 @@ export class CollectionItemMapperComponent implements OnInit { scope: undefined, dsoType: DSpaceObjectType.ITEM, sort: this.defaultSortOptions - }), 1000).pipe( + }), 10000).pipe( toDSpaceObjectListRD(), startWith(undefined) ); 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 4ee8498bb7..97b8164a6e 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 @@ -118,7 +118,7 @@ export class ItemCollectionMapperComponent implements OnInit { return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), { query: this.buildQuery([...itemCollectionsRD.payload.page, owningCollectionRD.payload], searchOptions.query), dsoType: DSpaceObjectType.COLLECTION - }), 1000).pipe( + }), 10000).pipe( toDSpaceObjectListRD(), startWith(undefined) ); diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index b0efc0ce71..43ab9e58e7 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -190,7 +190,7 @@ export class BrowseItemsRequest extends GetRequest { * Request to fetch the mapped collections of an item */ export class MappedCollectionsRequest extends GetRequest { - public responseMsToLive = 0; + public responseMsToLive = 10000; getResponseParser(): GenericConstructor { return MappedCollectionsReponseParsingService; From 96f8e905dddd60b8bba777442348336db66e53a8 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 28 Aug 2019 17:05:37 -0700 Subject: [PATCH 095/115] Refactored to remove forceBypassCache param from requestService and from data service classes. --- .../browse-by-date-page.component.spec.ts | 1 - .../browse-by-date-page.component.ts | 3 ++- src/app/core/auth/auth-request.service.ts | 4 ++-- src/app/core/browse/browse.service.spec.ts | 8 ++++---- src/app/core/data/collection-data.service.ts | 1 - src/app/core/data/comcol-data.service.spec.ts | 1 - src/app/core/data/community-data.service.ts | 1 - src/app/core/data/data.service.spec.ts | 1 - src/app/core/data/data.service.ts | 9 ++++----- src/app/core/data/dspace-object-data.service.spec.ts | 2 +- src/app/core/data/dspace-object-data.service.ts | 1 - src/app/core/data/item-data.service.ts | 1 - src/app/core/data/metadata-schema-data.service.ts | 1 - src/app/core/data/request.service.ts | 10 +++++----- src/app/core/data/resource-policy.service.spec.ts | 2 +- src/app/core/data/resource-policy.service.ts | 1 - src/app/core/eperson/group-eperson.service.ts | 1 - src/app/core/shared/operators.spec.ts | 2 +- src/app/core/shared/operators.ts | 4 ++-- .../core/submission/submission-rest.service.spec.ts | 2 +- src/app/core/submission/submission-rest.service.ts | 2 +- src/app/core/submission/workflowitem-data.service.ts | 1 - src/app/core/submission/workspaceitem-data.service.ts | 1 - src/app/core/tasks/claimed-task-data.service.ts | 5 ----- src/app/core/tasks/pool-task-data.service.ts | 5 ----- src/app/core/tasks/tasks.service.spec.ts | 1 - 26 files changed, 24 insertions(+), 47 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..477a657048 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 @@ -11,7 +11,6 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv import { MockRouter } from '../../shared/mocks/mock-router'; import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core'; import { of as observableOf } from 'rxjs/internal/observable/of'; -import { RemoteData } from '../../core/data/remote-data'; import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; import { Community } from '../../core/shared/community.model'; import { Item } from '../../core/shared/item.model'; diff --git a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts index 98d0cc9cd1..34f9ff200e 100644 --- a/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/+browse-by/+browse-by-date-page/browse-by-date-page.component.ts @@ -82,7 +82,8 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { const date = firstItemRD.payload.firstMetadataValue(metadataField); if (hasValue(date)) { const dateObj = new Date(date); - lowerLimit = dateObj.getFullYear(); + // TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC. + lowerLimit = dateObj.getUTCFullYear(); } } const options = []; diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index cbabe5c3fd..713eb8cae8 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -44,7 +44,7 @@ export class AuthRequestService { map((endpointURL) => this.getEndpointByMethod(endpointURL, method)), distinctUntilChanged(), map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options)), - tap((request: PostRequest) => this.requestService.configure(request, true)), + tap((request: PostRequest) => this.requestService.configure(request)), mergeMap((request: PostRequest) => this.fetchRequest(request)), distinctUntilChanged()); } @@ -55,7 +55,7 @@ export class AuthRequestService { map((endpointURL) => this.getEndpointByMethod(endpointURL, method)), distinctUntilChanged(), map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options)), - tap((request: GetRequest) => this.requestService.configure(request, true)), + tap((request: GetRequest) => this.requestService.configure(request)), mergeMap((request: GetRequest) => this.fetchRequest(request)), distinctUntilChanged()); } diff --git a/src/app/core/browse/browse.service.spec.ts b/src/app/core/browse/browse.service.spec.ts index 725b371c14..55ff7a090e 100644 --- a/src/app/core/browse/browse.service.spec.ts +++ b/src/app/core/browse/browse.service.spec.ts @@ -114,7 +114,7 @@ describe('BrowseService', () => { scheduler.schedule(() => service.getBrowseDefinitions().subscribe()); scheduler.flush(); - expect(requestService.configure).toHaveBeenCalledWith(expected, undefined); + expect(requestService.configure).toHaveBeenCalledWith(expected); }); it('should call RemoteDataBuildService to create the RemoteData Observable', () => { @@ -155,7 +155,7 @@ describe('BrowseService', () => { scheduler.schedule(() => service.getBrowseEntriesFor(new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe()); scheduler.flush(); - expect(requestService.configure).toHaveBeenCalledWith(expected, undefined); + expect(requestService.configure).toHaveBeenCalledWith(expected); }); it('should call RemoteDataBuildService to create the RemoteData Observable', () => { @@ -174,7 +174,7 @@ describe('BrowseService', () => { scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe()); scheduler.flush(); - expect(requestService.configure).toHaveBeenCalledWith(expected, undefined); + expect(requestService.configure).toHaveBeenCalledWith(expected); }); it('should call RemoteDataBuildService to create the RemoteData Observable', () => { @@ -303,7 +303,7 @@ describe('BrowseService', () => { scheduler.schedule(() => service.getFirstItemFor(browseDefinitions[1].id).subscribe()); scheduler.flush(); - expect(requestService.configure).toHaveBeenCalledWith(expected, undefined); + expect(requestService.configure).toHaveBeenCalledWith(expected); }); it('should call RemoteDataBuildService to create the RemoteData Observable', () => { diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index e49267d1f2..7ec31d8970 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -31,7 +31,6 @@ import { SearchParam } from '../cache/models/search-param.model'; @Injectable() export class CollectionDataService extends ComColDataService { protected linkPath = 'collections'; - protected forceBypassCache = false; constructor( protected requestService: RequestService, diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index b5232b0bff..5cc474dff9 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -28,7 +28,6 @@ class NormalizedTestObject extends NormalizedObject { } class TestService extends ComColDataService { - protected forceBypassCache = false; constructor( protected requestService: RequestService, diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 8db4d762eb..cc55fe6869 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -24,7 +24,6 @@ export class CommunityDataService extends ComColDataService { protected linkPath = 'communities'; protected topLinkPath = 'communities/search/top'; protected cds = this; - protected forceBypassCache = false; constructor( protected requestService: RequestService, diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index dede6f8ae2..ab3cf0ac16 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -24,7 +24,6 @@ class NormalizedTestObject extends NormalizedObject { } class TestService extends DataService { - protected forceBypassCache = false; constructor( protected requestService: RequestService, diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index ad0db51980..1ed3c7a4a1 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -45,7 +45,6 @@ export abstract class DataService { protected abstract store: Store; protected abstract linkPath: string; protected abstract halService: HALEndpointService; - protected abstract forceBypassCache = false; protected abstract objectCache: ObjectCacheService; protected abstract notificationsService: NotificationsService; protected abstract http: HttpClient; @@ -131,7 +130,7 @@ export abstract class DataService { first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); - this.requestService.configure(request, this.forceBypassCache); + this.requestService.configure(request); }); return this.rdbService.buildList(hrefObs) as Observable>>; @@ -154,14 +153,14 @@ export abstract class DataService { find((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id); - this.requestService.configure(request, this.forceBypassCache); + this.requestService.configure(request); }); return this.rdbService.buildSingle(hrefObs); } findByHref(href: string, options?: HttpOptions): Observable> { - this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), href, null, options), this.forceBypassCache); + this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), href, null, options)); return this.rdbService.buildSingle(href); } @@ -192,7 +191,7 @@ export abstract class DataService { first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); - this.requestService.configure(request, true); + this.requestService.configure(request); }); return this.rdbService.buildList(hrefObs) as Observable>>; diff --git a/src/app/core/data/dspace-object-data.service.spec.ts b/src/app/core/data/dspace-object-data.service.spec.ts index a0bba214ae..7047db6065 100644 --- a/src/app/core/data/dspace-object-data.service.spec.ts +++ b/src/app/core/data/dspace-object-data.service.spec.ts @@ -72,7 +72,7 @@ describe('DSpaceObjectDataService', () => { scheduler.schedule(() => service.findById(testObject.uuid)); scheduler.flush(); - expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestURL, testObject.uuid), false); + expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestURL, testObject.uuid)); }); it('should return a RemoteData for the object with the given ID', () => { diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 4f0653f416..bb02afbcd1 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -18,7 +18,6 @@ import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; /* tslint:disable:max-classes-per-file */ class DataServiceImpl extends DataService { protected linkPath = 'dso'; - protected forceBypassCache = false; constructor( protected requestService: RequestService, diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index de05dad0c1..e616cb8020 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -41,7 +41,6 @@ import { PaginatedList } from './paginated-list'; @Injectable() export class ItemDataService extends DataService { protected linkPath = 'items'; - protected forceBypassCache = false; constructor( protected requestService: RequestService, diff --git a/src/app/core/data/metadata-schema-data.service.ts b/src/app/core/data/metadata-schema-data.service.ts index 4baca6e8ed..b15dd6865f 100644 --- a/src/app/core/data/metadata-schema-data.service.ts +++ b/src/app/core/data/metadata-schema-data.service.ts @@ -19,7 +19,6 @@ import { MetadataSchema } from '../metadata/metadata-schema.model'; /* tslint:disable:max-classes-per-file */ class DataServiceImpl extends DataService { protected linkPath = 'metadataschemas'; - protected forceBypassCache = false; constructor( protected requestService: RequestService, diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 0980d48537..ec0af28595 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -147,12 +147,12 @@ export class RequestService { * @param {RestRequest} request The request to send out * @param {boolean} forceBypassCache When true, a new request is always dispatched */ - configure(request: RestRequest, forceBypassCache: boolean = false): void { + configure(request: RestRequest): void { const isGetRequest = request.method === RestRequestMethod.GET; - if (forceBypassCache) { - this.clearRequestsOnTheirWayToTheStore(request); - } - if (!isGetRequest || (forceBypassCache && !this.isPending(request)) || !this.isCachedOrPending(request)) { + // if (forceBypassCache) { + // this.clearRequestsOnTheirWayToTheStore(request); + // } + if (!isGetRequest || !this.isCachedOrPending(request)) { this.dispatchRequest(request); if (isGetRequest) { this.trackRequestsOnTheirWayToTheStore(request); diff --git a/src/app/core/data/resource-policy.service.spec.ts b/src/app/core/data/resource-policy.service.spec.ts index 35d28684a7..1a02171be3 100644 --- a/src/app/core/data/resource-policy.service.spec.ts +++ b/src/app/core/data/resource-policy.service.spec.ts @@ -61,7 +61,7 @@ describe('ResourcePolicyService', () => { scheduler.schedule(() => service.findByHref(requestURL)); scheduler.flush(); - expect(requestService.configure).toHaveBeenCalledWith(new GetRequest(requestUUID, requestURL, null), false); + expect(requestService.configure).toHaveBeenCalledWith(new GetRequest(requestUUID, requestURL, null)); }); it('should return a RemoteData for the object with the given URL', () => { diff --git a/src/app/core/data/resource-policy.service.ts b/src/app/core/data/resource-policy.service.ts index 1a6a1afedc..1574111232 100644 --- a/src/app/core/data/resource-policy.service.ts +++ b/src/app/core/data/resource-policy.service.ts @@ -22,7 +22,6 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; /* tslint:disable:max-classes-per-file */ class DataServiceImpl extends DataService { protected linkPath = 'resourcepolicies'; - protected forceBypassCache = false; constructor( protected requestService: RequestService, diff --git a/src/app/core/eperson/group-eperson.service.ts b/src/app/core/eperson/group-eperson.service.ts index 07a1bb6aba..2014e6120a 100644 --- a/src/app/core/eperson/group-eperson.service.ts +++ b/src/app/core/eperson/group-eperson.service.ts @@ -27,7 +27,6 @@ import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; export class GroupEpersonService extends EpersonService { protected linkPath = 'groups'; protected browseEndpoint = ''; - protected forceBypassCache = false; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/shared/operators.spec.ts b/src/app/core/shared/operators.spec.ts index 548a3f1339..ec069772f8 100644 --- a/src/app/core/shared/operators.spec.ts +++ b/src/app/core/shared/operators.spec.ts @@ -148,7 +148,7 @@ describe('Core Module - RxJS Operators', () => { scheduler.schedule(() => source.pipe(configureRequest(requestService)).subscribe()); scheduler.flush(); - expect(requestService.configure).toHaveBeenCalledWith(testRequest, undefined); + expect(requestService.configure).toHaveBeenCalledWith(testRequest); }); }); diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index d46c688e68..b4c7c8f1a9 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -52,9 +52,9 @@ export const getResourceLinksFromResponse = () => map((response: DSOSuccessResponse) => response.resourceSelfLinks), ); -export const configureRequest = (requestService: RequestService, forceBypassCache?: boolean) => +export const configureRequest = (requestService: RequestService) => (source: Observable): Observable => - source.pipe(tap((request: RestRequest) => requestService.configure(request, forceBypassCache))); + source.pipe(tap((request: RestRequest) => requestService.configure(request))); export const getRemoteDataPayload = () => (source: Observable>): Observable => diff --git a/src/app/core/submission/submission-rest.service.spec.ts b/src/app/core/submission/submission-rest.service.spec.ts index 6e748c5575..6f3e24e9c4 100644 --- a/src/app/core/submission/submission-rest.service.spec.ts +++ b/src/app/core/submission/submission-rest.service.spec.ts @@ -62,7 +62,7 @@ describe('SubmissionRestService test suite', () => { scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe()); scheduler.flush(); - expect(requestService.configure).toHaveBeenCalledWith(expected, true); + expect(requestService.configure).toHaveBeenCalledWith(expected); }); }); diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index e2b8bb01c8..9df4405c07 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -109,7 +109,7 @@ export class SubmissionRestService { filter((href: string) => isNotEmpty(href)), distinctUntilChanged(), map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)), - tap((request: RestRequest) => this.requestService.configure(request, true)), + tap((request: RestRequest) => this.requestService.configure(request)), flatMap(() => this.fetchRequest(requestId)), distinctUntilChanged()); } diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index fd769745da..6877c3965c 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -20,7 +20,6 @@ import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; @Injectable() export class WorkflowItemDataService extends DataService { protected linkPath = 'workflowitems'; - protected forceBypassCache = true; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index de671d57c7..59f1e78e06 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -20,7 +20,6 @@ import { WorkspaceItem } from './models/workspaceitem.model'; @Injectable() export class WorkspaceitemDataService extends DataService { protected linkPath = 'workspaceitems'; - protected forceBypassCache = true; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index dda42e1c67..a2835dbd1e 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -27,11 +27,6 @@ export class ClaimedTaskDataService extends TasksService { */ protected linkPath = 'claimedtasks'; - /** - * When true, a new request is always dispatched - */ - protected forceBypassCache = true; - /** * Initialize instance variables * diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index 1a93450d4d..c031451525 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -27,11 +27,6 @@ export class PoolTaskDataService extends TasksService { */ protected linkPath = 'pooltasks'; - /** - * When true, a new request is always dispatched - */ - protected forceBypassCache = true; - /** * Initialize instance variables * diff --git a/src/app/core/tasks/tasks.service.spec.ts b/src/app/core/tasks/tasks.service.spec.ts index 753ce2ddd5..3ca9b8ea8f 100644 --- a/src/app/core/tasks/tasks.service.spec.ts +++ b/src/app/core/tasks/tasks.service.spec.ts @@ -29,7 +29,6 @@ class TestTask extends TaskObject { class TestService extends TasksService { protected linkPath = LINK_NAME; - protected forceBypassCache = true; constructor( protected requestService: RequestService, From d2897cec3f41228bfd7b74432a23c4b4cb65ec81 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 29 Aug 2019 16:36:28 -0700 Subject: [PATCH 096/115] Removed final reference to forceBypassCache param. --- src/app/core/data/request.service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index ec0af28595..f2f66ebf99 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -145,13 +145,9 @@ export class RequestService { * Configure a certain request * Used to make sure a request is in the cache * @param {RestRequest} request The request to send out - * @param {boolean} forceBypassCache When true, a new request is always dispatched */ configure(request: RestRequest): void { const isGetRequest = request.method === RestRequestMethod.GET; - // if (forceBypassCache) { - // this.clearRequestsOnTheirWayToTheStore(request); - // } if (!isGetRequest || !this.isCachedOrPending(request)) { this.dispatchRequest(request); if (isGetRequest) { From 6e3127c3a68b1530b29351a24e5e5ff06f725e0d Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 12 Sep 2019 16:14:13 -0700 Subject: [PATCH 097/115] Setting cache period to zero for all instances where forceBypassCache was previously true. --- src/app/core/auth/auth-request.service.ts | 2 ++ src/app/core/data/data.service.ts | 14 +++++++++++++- src/app/core/submission/submission-rest.service.ts | 3 ++- .../core/submission/workflowitem-data.service.ts | 1 + .../core/submission/workspaceitem-data.service.ts | 1 + src/app/core/tasks/claimed-task-data.service.ts | 2 ++ src/app/core/tasks/pool-task-data.service.ts | 2 ++ 7 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 713eb8cae8..4c8ce273bd 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -44,6 +44,7 @@ export class AuthRequestService { map((endpointURL) => this.getEndpointByMethod(endpointURL, method)), distinctUntilChanged(), map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options)), + map ((request: PostRequest) => request.responseMsToLive = 0), tap((request: PostRequest) => this.requestService.configure(request)), mergeMap((request: PostRequest) => this.fetchRequest(request)), distinctUntilChanged()); @@ -55,6 +56,7 @@ export class AuthRequestService { map((endpointURL) => this.getEndpointByMethod(endpointURL, method)), distinctUntilChanged(), map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options)), + map ((request: GetRequest) => request.responseMsToLive = 0), tap((request: GetRequest) => this.requestService.configure(request)), mergeMap((request: GetRequest) => this.fetchRequest(request)), distinctUntilChanged()); diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 1ed3c7a4a1..ae7fccc91b 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -49,6 +49,7 @@ export abstract class DataService { protected abstract notificationsService: NotificationsService; protected abstract http: HttpClient; protected abstract comparator: ChangeAnalyzer; + protected resetMsToLive = false; public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable @@ -130,6 +131,9 @@ export abstract class DataService { first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + if (this.resetMsToLive) { + request.responseMsToLive = 0; + } this.requestService.configure(request); }); @@ -153,6 +157,9 @@ export abstract class DataService { find((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id); + if (this.resetMsToLive) { + request.responseMsToLive = 0; + } this.requestService.configure(request); }); @@ -160,7 +167,11 @@ export abstract class DataService { } findByHref(href: string, options?: HttpOptions): Observable> { - this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), href, null, options)); + const request = new GetRequest(this.requestService.generateRequestId(), href, null, options); + if (this.resetMsToLive) { + request.responseMsToLive = 0; + } + this.requestService.configure(request); return this.rdbService.buildSingle(href); } @@ -191,6 +202,7 @@ export abstract class DataService { first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + request.responseMsToLive = 0; this.requestService.configure(request); }); diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index 9df4405c07..fb15975caa 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -6,7 +6,7 @@ import { distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/ import { RequestService } from '../data/request.service'; import { isNotEmpty } from '../../shared/empty.util'; import { - DeleteRequest, + DeleteRequest, GetRequest, PostRequest, RestRequest, SubmissionDeleteRequest, @@ -109,6 +109,7 @@ export class SubmissionRestService { filter((href: string) => isNotEmpty(href)), distinctUntilChanged(), map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)), + map ((request: RestRequest) => request.responseMsToLive = 0), tap((request: RestRequest) => this.requestService.configure(request)), flatMap(() => this.fetchRequest(requestId)), distinctUntilChanged()); diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index 6877c3965c..dca10d2b30 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -20,6 +20,7 @@ import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; @Injectable() export class WorkflowItemDataService extends DataService { protected linkPath = 'workflowitems'; + protected resetMsToLive = true; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 59f1e78e06..c2b6ab5816 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -20,6 +20,7 @@ import { WorkspaceItem } from './models/workspaceitem.model'; @Injectable() export class WorkspaceitemDataService extends DataService { protected linkPath = 'workspaceitems'; + protected resetMsToLive = true; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index a2835dbd1e..aa85b3e8b5 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -22,6 +22,8 @@ import { ProcessTaskResponse } from './models/process-task-response'; @Injectable() export class ClaimedTaskDataService extends TasksService { + protected resetMsToLive = true; + /** * The endpoint link name */ diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index c031451525..8a12af0b68 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -27,6 +27,8 @@ export class PoolTaskDataService extends TasksService { */ protected linkPath = 'pooltasks'; + protected resetMsToLive = true; + /** * Initialize instance variables * From d449bd70a08e84677b0728f3680dd96d3ce48af3 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 12 Sep 2019 16:57:11 -0700 Subject: [PATCH 098/115] Fixes unit test and problem in map fuction. --- src/app/core/auth/auth-request.service.ts | 10 ++++++++-- .../core/submission/submission-rest.service.spec.ts | 2 ++ src/app/core/submission/submission-rest.service.ts | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 4c8ce273bd..e717de9df9 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -44,7 +44,10 @@ export class AuthRequestService { map((endpointURL) => this.getEndpointByMethod(endpointURL, method)), distinctUntilChanged(), map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options)), - map ((request: PostRequest) => request.responseMsToLive = 0), + map ((request: PostRequest) => { + request.responseMsToLive = 0; + return request; + }), tap((request: PostRequest) => this.requestService.configure(request)), mergeMap((request: PostRequest) => this.fetchRequest(request)), distinctUntilChanged()); @@ -56,7 +59,10 @@ export class AuthRequestService { map((endpointURL) => this.getEndpointByMethod(endpointURL, method)), distinctUntilChanged(), map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options)), - map ((request: GetRequest) => request.responseMsToLive = 0), + map ((request: GetRequest) => { + request.responseMsToLive = 0; + return request; + }), tap((request: GetRequest) => this.requestService.configure(request)), mergeMap((request: GetRequest) => this.fetchRequest(request)), distinctUntilChanged()); diff --git a/src/app/core/submission/submission-rest.service.spec.ts b/src/app/core/submission/submission-rest.service.spec.ts index 6f3e24e9c4..8e47bbd994 100644 --- a/src/app/core/submission/submission-rest.service.spec.ts +++ b/src/app/core/submission/submission-rest.service.spec.ts @@ -59,6 +59,8 @@ describe('SubmissionRestService test suite', () => { describe('getDataById', () => { it('should configure a new SubmissionRequest', () => { const expected = new SubmissionRequest(requestService.generateRequestId(), resourceHref); + // set cache time to zero + expected.responseMsToLive = 0; scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe()); scheduler.flush(); diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index fb15975caa..58aa507314 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -109,7 +109,10 @@ export class SubmissionRestService { filter((href: string) => isNotEmpty(href)), distinctUntilChanged(), map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)), - map ((request: RestRequest) => request.responseMsToLive = 0), + map ((request: RestRequest) => { + request.responseMsToLive = 0; + return request; + }), tap((request: RestRequest) => this.requestService.configure(request)), flatMap(() => this.fetchRequest(requestId)), distinctUntilChanged()); From a50e568899902953f251c46adcd4efcd6e160a85 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Mon, 16 Sep 2019 10:00:45 -0700 Subject: [PATCH 099/115] Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. --- src/app/core/data/data.service.ts | 11 +++++++---- src/app/core/submission/workflowitem-data.service.ts | 2 +- src/app/core/submission/workspaceitem-data.service.ts | 2 +- src/app/core/tasks/claimed-task-data.service.ts | 2 +- src/app/core/tasks/pool-task-data.service.ts | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index ae7fccc91b..dcba9f1643 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -49,7 +49,10 @@ export abstract class DataService { protected abstract notificationsService: NotificationsService; protected abstract http: HttpClient; protected abstract comparator: ChangeAnalyzer; - protected resetMsToLive = false; + /** + * Allows subclasses to reset the response cache time. + */ + protected resetMsToLive: number; public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable @@ -132,7 +135,7 @@ export abstract class DataService { .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); if (this.resetMsToLive) { - request.responseMsToLive = 0; + request.responseMsToLive = this.resetMsToLive; } this.requestService.configure(request); }); @@ -158,7 +161,7 @@ export abstract class DataService { .subscribe((href: string) => { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id); if (this.resetMsToLive) { - request.responseMsToLive = 0; + request.responseMsToLive = this.resetMsToLive; } this.requestService.configure(request); }); @@ -169,7 +172,7 @@ export abstract class DataService { findByHref(href: string, options?: HttpOptions): Observable> { const request = new GetRequest(this.requestService.generateRequestId(), href, null, options); if (this.resetMsToLive) { - request.responseMsToLive = 0; + request.responseMsToLive = this.resetMsToLive; } this.requestService.configure(request); return this.rdbService.buildSingle(href); diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index dca10d2b30..b641531892 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -20,7 +20,7 @@ import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; @Injectable() export class WorkflowItemDataService extends DataService { protected linkPath = 'workflowitems'; - protected resetMsToLive = true; + protected resetMsToLive = 0; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index c2b6ab5816..b49cb065bd 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -20,7 +20,7 @@ import { WorkspaceItem } from './models/workspaceitem.model'; @Injectable() export class WorkspaceitemDataService extends DataService { protected linkPath = 'workspaceitems'; - protected resetMsToLive = true; + protected resetMsToLive = 0; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index aa85b3e8b5..546c4343b6 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -22,7 +22,7 @@ import { ProcessTaskResponse } from './models/process-task-response'; @Injectable() export class ClaimedTaskDataService extends TasksService { - protected resetMsToLive = true; + protected resetMsToLive = 0; /** * The endpoint link name diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index 8a12af0b68..ee3568e585 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -27,7 +27,7 @@ export class PoolTaskDataService extends TasksService { */ protected linkPath = 'pooltasks'; - protected resetMsToLive = true; + protected resetMsToLive = 0; /** * Initialize instance variables From 2a1c85937c3b88fee985902f9d71f058f3882038 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Mon, 16 Sep 2019 11:56:04 -0700 Subject: [PATCH 100/115] Fixed unit test that was missing from project before rebase. --- src/app/core/data/relationship.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/data/relationship.service.spec.ts b/src/app/core/data/relationship.service.spec.ts index 31513bb779..7b8ed4a60b 100644 --- a/src/app/core/data/relationship.service.spec.ts +++ b/src/app/core/data/relationship.service.spec.ts @@ -106,7 +106,7 @@ describe('RelationshipService', () => { it('should send a DeleteRequest', () => { const expected = new DeleteRequest(requestService.generateRequestId(), relationshipsEndpointURL + '/' + relationship1.uuid); - expect(requestService.configure).toHaveBeenCalledWith(expected, undefined); + expect(requestService.configure).toHaveBeenCalledWith(expected); }); it('should clear the related items their cache', () => { From 8ae5e415db50101918efe1b2a552157ad839a0ef Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Mon, 16 Sep 2019 12:13:24 -0700 Subject: [PATCH 101/115] Explicitly checking for undefined in dataService (since I think 0 is not truthy for number). --- src/app/core/data/data.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index dcba9f1643..aad527ea7a 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -134,7 +134,7 @@ export abstract class DataService { first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); - if (this.resetMsToLive) { + if (this.resetMsToLive !== undefined) { request.responseMsToLive = this.resetMsToLive; } this.requestService.configure(request); @@ -160,7 +160,7 @@ export abstract class DataService { find((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id); - if (this.resetMsToLive) { + if (this.resetMsToLive !== undefined) { request.responseMsToLive = this.resetMsToLive; } this.requestService.configure(request); @@ -171,7 +171,7 @@ export abstract class DataService { findByHref(href: string, options?: HttpOptions): Observable> { const request = new GetRequest(this.requestService.generateRequestId(), href, null, options); - if (this.resetMsToLive) { + if (this.resetMsToLive !== undefined) { request.responseMsToLive = this.resetMsToLive; } this.requestService.configure(request); From 7bda63bc5a78815ae2dea58829e42e5bc2adb030 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Sun, 22 Sep 2019 21:51:04 -0700 Subject: [PATCH 102/115] Renamed cache property and replaced conditional with hasValue. --- src/app/core/data/data.service.ts | 14 +++++++------- .../core/submission/workflowitem-data.service.ts | 2 +- .../core/submission/workspaceitem-data.service.ts | 2 +- src/app/core/tasks/claimed-task-data.service.ts | 2 +- src/app/core/tasks/pool-task-data.service.ts | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index aad527ea7a..8362358d61 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -52,7 +52,7 @@ export abstract class DataService { /** * Allows subclasses to reset the response cache time. */ - protected resetMsToLive: number; + protected responseMsToLive: number; public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable @@ -134,8 +134,8 @@ export abstract class DataService { first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); - if (this.resetMsToLive !== undefined) { - request.responseMsToLive = this.resetMsToLive; + if (hasValue(this.responseMsToLive)) { + request.responseMsToLive = this.responseMsToLive; } this.requestService.configure(request); }); @@ -160,8 +160,8 @@ export abstract class DataService { find((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id); - if (this.resetMsToLive !== undefined) { - request.responseMsToLive = this.resetMsToLive; + if (hasValue(this.responseMsToLive)) { + request.responseMsToLive = this.responseMsToLive; } this.requestService.configure(request); }); @@ -171,8 +171,8 @@ export abstract class DataService { findByHref(href: string, options?: HttpOptions): Observable> { const request = new GetRequest(this.requestService.generateRequestId(), href, null, options); - if (this.resetMsToLive !== undefined) { - request.responseMsToLive = this.resetMsToLive; + if (hasValue(this.responseMsToLive)) { + request.responseMsToLive = this.responseMsToLive; } this.requestService.configure(request); return this.rdbService.buildSingle(href); diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index b641531892..218de9c81a 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -20,7 +20,7 @@ import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; @Injectable() export class WorkflowItemDataService extends DataService { protected linkPath = 'workflowitems'; - protected resetMsToLive = 0; + protected responseMsToLive = 0; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index b49cb065bd..70e07edd5e 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -20,7 +20,7 @@ import { WorkspaceItem } from './models/workspaceitem.model'; @Injectable() export class WorkspaceitemDataService extends DataService { protected linkPath = 'workspaceitems'; - protected resetMsToLive = 0; + protected responseMsToLive = 0; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index 546c4343b6..fecffaabe1 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -22,7 +22,7 @@ import { ProcessTaskResponse } from './models/process-task-response'; @Injectable() export class ClaimedTaskDataService extends TasksService { - protected resetMsToLive = 0; + protected responseMsToLive = 0; /** * The endpoint link name diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index ee3568e585..62689c4cfc 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -27,7 +27,7 @@ export class PoolTaskDataService extends TasksService { */ protected linkPath = 'pooltasks'; - protected resetMsToLive = 0; + protected responseMsToLive = 0; /** * Initialize instance variables From 121e564e12cd72fab5f20ab5d6264e9f3bc84dba Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 28 Aug 2019 17:05:37 -0700 Subject: [PATCH 103/115] Refactored to remove forceBypassCache param from requestService and from data service classes. --- src/app/core/tasks/pool-task-data.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index 62689c4cfc..a04c0b20ea 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -27,8 +27,11 @@ export class PoolTaskDataService extends TasksService { */ protected linkPath = 'pooltasks'; +<<<<<<< HEAD protected responseMsToLive = 0; +======= +>>>>>>> Refactored to remove forceBypassCache param from requestService and from data service classes. /** * Initialize instance variables * From 34defdc5cd6e7360c6bed25fe235756c0a0ed91c Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 12 Sep 2019 16:14:13 -0700 Subject: [PATCH 104/115] Setting cache period to zero for all instances where forceBypassCache was previously true. --- src/app/core/submission/submission-rest.service.ts | 4 ++++ src/app/core/submission/workflowitem-data.service.ts | 4 ++++ src/app/core/submission/workspaceitem-data.service.ts | 4 ++++ src/app/core/tasks/claimed-task-data.service.ts | 4 ++++ src/app/core/tasks/pool-task-data.service.ts | 5 +++++ 5 files changed, 21 insertions(+) diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index 58aa507314..22594e134d 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -109,10 +109,14 @@ export class SubmissionRestService { filter((href: string) => isNotEmpty(href)), distinctUntilChanged(), map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)), +<<<<<<< HEAD map ((request: RestRequest) => { request.responseMsToLive = 0; return request; }), +======= + map ((request: RestRequest) => request.responseMsToLive = 0), +>>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. tap((request: RestRequest) => this.requestService.configure(request)), flatMap(() => this.fetchRequest(requestId)), distinctUntilChanged()); diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index 218de9c81a..4e65b5c58c 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -20,7 +20,11 @@ import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; @Injectable() export class WorkflowItemDataService extends DataService { protected linkPath = 'workflowitems'; +<<<<<<< HEAD protected responseMsToLive = 0; +======= + protected resetMsToLive = true; +>>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 70e07edd5e..4bf44e8764 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -20,7 +20,11 @@ import { WorkspaceItem } from './models/workspaceitem.model'; @Injectable() export class WorkspaceitemDataService extends DataService { protected linkPath = 'workspaceitems'; +<<<<<<< HEAD protected responseMsToLive = 0; +======= + protected resetMsToLive = true; +>>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index fecffaabe1..5af62318d3 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -22,7 +22,11 @@ import { ProcessTaskResponse } from './models/process-task-response'; @Injectable() export class ClaimedTaskDataService extends TasksService { +<<<<<<< HEAD protected responseMsToLive = 0; +======= + protected resetMsToLive = true; +>>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. /** * The endpoint link name diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index a04c0b20ea..6ceb3915de 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -27,11 +27,16 @@ export class PoolTaskDataService extends TasksService { */ protected linkPath = 'pooltasks'; +<<<<<<< HEAD <<<<<<< HEAD protected responseMsToLive = 0; ======= >>>>>>> Refactored to remove forceBypassCache param from requestService and from data service classes. +======= + protected resetMsToLive = true; + +>>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. /** * Initialize instance variables * From fbcffd80979d5411b43b76632b86b180617006ea Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 12 Sep 2019 16:57:11 -0700 Subject: [PATCH 105/115] Fixes unit test and problem in map fuction. --- src/app/core/submission/submission-rest.service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index 22594e134d..58aa507314 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -109,14 +109,10 @@ export class SubmissionRestService { filter((href: string) => isNotEmpty(href)), distinctUntilChanged(), map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)), -<<<<<<< HEAD map ((request: RestRequest) => { request.responseMsToLive = 0; return request; }), -======= - map ((request: RestRequest) => request.responseMsToLive = 0), ->>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. tap((request: RestRequest) => this.requestService.configure(request)), flatMap(() => this.fetchRequest(requestId)), distinctUntilChanged()); From 934f5a1bd0254870e22a04a25b2cf11e9d1e9254 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Mon, 16 Sep 2019 10:00:45 -0700 Subject: [PATCH 106/115] Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. --- src/app/core/data/data.service.ts | 19 +++++++++++++++++++ .../submission/workflowitem-data.service.ts | 4 ---- .../submission/workspaceitem-data.service.ts | 4 ---- .../core/tasks/claimed-task-data.service.ts | 4 ++++ src/app/core/tasks/pool-task-data.service.ts | 4 ++++ 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 8362358d61..9c6eda0793 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -52,7 +52,11 @@ export abstract class DataService { /** * Allows subclasses to reset the response cache time. */ +<<<<<<< HEAD protected responseMsToLive: number; +======= + protected resetMsToLive: number; +>>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable @@ -134,8 +138,13 @@ export abstract class DataService { first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); +<<<<<<< HEAD if (hasValue(this.responseMsToLive)) { request.responseMsToLive = this.responseMsToLive; +======= + if (this.resetMsToLive) { + request.responseMsToLive = this.resetMsToLive; +>>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. } this.requestService.configure(request); }); @@ -160,8 +169,13 @@ export abstract class DataService { find((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id); +<<<<<<< HEAD if (hasValue(this.responseMsToLive)) { request.responseMsToLive = this.responseMsToLive; +======= + if (this.resetMsToLive) { + request.responseMsToLive = this.resetMsToLive; +>>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. } this.requestService.configure(request); }); @@ -171,8 +185,13 @@ export abstract class DataService { findByHref(href: string, options?: HttpOptions): Observable> { const request = new GetRequest(this.requestService.generateRequestId(), href, null, options); +<<<<<<< HEAD if (hasValue(this.responseMsToLive)) { request.responseMsToLive = this.responseMsToLive; +======= + if (this.resetMsToLive) { + request.responseMsToLive = this.resetMsToLive; +>>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. } this.requestService.configure(request); return this.rdbService.buildSingle(href); diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index 4e65b5c58c..218de9c81a 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -20,11 +20,7 @@ import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; @Injectable() export class WorkflowItemDataService extends DataService { protected linkPath = 'workflowitems'; -<<<<<<< HEAD protected responseMsToLive = 0; -======= - protected resetMsToLive = true; ->>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 4bf44e8764..70e07edd5e 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -20,11 +20,7 @@ import { WorkspaceItem } from './models/workspaceitem.model'; @Injectable() export class WorkspaceitemDataService extends DataService { protected linkPath = 'workspaceitems'; -<<<<<<< HEAD protected responseMsToLive = 0; -======= - protected resetMsToLive = true; ->>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index 5af62318d3..31d6448a1a 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -22,11 +22,15 @@ import { ProcessTaskResponse } from './models/process-task-response'; @Injectable() export class ClaimedTaskDataService extends TasksService { +<<<<<<< HEAD <<<<<<< HEAD protected responseMsToLive = 0; ======= protected resetMsToLive = true; >>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. +======= + protected resetMsToLive = 0; +>>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. /** * The endpoint link name diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index 6ceb3915de..77fff02618 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -27,6 +27,7 @@ export class PoolTaskDataService extends TasksService { */ protected linkPath = 'pooltasks'; +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD protected responseMsToLive = 0; @@ -35,6 +36,9 @@ export class PoolTaskDataService extends TasksService { >>>>>>> Refactored to remove forceBypassCache param from requestService and from data service classes. ======= protected resetMsToLive = true; +======= + protected resetMsToLive = 0; +>>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. >>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. /** From 2d9fdef9743188f63dea2731d1cbd74fed7a159c Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Mon, 16 Sep 2019 12:13:24 -0700 Subject: [PATCH 107/115] Explicitly checking for undefined in dataService (since I think 0 is not truthy for number). --- src/app/core/data/data.service.ts | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 9c6eda0793..dbe98ddf89 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -52,11 +52,7 @@ export abstract class DataService { /** * Allows subclasses to reset the response cache time. */ -<<<<<<< HEAD protected responseMsToLive: number; -======= - protected resetMsToLive: number; ->>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable @@ -138,13 +134,8 @@ export abstract class DataService { first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); -<<<<<<< HEAD if (hasValue(this.responseMsToLive)) { request.responseMsToLive = this.responseMsToLive; -======= - if (this.resetMsToLive) { - request.responseMsToLive = this.resetMsToLive; ->>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. } this.requestService.configure(request); }); @@ -169,13 +160,8 @@ export abstract class DataService { find((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindByIDRequest(this.requestService.generateRequestId(), href, id); -<<<<<<< HEAD if (hasValue(this.responseMsToLive)) { request.responseMsToLive = this.responseMsToLive; -======= - if (this.resetMsToLive) { - request.responseMsToLive = this.resetMsToLive; ->>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. } this.requestService.configure(request); }); @@ -185,13 +171,9 @@ export abstract class DataService { findByHref(href: string, options?: HttpOptions): Observable> { const request = new GetRequest(this.requestService.generateRequestId(), href, null, options); -<<<<<<< HEAD + if (hasValue(this.responseMsToLive)) { request.responseMsToLive = this.responseMsToLive; -======= - if (this.resetMsToLive) { - request.responseMsToLive = this.resetMsToLive; ->>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. } this.requestService.configure(request); return this.rdbService.buildSingle(href); From 5c2b7767ed43a56f740bdec15539a5804bad0b84 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Sun, 22 Sep 2019 21:51:04 -0700 Subject: [PATCH 108/115] Renamed cache property and replaced conditional with hasValue. --- src/app/core/data/data.service.ts | 1 - src/app/core/tasks/claimed-task-data.service.ts | 4 ++++ src/app/core/tasks/pool-task-data.service.ts | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index dbe98ddf89..8362358d61 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -171,7 +171,6 @@ export abstract class DataService { findByHref(href: string, options?: HttpOptions): Observable> { const request = new GetRequest(this.requestService.generateRequestId(), href, null, options); - if (hasValue(this.responseMsToLive)) { request.responseMsToLive = this.responseMsToLive; } diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index 31d6448a1a..6341c2713a 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -22,6 +22,7 @@ import { ProcessTaskResponse } from './models/process-task-response'; @Injectable() export class ClaimedTaskDataService extends TasksService { +<<<<<<< HEAD <<<<<<< HEAD <<<<<<< HEAD protected responseMsToLive = 0; @@ -31,6 +32,9 @@ export class ClaimedTaskDataService extends TasksService { ======= protected resetMsToLive = 0; >>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. +======= + protected responseMsToLive = 0; +>>>>>>> Renamed cache property and replaced conditional with hasValue. /** * The endpoint link name diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index 77fff02618..f763abb1cc 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -29,6 +29,7 @@ export class PoolTaskDataService extends TasksService { <<<<<<< HEAD <<<<<<< HEAD +<<<<<<< HEAD <<<<<<< HEAD protected responseMsToLive = 0; @@ -39,6 +40,9 @@ export class PoolTaskDataService extends TasksService { ======= protected resetMsToLive = 0; >>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. +======= + protected responseMsToLive = 0; +>>>>>>> Renamed cache property and replaced conditional with hasValue. >>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. /** From 255e17b1a98c0d646b5547532d68c185a433606e Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 26 Sep 2019 14:13:17 -0700 Subject: [PATCH 109/115] Removed a missed forcedBypassCache property. --- src/app/core/data/bitstream-format-data.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/core/data/bitstream-format-data.service.ts b/src/app/core/data/bitstream-format-data.service.ts index a5638183c0..bdf9b16acf 100644 --- a/src/app/core/data/bitstream-format-data.service.ts +++ b/src/app/core/data/bitstream-format-data.service.ts @@ -38,7 +38,6 @@ const selectedBitstreamFormatSelector = createSelector(bitstreamFormatsStateSele export class BitstreamFormatDataService extends DataService { protected linkPath = 'bitstreamformats'; - protected forceBypassCache = false; constructor( protected requestService: RequestService, From 2475de726fe09855f8cd78b95646332db508a8fb Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 3 Oct 2019 16:37:52 -0700 Subject: [PATCH 110/115] Tentative fix for the mydspace submission requests. --- src/app/core/data/request.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index f2f66ebf99..55186e5382 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -19,7 +19,7 @@ import { } from '../index/index.selectors'; import { UUIDService } from '../shared/uuid.service'; import { RequestConfigureAction, RequestExecuteAction, RequestRemoveAction } from './request.actions'; -import { GetRequest, RestRequest } from './request.models'; +import { GetRequest, RestRequest, SubmissionRequest } from './request.models'; import { RequestEntry, RequestState } from './request.reducer'; import { CommitSSBAction } from '../cache/server-sync-buffer.actions'; import { RestRequestMethod } from './rest-request-method'; @@ -148,7 +148,8 @@ export class RequestService { */ configure(request: RestRequest): void { const isGetRequest = request.method === RestRequestMethod.GET; - if (!isGetRequest || !this.isCachedOrPending(request)) { + const isSubmission = request instanceof SubmissionRequest; + if (!isGetRequest || !this.isCachedOrPending(request) || isSubmission) { this.dispatchRequest(request); if (isGetRequest) { this.trackRequestsOnTheirWayToTheStore(request); @@ -222,7 +223,6 @@ export class RequestService { const inReqCache = this.hasByHref(request.href); const inObjCache = this.objectCache.hasBySelfLink(request.href); const isCached = inReqCache || inObjCache; - const isPending = this.isPending(request); return isCached || isPending; } From 70a5e271f4affc4ad938c8ab83a567b74968d979 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Mon, 7 Oct 2019 15:27:45 +0200 Subject: [PATCH 111/115] ensure object cache uses responseMsToLive from request --- .../auth/auth-response-parsing.service.ts | 2 +- .../config/config-response-parsing.service.ts | 2 +- .../data/base-response-parsing.service.ts | 29 ++++++++++--------- .../core/data/dso-response-parsing.service.ts | 2 +- src/app/core/data/request.service.ts | 3 +- .../eperson-response-parsing.service.ts | 2 +- .../integration-response-parsing.service.ts | 2 +- .../submission-response-parsing.service.ts | 8 ++--- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/app/core/auth/auth-response-parsing.service.ts b/src/app/core/auth/auth-response-parsing.service.ts index a5a160531c..8137734c49 100644 --- a/src/app/core/auth/auth-response-parsing.service.ts +++ b/src/app/core/auth/auth-response-parsing.service.ts @@ -25,7 +25,7 @@ export class AuthResponseParsingService extends BaseResponseParsingService imple parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === 200)) { - const response = this.process>(data.payload, request.uuid); + const response = this.process>(data.payload, request); return new AuthStatusResponse(response, data.statusCode, data.statusText); } else { return new AuthStatusResponse(data.payload as NormalizedAuthStatus, data.statusCode, data.statusText); diff --git a/src/app/core/config/config-response-parsing.service.ts b/src/app/core/config/config-response-parsing.service.ts index 08fe581406..d1f49710d3 100644 --- a/src/app/core/config/config-response-parsing.service.ts +++ b/src/app/core/config/config-response-parsing.service.ts @@ -24,7 +24,7 @@ export class ConfigResponseParsingService extends BaseResponseParsingService imp parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === 201 || data.statusCode === 200)) { - const configDefinition = this.process(data.payload, request.uuid); + const configDefinition = this.process(data.payload, request); return new ConfigSuccessResponse(configDefinition, data.statusCode, data.statusText, this.processPageInfo(data.payload)); } else { return new ErrorResponse( diff --git a/src/app/core/data/base-response-parsing.service.ts b/src/app/core/data/base-response-parsing.service.ts index 0ed5dc363c..ea2d71faa7 100644 --- a/src/app/core/data/base-response-parsing.service.ts +++ b/src/app/core/data/base-response-parsing.service.ts @@ -9,6 +9,7 @@ import { PaginatedList } from './paginated-list'; import { isRestDataObject, isRestPaginatedList } from '../cache/builders/normalized-object-build.service'; import { ResourceType } from '../shared/resource-type'; import { getMapsToType } from '../cache/builders/build-decorators'; +import { RestRequest } from './request.models'; /* tslint:disable:max-classes-per-file */ export abstract class BaseResponseParsingService { @@ -16,14 +17,14 @@ export abstract class BaseResponseParsingService { protected abstract objectCache: ObjectCacheService; protected abstract toCache: boolean; - protected process(data: any, requestUUID: string): any { + protected process(data: any, request: RestRequest): any { if (isNotEmpty(data)) { if (hasNoValue(data) || (typeof data !== 'object')) { return data; } else if (isRestPaginatedList(data)) { - return this.processPaginatedList(data, requestUUID); + return this.processPaginatedList(data, request); } else if (Array.isArray(data)) { - return this.processArray(data, requestUUID); + return this.processArray(data, request); } else if (isRestDataObject(data)) { const object = this.deserialize(data); if (isNotEmpty(data._embedded)) { @@ -31,7 +32,7 @@ export abstract class BaseResponseParsingService { .keys(data._embedded) .filter((property) => data._embedded.hasOwnProperty(property)) .forEach((property) => { - const parsedObj = this.process(data._embedded[property], requestUUID); + const parsedObj = this.process(data._embedded[property], request); if (isNotEmpty(parsedObj)) { if (isRestPaginatedList(data._embedded[property])) { object[property] = parsedObj; @@ -45,7 +46,7 @@ export abstract class BaseResponseParsingService { }); } - this.cache(object, requestUUID); + this.cache(object, request); return object; } const result = {}; @@ -53,14 +54,14 @@ export abstract class BaseResponseParsingService { .filter((property) => data.hasOwnProperty(property)) .filter((property) => hasValue(data[property])) .forEach((property) => { - result[property] = this.process(data[property], requestUUID); + result[property] = this.process(data[property], request); }); return result; } } - protected processPaginatedList(data: any, requestUUID: string): PaginatedList { + protected processPaginatedList(data: any, request: RestRequest): PaginatedList { const pageInfo: PageInfo = this.processPageInfo(data); let list = data._embedded; @@ -70,14 +71,14 @@ export abstract class BaseResponseParsingService { } else if (!Array.isArray(list)) { list = this.flattenSingleKeyObject(list); } - const page: ObjectDomain[] = this.processArray(list, requestUUID); + const page: ObjectDomain[] = this.processArray(list, request); return new PaginatedList(pageInfo, page, ); } - protected processArray(data: any, requestUUID: string): ObjectDomain[] { + protected processArray(data: any, request: RestRequest): ObjectDomain[] { let array: ObjectDomain[] = []; data.forEach((datum) => { - array = [...array, this.process(datum, requestUUID)]; + array = [...array, this.process(datum, request)]; } ); return array; @@ -104,17 +105,17 @@ export abstract class BaseResponseParsingService { } } - protected cache(obj, requestUUID) { + protected cache(obj, request: RestRequest) { if (this.toCache) { - this.addToObjectCache(obj, requestUUID); + this.addToObjectCache(obj, request); } } - protected addToObjectCache(co: CacheableObject, requestUUID: string): void { + protected addToObjectCache(co: CacheableObject, request: RestRequest): void { if (hasNoValue(co) || hasNoValue(co.self)) { throw new Error('The server returned an invalid object'); } - this.objectCache.add(co, this.EnvConfig.cache.msToLive.default, requestUUID); + this.objectCache.add(co, hasValue(request.responseMsToLive) ? request.responseMsToLive : this.EnvConfig.cache.msToLive.default, request.uuid); } processPageInfo(payload: any): PageInfo { diff --git a/src/app/core/data/dso-response-parsing.service.ts b/src/app/core/data/dso-response-parsing.service.ts index d6c3b2caa6..d2c21825cc 100644 --- a/src/app/core/data/dso-response-parsing.service.ts +++ b/src/app/core/data/dso-response-parsing.service.ts @@ -30,7 +30,7 @@ export class DSOResponseParsingService extends BaseResponseParsingService implem if (hasValue(data.payload) && hasValue(data.payload.page) && data.payload.page.totalElements === 0) { processRequestDTO = { page: [] }; } else { - processRequestDTO = this.process>(data.payload, request.uuid); + processRequestDTO = this.process>(data.payload, request); } let objectList = processRequestDTO; diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 55186e5382..17aab032e3 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -148,8 +148,7 @@ export class RequestService { */ configure(request: RestRequest): void { const isGetRequest = request.method === RestRequestMethod.GET; - const isSubmission = request instanceof SubmissionRequest; - if (!isGetRequest || !this.isCachedOrPending(request) || isSubmission) { + if (!isGetRequest || !this.isCachedOrPending(request)) { this.dispatchRequest(request); if (isGetRequest) { this.trackRequestsOnTheirWayToTheStore(request); diff --git a/src/app/core/eperson/eperson-response-parsing.service.ts b/src/app/core/eperson/eperson-response-parsing.service.ts index 481f37d1fa..76222323ae 100644 --- a/src/app/core/eperson/eperson-response-parsing.service.ts +++ b/src/app/core/eperson/eperson-response-parsing.service.ts @@ -28,7 +28,7 @@ export class EpersonResponseParsingService extends BaseResponseParsingService im parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links)) { - const epersonDefinition = this.process(data.payload, request.href); + const epersonDefinition = this.process(data.payload, request); return new EpersonSuccessResponse(epersonDefinition[Object.keys(epersonDefinition)[0]], data.statusCode, data.statusText, this.processPageInfo(data.payload)); } else { return new ErrorResponse( diff --git a/src/app/core/integration/integration-response-parsing.service.ts b/src/app/core/integration/integration-response-parsing.service.ts index 8cc0f8d252..0609cd804e 100644 --- a/src/app/core/integration/integration-response-parsing.service.ts +++ b/src/app/core/integration/integration-response-parsing.service.ts @@ -27,7 +27,7 @@ export class IntegrationResponseParsingService extends BaseResponseParsingServic parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links)) { - const dataDefinition = this.process(data.payload, request.uuid); + const dataDefinition = this.process(data.payload, request); return new IntegrationSuccessResponse(this.processResponse(dataDefinition), data.statusCode, data.statusText, this.processPageInfo(data.payload)); } else { return new ErrorResponse( diff --git a/src/app/core/submission/submission-response-parsing.service.ts b/src/app/core/submission/submission-response-parsing.service.ts index de7d683d91..da3b578fcd 100644 --- a/src/app/core/submission/submission-response-parsing.service.ts +++ b/src/app/core/submission/submission-response-parsing.service.ts @@ -91,7 +91,7 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && this.isSuccessStatus(data.statusCode)) { - const dataDefinition = this.processResponse(data.payload, request.href); + const dataDefinition = this.processResponse(data.payload, request); return new SubmissionSuccessResponse(dataDefinition, data.statusCode, data.statusText, this.processPageInfo(data.payload)); } else if (isEmpty(data.payload) && this.isSuccessStatus(data.statusCode)) { return new SubmissionSuccessResponse(null, data.statusCode, data.statusText); @@ -109,11 +109,11 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService * Parses response and normalize it * * @param {DSpaceRESTV2Response} data - * @param {string} requestHref + * @param {RestRequest} request * @returns {any[]} */ - protected processResponse(data: any, requestHref: string): any[] { - const dataDefinition = this.process(data, requestHref); + protected processResponse(data: any, request: RestRequest): any[] { + const dataDefinition = this.process(data, request); const normalizedDefinition = Array.of(); const processedList = Array.isArray(dataDefinition) ? dataDefinition : Array.of(dataDefinition); From 040692455ead6433d1ec591fa80b82568399d12f Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Mon, 7 Oct 2019 15:35:19 +0200 Subject: [PATCH 112/115] increased responseMsToLive from 0 to 10s, to prevent infinite loops --- src/app/core/auth/auth-request.service.ts | 4 ++-- src/app/core/data/data.service.ts | 2 +- src/app/core/data/request.models.ts | 4 ++-- .../submission-rest.service.spec.ts | 2 +- .../submission/submission-rest.service.ts | 2 +- .../submission/workflowitem-data.service.ts | 2 +- .../submission/workspaceitem-data.service.ts | 2 +- .../core/tasks/claimed-task-data.service.ts | 15 +-------------- src/app/core/tasks/pool-task-data.service.ts | 19 +------------------ 9 files changed, 11 insertions(+), 41 deletions(-) diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index e717de9df9..8773b1a9fb 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -45,7 +45,7 @@ export class AuthRequestService { distinctUntilChanged(), map((endpointURL: string) => new AuthPostRequest(this.requestService.generateRequestId(), endpointURL, body, options)), map ((request: PostRequest) => { - request.responseMsToLive = 0; + request.responseMsToLive = 10 * 1000; return request; }), tap((request: PostRequest) => this.requestService.configure(request)), @@ -60,7 +60,7 @@ export class AuthRequestService { distinctUntilChanged(), map((endpointURL: string) => new AuthGetRequest(this.requestService.generateRequestId(), endpointURL, options)), map ((request: GetRequest) => { - request.responseMsToLive = 0; + request.responseMsToLive = 10 * 1000; return request; }), tap((request: GetRequest) => this.requestService.configure(request)), diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 8362358d61..34215f1a2f 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -205,7 +205,7 @@ export abstract class DataService { first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); - request.responseMsToLive = 0; + request.responseMsToLive = 10 * 1000; this.requestService.configure(request); }); diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 43ab9e58e7..766ad75cde 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -23,7 +23,7 @@ import { MappedCollectionsReponseParsingService } from './mapped-collections-rep /* tslint:disable:max-classes-per-file */ export abstract class RestRequest { - public responseMsToLive = 0; + public responseMsToLive = 10 * 1000; constructor( public uuid: string, public href: string, @@ -404,7 +404,7 @@ export class TaskDeleteRequest extends DeleteRequest { } export class MyDSpaceRequest extends GetRequest { - public responseMsToLive = 0; + public responseMsToLive = 10 * 1000; } export class RequestError extends Error { diff --git a/src/app/core/submission/submission-rest.service.spec.ts b/src/app/core/submission/submission-rest.service.spec.ts index 8e47bbd994..e6cf3cb6b0 100644 --- a/src/app/core/submission/submission-rest.service.spec.ts +++ b/src/app/core/submission/submission-rest.service.spec.ts @@ -60,7 +60,7 @@ describe('SubmissionRestService test suite', () => { it('should configure a new SubmissionRequest', () => { const expected = new SubmissionRequest(requestService.generateRequestId(), resourceHref); // set cache time to zero - expected.responseMsToLive = 0; + expected.responseMsToLive = 10 * 1000; scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe()); scheduler.flush(); diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index 58aa507314..42bdfbc22d 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -110,7 +110,7 @@ export class SubmissionRestService { distinctUntilChanged(), map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)), map ((request: RestRequest) => { - request.responseMsToLive = 0; + request.responseMsToLive = 10 * 1000; return request; }), tap((request: RestRequest) => this.requestService.configure(request)), diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index 218de9c81a..43c4aecafe 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -20,7 +20,7 @@ import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; @Injectable() export class WorkflowItemDataService extends DataService { protected linkPath = 'workflowitems'; - protected responseMsToLive = 0; + protected responseMsToLive = 10 * 1000; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 70e07edd5e..4d388ec513 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -20,7 +20,7 @@ import { WorkspaceItem } from './models/workspaceitem.model'; @Injectable() export class WorkspaceitemDataService extends DataService { protected linkPath = 'workspaceitems'; - protected responseMsToLive = 0; + protected responseMsToLive = 10 * 1000; constructor( protected comparator: DSOChangeAnalyzer, diff --git a/src/app/core/tasks/claimed-task-data.service.ts b/src/app/core/tasks/claimed-task-data.service.ts index 6341c2713a..cc6a9d2d20 100644 --- a/src/app/core/tasks/claimed-task-data.service.ts +++ b/src/app/core/tasks/claimed-task-data.service.ts @@ -21,20 +21,7 @@ import { ProcessTaskResponse } from './models/process-task-response'; */ @Injectable() export class ClaimedTaskDataService extends TasksService { - -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - protected responseMsToLive = 0; -======= - protected resetMsToLive = true; ->>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. -======= - protected resetMsToLive = 0; ->>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. -======= - protected responseMsToLive = 0; ->>>>>>> Renamed cache property and replaced conditional with hasValue. + protected responseMsToLive = 10 * 1000; /** * The endpoint link name diff --git a/src/app/core/tasks/pool-task-data.service.ts b/src/app/core/tasks/pool-task-data.service.ts index f763abb1cc..379f2289ad 100644 --- a/src/app/core/tasks/pool-task-data.service.ts +++ b/src/app/core/tasks/pool-task-data.service.ts @@ -26,25 +26,8 @@ export class PoolTaskDataService extends TasksService { * The endpoint link name */ protected linkPath = 'pooltasks'; + protected responseMsToLive = 10 * 1000; -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - protected responseMsToLive = 0; - -======= ->>>>>>> Refactored to remove forceBypassCache param from requestService and from data service classes. -======= - protected resetMsToLive = true; -======= - protected resetMsToLive = 0; ->>>>>>> Sets responseMsToLive to zero in some dataService methods and adds ability to reset the responseMsToLive value in subclasses. -======= - protected responseMsToLive = 0; ->>>>>>> Renamed cache property and replaced conditional with hasValue. - ->>>>>>> Setting cache period to zero for all instances where forceBypassCache was previously true. /** * Initialize instance variables * From 4b03f76db2e777add88d621c9040c3e3f850c600 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 10 Oct 2019 15:53:52 -0700 Subject: [PATCH 113/115] Setting responseMsToLive to zero for SubmissionRequest. --- src/app/core/submission/submission-rest.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index 42bdfbc22d..58aa507314 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -110,7 +110,7 @@ export class SubmissionRestService { distinctUntilChanged(), map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)), map ((request: RestRequest) => { - request.responseMsToLive = 10 * 1000; + request.responseMsToLive = 0; return request; }), tap((request: RestRequest) => this.requestService.configure(request)), From ef4ae44898bcc5fa900031ed6454cfef12abef54 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 10 Oct 2019 16:25:00 -0700 Subject: [PATCH 114/115] Added forceBypassCache property to request model. --- src/app/core/data/request.models.ts | 2 ++ src/app/core/data/request.service.ts | 2 +- src/app/core/submission/submission-rest.service.spec.ts | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 766ad75cde..d7dfa16024 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -24,6 +24,7 @@ import { MappedCollectionsReponseParsingService } from './mapped-collections-rep export abstract class RestRequest { public responseMsToLive = 10 * 1000; + public forceBypassCache = false; constructor( public uuid: string, public href: string, @@ -293,6 +294,7 @@ export class UpdateMetadataFieldRequest extends PutRequest { * Class representing a submission HTTP GET request object */ export class SubmissionRequest extends GetRequest { + forceBypassCache = true; constructor(uuid: string, href: string) { super(uuid, href); } diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 17aab032e3..bd65163892 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -148,7 +148,7 @@ export class RequestService { */ configure(request: RestRequest): void { const isGetRequest = request.method === RestRequestMethod.GET; - if (!isGetRequest || !this.isCachedOrPending(request)) { + if (!isGetRequest || request.forceBypassCache || !this.isCachedOrPending(request)) { this.dispatchRequest(request); if (isGetRequest) { this.trackRequestsOnTheirWayToTheStore(request); diff --git a/src/app/core/submission/submission-rest.service.spec.ts b/src/app/core/submission/submission-rest.service.spec.ts index e6cf3cb6b0..30fe9f9163 100644 --- a/src/app/core/submission/submission-rest.service.spec.ts +++ b/src/app/core/submission/submission-rest.service.spec.ts @@ -60,7 +60,8 @@ describe('SubmissionRestService test suite', () => { it('should configure a new SubmissionRequest', () => { const expected = new SubmissionRequest(requestService.generateRequestId(), resourceHref); // set cache time to zero - expected.responseMsToLive = 10 * 1000; + expected.responseMsToLive = 0; + expected.forceBypassCache = true; scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe()); scheduler.flush(); From e38556e8214fd3e3755ae4e079426b1dbdd837db Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 16 Oct 2019 23:42:36 -0700 Subject: [PATCH 115/115] Updated unit test. --- src/app/core/data/collection-data.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/data/collection-data.service.spec.ts b/src/app/core/data/collection-data.service.spec.ts index 5cb7fed5e4..a3e1a916e3 100644 --- a/src/app/core/data/collection-data.service.spec.ts +++ b/src/app/core/data/collection-data.service.spec.ts @@ -37,7 +37,7 @@ describe('CollectionDataService', () => { }); it('should configure a GET request', () => { - expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest), undefined); + expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest)); }); });