From ec96962a87a5ff3d530d4aa435b50ca4aa3d325f Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 8 Oct 2018 17:50:52 +0200 Subject: [PATCH 001/111] 55946: Start of Edit Item Page Conflicts: resources/i18n/en.json --- resources/i18n/en.json | 61 +++++++++++++++++++ .../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, 181 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 b6a23068d7..deebe09760 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -43,6 +43,67 @@ "simple": "Simple item page", "full": "Full item page" } + }, + "select": { + "table": { + "collection": "Collection", + "author": "Author", + "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 05a4918ef0a15ba27c5a80c87767b88f7ed3352f Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 9 Oct 2018 11:51:52 +0200 Subject: [PATCH 002/111] 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 deebe09760..5cd54869d2 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -85,7 +85,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 fb0e1d81e4f461c8eac754831877d2494cb74ac7 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 9 Oct 2018 14:30:40 +0200 Subject: [PATCH 003/111] 55946: Edit item page cleanup --- .../edit-item-page/edit-item-page.module.ts | 4 +--- .../edit-item-page.routing.module.ts | 8 -------- .../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.ts | 8 +++++++- 7 files changed, 8 insertions(+), 38 deletions(-) delete mode 100644 src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html delete mode 100644 src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.scss delete mode 100644 src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts delete mode 100644 src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts 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..e7016eb05d 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 @@ -3,7 +3,6 @@ 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({ @@ -14,8 +13,7 @@ import { ItemStatusComponent } from './item-status/item-status.component'; ], declarations: [ EditItemPageComponent, - ItemStatusComponent, - ItemCollectionMapperComponent + ItemStatusComponent ] }) export class EditItemPageModule { diff --git a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts index f2209cddcc..46e8dab609 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 @@ -2,7 +2,6 @@ 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: [ @@ -13,13 +12,6 @@ import { ItemCollectionMapperComponent } from './item-collection-mapper/item-col resolve: { item: ItemPageResolver } - }, - { - path: 'map', - 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 deleted file mode 100644 index 3fb829fe8b..0000000000 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
-
-

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 deleted file mode 100644 index e69de29bb2..0000000000 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 deleted file mode 100644 index e69de29bb2..0000000000 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 deleted file mode 100644 index 592e3bd26c..0000000000 --- a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts +++ /dev/null @@ -1,19 +0,0 @@ -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.ts b/src/app/+item-page/edit-item-page/item-status/item-status.component.ts index 715614c1d9..8d68a9e961 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 @@ -53,8 +53,14 @@ export class ItemStatusComponent implements OnInit { }); this.statusDataKeys = Object.keys(this.statusData); + /* + The key is used to build messages + i18n example: 'item.edit.tabs.status.buttons..label' + The value is supposed to be a href for the button + */ this.actions = Object.assign({ - mappedCollections: this.getCurrentUrl() + '/map' + // TODO: Create mapping component on item level + mappedCollections: this.getCurrentUrl() + '/' }); this.actionsKeys = Object.keys(this.actions); } From 7d9afeefea78ee1cbe60a2ba74a687cf6014658f Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 9 Oct 2018 15:32:55 +0200 Subject: [PATCH 004/111] 55946: Removed unnecessary files and created tests --- .../edit-item-page.component.scss | 0 .../edit-item-page.component.spec.ts | 0 .../edit-item-page.component.ts | 1 - .../item-status/item-status.component.html | 12 ++-- .../item-status/item-status.component.scss | 0 .../item-status/item-status.component.spec.ts | 67 +++++++++++++++++++ .../item-status/item-status.component.ts | 1 - 7 files changed, 73 insertions(+), 8 deletions(-) delete mode 100644 src/app/+item-page/edit-item-page/edit-item-page.component.scss delete mode 100644 src/app/+item-page/edit-item-page/edit-item-page.component.spec.ts delete mode 100644 src/app/+item-page/edit-item-page/item-status/item-status.component.scss 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 deleted file mode 100644 index e69de29bb2..0000000000 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 deleted file mode 100644 index e69de29bb2..0000000000 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 7702fc94e8..8bcf53f140 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 @@ -7,7 +7,6 @@ import { Item } from '../../core/shared/item.model'; @Component({ selector: 'ds-edit-item-page', - styleUrls: ['./edit-item-page.component.scss'], templateUrl: './edit-item-page.component.html', changeDetection: ChangeDetectionStrategy.OnPush, animations: [ 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 index 0a93e7659d..78ab9174eb 100644 --- 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 @@ -1,27 +1,27 @@

{{'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}}
-
+
{{'item.edit.tabs.status.buttons.' + actionKey + '.button' | 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 deleted file mode 100644 index e69de29bb2..0000000000 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 index e69de29bb2..2df4b977cb 100644 --- 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 @@ -0,0 +1,67 @@ +import { ItemStatusComponent } from './item-status.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { CommonModule } from '@angular/common'; +import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub'; +import { HostWindowService } from '../../../shared/host-window.service'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Router } from '@angular/router'; +import { RouterStub } from '../../../shared/testing/router-stub'; +import { Item } from '../../../core/shared/item.model'; +import { By } from '@angular/platform-browser'; + +describe('ItemStatusComponent', () => { + let comp: ItemStatusComponent; + let fixture: ComponentFixture; + + const mockItem = Object.assign(new Item(), { + id: 'fake-id', + handle: 'fake/handle', + lastModified: '2018' + }); + + const itemPageUrl = `fake-url/${mockItem.id}`; + const routerStub = Object.assign(new RouterStub(), { + url: `${itemPageUrl}/edit` + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [ItemStatusComponent], + providers: [ + { provide: Router, useValue: routerStub }, + { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemStatusComponent); + comp = fixture.componentInstance; + comp.item = mockItem; + fixture.detectChanges(); + }); + + it('should display the item\'s internal id', () => { + const statusId: HTMLElement = fixture.debugElement.query(By.css('.status-data#status-id')).nativeElement; + expect(statusId.textContent).toContain(mockItem.id); + }); + + it('should display the item\'s handle', () => { + const statusHandle: HTMLElement = fixture.debugElement.query(By.css('.status-data#status-handle')).nativeElement; + expect(statusHandle.textContent).toContain(mockItem.handle); + }); + + it('should display the item\'s last modified date', () => { + const statusLastModified: HTMLElement = fixture.debugElement.query(By.css('.status-data#status-lastModified')).nativeElement; + expect(statusLastModified.textContent).toContain(mockItem.lastModified); + }); + + it('should display the item\'s page url', () => { + const statusItemPage: HTMLElement = fixture.debugElement.query(By.css('.status-data#status-itemPage')).nativeElement; + expect(statusItemPage.textContent).toContain(itemPageUrl); + }); + +}); 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 8d68a9e961..bc9dda61ec 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 @@ -5,7 +5,6 @@ import { Router } from '@angular/router'; @Component({ selector: 'ds-item-status', - styleUrls: ['./item-status.component.scss'], templateUrl: './item-status.component.html', changeDetection: ChangeDetectionStrategy.OnPush, animations: [ From 8570ff25784f2ccf93913f6e7a4954621de50e8c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 25 Oct 2018 18:13:49 +0200 Subject: [PATCH 005/111] 55990: Item move component --- .../item-move/item-move.component.html | 51 +++++++++++++++++++ .../item-move/item-move.component.ts | 0 .../item-operation.component.html | 10 ++++ .../item-operation.component.ts | 0 .../item-operation/itemOperation.model.ts | 0 5 files changed, 61 insertions(+) create mode 100644 src/app/+item-page/edit-item-page/item-move/item-move.component.html create mode 100644 src/app/+item-page/edit-item-page/item-move/item-move.component.ts create mode 100644 src/app/+item-page/edit-item-page/item-operation/item-operation.component.html create mode 100644 src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts create mode 100644 src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.html b/src/app/+item-page/edit-item-page/item-move/item-move.component.html new file mode 100644 index 0000000000..a060aa0fed --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.html @@ -0,0 +1,51 @@ +import {Component, OnInit} from '@angular/core'; +import {Collection} from '../../../core/shared/collection.model'; +import {RemoteData} from '../../../core/data/remote-data'; +import {Item} from '../../../core/shared/item.model'; +import {getSucceededRemoteData} from '../../../core/shared/operators'; +import {Observable} from 'rxjs'; +import {PaginatedList} from '../../../core/data/paginated-list'; +import {TranslateService} from '@ngx-translate/core'; +import {NotificationsService} from '../../../shared/notifications/notifications.service'; +import {SearchService} from '../../../+search-page/search-service/search.service'; +import {SearchConfigurationService} from '../../../+search-page/search-service/search-configuration.service'; +import {ActivatedRoute, Router} from '@angular/router'; +import {CollectionDataService} from '../../../core/data/collection-data.service'; + +@Component({ + selector: 'ds-item-move', + templateUrl: './item-move.component.html' +}) +export class ItemMoveComponent implements OnInit { + inheritPolicies: boolean; + + itemRD$: Observable>; + + /** + * List of collections to show under the "Browse" tab + * Collections that are mapped to the item + */ + itemCollectionsRD$: Observable>>; + + constructor(private route: ActivatedRoute, + private router: Router, + private searchConfigService: SearchConfigurationService, + private searchService: SearchService, + private notificationsService: NotificationsService, + private collectionDataService: CollectionDataService, + private translateService: TranslateService) { + } + + ngOnInit(): void { + this.itemRD$ = this.route.data.map((data) => data.item).pipe(getSucceededRemoteData()) as Observable>; + this.loadCollectionLists(); + } + + /** + * Load all available collections to move the item to. + * TODO: When the API support it, only fetch collections where user has ADD rights to. + */ + loadCollectionLists() { + this.itemCollectionsRD$ = this.collectionDataService.findAll(); + } +} diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html new file mode 100644 index 0000000000..59b625d8c0 --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html @@ -0,0 +1,10 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'ds-item-operation', + templateUrl: './ds-item-operation.html' +}) + +export class ItemOperationComponent { + +} diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts b/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts new file mode 100644 index 0000000000..e69de29bb2 From d26bba8e14847294d2453f575aab1aba0eb54c61 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 25 Oct 2018 18:14:22 +0200 Subject: [PATCH 006/111] 5590: Item move component --- resources/i18n/en.json | 10 ++ .../edit-item-page/edit-item-page.module.ts | 8 +- .../edit-item-page.routing.module.ts | 24 +++- .../item-move/item-move.component.html | 90 +++++++------- .../item-move/item-move.component.ts | 111 ++++++++++++++++++ .../item-operation.component.html | 25 ++-- .../item-operation.component.ts | 13 ++ .../item-operation/itemOperation.model.ts | 12 ++ .../item-status/item-status.component.html | 13 +- .../item-status/item-status.component.ts | 12 +- .../+item-page/item-page-routing.module.ts | 25 ++-- src/app/+search-page/search-page.module.ts | 80 +++++++------ src/app/app-routing.module.ts | 6 +- src/app/core/data/item-data.service.ts | 53 ++++++--- .../input-suggestions.component.ts | 9 ++ 15 files changed, 351 insertions(+), 140 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 5cd54869d2..2d20f21d17 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -103,6 +103,16 @@ "curate": { "head": "Curate" } + }, + "move": { + "head":"Move item: {{id}}", + "description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.", + "inheritpolicies": { + "description": "Inherit the default policies of the destination collection", + "checkbox": "Inherit policies" + }, + "move": "Move", + "cancel": "Cancel" } } }, 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 e7016eb05d..09a5e1d588 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 @@ -4,15 +4,21 @@ import { SharedModule } from '../../shared/shared.module'; import { EditItemPageRoutingModule } from './edit-item-page.routing.module'; import { EditItemPageComponent } from './edit-item-page.component'; import { ItemStatusComponent } from './item-status/item-status.component'; +import {ItemOperationComponent} from './item-operation/item-operation.component'; +import {ItemMoveComponent} from './item-move/item-move.component'; +import {SearchPageModule} from '../../+search-page/search-page.module'; @NgModule({ imports: [ CommonModule, SharedModule, - EditItemPageRoutingModule + EditItemPageRoutingModule, + SearchPageModule.forRoot(), ], declarations: [ EditItemPageComponent, + ItemOperationComponent, + ItemMoveComponent, ItemStatusComponent ] }) 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 46e8dab609..e9b0643cc1 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 @@ -1,7 +1,16 @@ -import { ItemPageResolver } from '../item-page.resolver'; -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; -import { EditItemPageComponent } from './edit-item-page.component'; +import {ItemPageResolver} from '../item-page.resolver'; +import {NgModule} from '@angular/core'; +import {RouterModule} from '@angular/router'; +import {EditItemPageComponent} from './edit-item-page.component'; +import {ItemMoveComponent} from './item-move/item-move.component'; +import {URLCombiner} from '../../core/url-combiner/url-combiner'; +import {getItemEditPath} from '../item-page-routing.module'; + +const ITEM_EDIT_MOVE_PATH = 'move'; + +export function getItemEditMovePath(id: string) { + return new URLCombiner(getItemEditPath(id), ITEM_EDIT_MOVE_PATH); +} @NgModule({ imports: [ @@ -12,6 +21,13 @@ import { EditItemPageComponent } from './edit-item-page.component'; resolve: { item: ItemPageResolver } + }, + { + path: ITEM_EDIT_MOVE_PATH, + component: ItemMoveComponent, + resolve: { + item: ItemPageResolver + } } ]) ], diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.html b/src/app/+item-page/edit-item-page/item-move/item-move.component.html index a060aa0fed..fe27ed36a5 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.html +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.html @@ -1,51 +1,41 @@ -import {Component, OnInit} from '@angular/core'; -import {Collection} from '../../../core/shared/collection.model'; -import {RemoteData} from '../../../core/data/remote-data'; -import {Item} from '../../../core/shared/item.model'; -import {getSucceededRemoteData} from '../../../core/shared/operators'; -import {Observable} from 'rxjs'; -import {PaginatedList} from '../../../core/data/paginated-list'; -import {TranslateService} from '@ngx-translate/core'; -import {NotificationsService} from '../../../shared/notifications/notifications.service'; -import {SearchService} from '../../../+search-page/search-service/search.service'; -import {SearchConfigurationService} from '../../../+search-page/search-service/search-configuration.service'; -import {ActivatedRoute, Router} from '@angular/router'; -import {CollectionDataService} from '../../../core/data/collection-data.service'; +
+
+
+

{{'item.edit.move.head' | translate: { id: (itemRD$ | async)?.payload?.id} }}

+

{{'item.edit.move.description' | translate}}

+
+
+ + +
+
+
+
+

+ + +

+

+ {{'item.edit.move.inheritpolicies.description' | translate}} +

+
+
-@Component({ - selector: 'ds-item-move', - templateUrl: './item-move.component.html' -}) -export class ItemMoveComponent implements OnInit { - inheritPolicies: boolean; - - itemRD$: Observable>; - - /** - * List of collections to show under the "Browse" tab - * Collections that are mapped to the item - */ - itemCollectionsRD$: Observable>>; - - constructor(private route: ActivatedRoute, - private router: Router, - private searchConfigService: SearchConfigurationService, - private searchService: SearchService, - private notificationsService: NotificationsService, - private collectionDataService: CollectionDataService, - private translateService: TranslateService) { - } - - ngOnInit(): void { - this.itemRD$ = this.route.data.map((data) => data.item).pipe(getSucceededRemoteData()) as Observable>; - this.loadCollectionLists(); - } - - /** - * Load all available collections to move the item to. - * TODO: When the API support it, only fetch collections where user has ADD rights to. - */ - loadCollectionLists() { - this.itemCollectionsRD$ = this.collectionDataService.findAll(); - } -} + + +
+
+
diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts index e69de29bb2..e0819257c2 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts @@ -0,0 +1,111 @@ +import {Component, OnInit} from '@angular/core'; +import {SearchService} from '../../../+search-page/search-service/search.service'; +import {Observable} from 'rxjs/Observable'; +import {map} from 'rxjs/operators'; +import {DSpaceObjectType} from '../../../core/shared/dspace-object-type.model'; +import {SearchOptions} from '../../../+search-page/search-options.model'; +import {RemoteData} from '../../../core/data/remote-data'; +import {DSpaceObject} from '../../../core/shared/dspace-object.model'; +import {PaginatedList} from '../../../core/data/paginated-list'; +import {SearchResult} from '../../../+search-page/search-result.model'; +import {PaginatedSearchOptions} from '../../../+search-page/paginated-search-options.model'; +import {Item} from '../../../core/shared/item.model'; +import {ActivatedRoute, Router} from '@angular/router'; +import {NotificationsService} from '../../../shared/notifications/notifications.service'; +import {CollectionDataService} from '../../../core/data/collection-data.service'; +import {SearchConfigurationService} from '../../../+search-page/search-service/search-configuration.service'; +import {TranslateService} from '@ngx-translate/core'; +import {getSucceededRemoteData} from '../../../core/shared/operators'; +import {ItemDataService} from '../../../core/data/item-data.service'; +import {RestResponse} from '../../../core/cache/response-cache.models'; +import {getItemEditPath} from '../../item-page-routing.module'; + +@Component({ + selector: 'ds-item-move', + templateUrl: './item-move.component.html' +}) +export class ItemMoveComponent implements OnInit { + + inheritPolicies = false; + itemRD$: Observable>; + /** + * Search options + */ + searchOptions$: Observable; + filterSearchResults: Observable = Observable.of([]); + selectedCollection: string; + + selectedCollectionId: string; + itemId: string; + + constructor(private route: ActivatedRoute, + private router: Router, + private notificationsService: NotificationsService, + private collectionDataService: CollectionDataService, + private itemDataService: ItemDataService, + private searchConfigService: SearchConfigurationService, + private searchService: SearchService, + private translateService: TranslateService) { + } + + ngOnInit(): void { + this.itemRD$ = this.route.data.map((data) => data.item).pipe(getSucceededRemoteData()) as Observable>; + this.itemRD$.first().subscribe((rd) => { + this.itemId = rd.payload.id; + } + ); + this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; + this.loadSuggestions(''); + } + + findSuggestions(query): void { + this.loadSuggestions(query); + } + + /** + * Load all available collections to move the item to. + * TODO: When the API support it, only fetch collections where user has ADD rights to. + */ + loadSuggestions(query): void { + this.filterSearchResults = this.searchService.search(new SearchOptions({ + dsoType: DSpaceObjectType.COLLECTION, + query: query + })).first().pipe( + map((rd: RemoteData>>) => { + return rd.payload.page.map((searchResult) => { + return { + displayValue: searchResult.dspaceObject.name, + value: {name: searchResult.dspaceObject.name, id: searchResult.dspaceObject.uuid} + }; + }); + }) + ); + + } + + onClick(data: any): void { + this.selectedCollection = data.name; + this.selectedCollectionId = data.id; + } + + /** + * @returns {string} the current URL + */ + getCurrentUrl() { + return this.router.url; + } + + moveCollection() { + this.itemDataService.moveToCollection(this.itemId, this.selectedCollectionId).first().subscribe( + (response: RestResponse) => { + this.router.navigate([getItemEditPath(this.itemId)]); + if (response.isSuccessful) { + this.notificationsService.success(this.translateService.get('item.move.success')); + } else { + this.notificationsService.error(this.translateService.get('item.move.error')); + } + } + ); + + } +} diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html index 59b625d8c0..4623195437 100644 --- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html +++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html @@ -1,10 +1,15 @@ -import {Component} from '@angular/core'; - -@Component({ - selector: 'ds-item-operation', - templateUrl: './ds-item-operation.html' -}) - -export class ItemOperationComponent { - -} +
+ + {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.label' | translate}} + +
+ +
+ + {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}} + +
\ No newline at end of file diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts index e69de29bb2..951d66cbd8 100644 --- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts +++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts @@ -0,0 +1,13 @@ +import {Component, Input} from '@angular/core'; +import {ItemOperation} from './itemOperation.model'; + +@Component({ + selector: 'ds-item-operation', + templateUrl: './item-operation.component.html' +}) + +export class ItemOperationComponent { + + @Input() operation: ItemOperation; + +} diff --git a/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts b/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts index e69de29bb2..6a54744fcb 100644 --- a/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts +++ b/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts @@ -0,0 +1,12 @@ +export class ItemOperation { + + operationKey: string; + operationUrl: string; + disabled: boolean; + + constructor(operationKey: string, operationUrl: string) { + this.operationKey = operationKey; + this.operationUrl = operationUrl; + } + +} 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 index 78ab9174eb..0f7d9a5607 100644 --- 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 @@ -15,16 +15,7 @@ {{getItemPage()}}
-
-
- - {{'item.edit.tabs.status.buttons.' + actionKey + '.label' | translate}} - -
- +
+
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 bc9dda61ec..e92ae10b55 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 @@ -2,6 +2,7 @@ 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'; +import {ItemOperation} from '../item-operation/itemOperation.model'; @Component({ selector: 'ds-item-status', @@ -35,7 +36,7 @@ export class ItemStatusComponent implements OnInit { * The possible actions that can be performed on the item * key: id value: url to action's component */ - actions: any; + operations: ItemOperation[]; /** * The keys of the actions (to loop over) */ @@ -57,11 +58,10 @@ export class ItemStatusComponent implements OnInit { i18n example: 'item.edit.tabs.status.buttons..label' 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() + '/' - }); - this.actionsKeys = Object.keys(this.actions); + this.operations = [ + new ItemOperation('mappedCollections',this.getCurrentUrl() + '/'), + new ItemOperation('move', this.getCurrentUrl() + '/move'), + ] } /** diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index be31b0a82d..a155d00cc0 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -1,10 +1,21 @@ -import { NgModule } from '@angular/core'; -import { RouterModule } from '@angular/router'; +import {NgModule} from '@angular/core'; +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 {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 {URLCombiner} from '../core/url-combiner/url-combiner'; +import {getItemModulePath} from '../app-routing.module'; + +export function getItemPageRoute(itemId: string) { + return new URLCombiner(getItemModulePath(), itemId).toString(); +} +export function getItemEditPath(id: string) { + return new URLCombiner(getItemModulePath(),ITEM_EDIT_PATH.replace(/:id/, id)).toString() +} + +const ITEM_EDIT_PATH = ':id/edit'; @NgModule({ imports: [ @@ -25,7 +36,7 @@ import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; } }, { - path: ':id/edit', + path: ITEM_EDIT_PATH, loadChildren: './edit-item-page/edit-item-page.module#EditItemPageModule', canActivate: [AuthenticatedGuard] } diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 0c8a4ee306..a231d8da5a 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -1,38 +1,45 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { CoreModule } from '../core/core.module'; -import { SharedModule } from '../shared/shared.module'; -import { SearchPageRoutingModule } from './search-page-routing.module'; -import { SearchPageComponent } from './search-page.component'; -import { SearchResultsComponent } from './search-results/search-results.component'; -import { ItemSearchResultListElementComponent } from '../shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component'; -import { CollectionSearchResultListElementComponent } from '../shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component'; -import { CommunitySearchResultListElementComponent } from '../shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component'; -import { ItemSearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component'; -import { CommunitySearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component' -import { CollectionSearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component'; -import { SearchService } from './search-service/search.service'; -import { SearchSidebarComponent } from './search-sidebar/search-sidebar.component'; -import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; -import { SearchSidebarEffects } from './search-sidebar/search-sidebar.effects'; -import { SearchSettingsComponent } from './search-settings/search-settings.component'; -import { EffectsModule } from '@ngrx/effects'; -import { SearchFiltersComponent } from './search-filters/search-filters.component'; -import { SearchFilterComponent } from './search-filters/search-filter/search-filter.component'; -import { SearchFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component'; -import { SearchFilterService } from './search-filters/search-filter/search-filter.service'; -import { SearchLabelsComponent } from './search-labels/search-labels.component'; -import { SearchRangeFilterComponent } from './search-filters/search-filter/search-range-filter/search-range-filter.component'; -import { SearchTextFilterComponent } from './search-filters/search-filter/search-text-filter/search-text-filter.component'; -import { SearchFacetFilterWrapperComponent } from './search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component'; -import { SearchBooleanFilterComponent } from './search-filters/search-filter/search-boolean-filter/search-boolean-filter.component'; -import { SearchHierarchyFilterComponent } from './search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component'; -import { SearchConfigurationService } from './search-service/search-configuration.service'; +import {ModuleWithProviders, NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {CoreModule} from '../core/core.module'; +import {SharedModule} from '../shared/shared.module'; +import {SearchPageRoutingModule} from './search-page-routing.module'; +import {SearchPageComponent} from './search-page.component'; +import {SearchResultsComponent} from './search-results/search-results.component'; +import {ItemSearchResultListElementComponent} from '../shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component'; +import {CollectionSearchResultListElementComponent} from '../shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component'; +import {CommunitySearchResultListElementComponent} from '../shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component'; +import {ItemSearchResultGridElementComponent} from '../shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component'; +import {CommunitySearchResultGridElementComponent} from '../shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component'; +import {CollectionSearchResultGridElementComponent} from '../shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component'; +import {SearchService} from './search-service/search.service'; +import {SearchSidebarComponent} from './search-sidebar/search-sidebar.component'; +import {SearchSidebarService} from './search-sidebar/search-sidebar.service'; +import {SearchSidebarEffects} from './search-sidebar/search-sidebar.effects'; +import {SearchSettingsComponent} from './search-settings/search-settings.component'; +import {EffectsModule} from '@ngrx/effects'; +import {SearchFiltersComponent} from './search-filters/search-filters.component'; +import {SearchFilterComponent} from './search-filters/search-filter/search-filter.component'; +import {SearchFacetFilterComponent} from './search-filters/search-filter/search-facet-filter/search-facet-filter.component'; +import {SearchFilterService} from './search-filters/search-filter/search-filter.service'; +import {SearchLabelsComponent} from './search-labels/search-labels.component'; +import {SearchRangeFilterComponent} from './search-filters/search-filter/search-range-filter/search-range-filter.component'; +import {SearchTextFilterComponent} from './search-filters/search-filter/search-text-filter/search-text-filter.component'; +import {SearchFacetFilterWrapperComponent} from './search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component'; +import {SearchBooleanFilterComponent} from './search-filters/search-filter/search-boolean-filter/search-boolean-filter.component'; +import {SearchHierarchyFilterComponent} from './search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component'; +import {SearchConfigurationService} from './search-service/search-configuration.service'; const effects = [ SearchSidebarEffects ]; +const PROVIDERS = [ + SearchService, + SearchSidebarService, + SearchFilterService, + SearchConfigurationService +]; + @NgModule({ imports: [ SearchPageRoutingModule, @@ -65,10 +72,7 @@ const effects = [ SearchBooleanFilterComponent, ], providers: [ - SearchService, - SearchSidebarService, - SearchFilterService, - SearchConfigurationService + ...PROVIDERS ], entryComponents: [ ItemSearchResultListElementComponent, @@ -89,4 +93,12 @@ const effects = [ * This module handles all components and pipes that are necessary for the search page */ export class SearchPageModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: CoreModule, + providers: [ + ...PROVIDERS + ] + }; + } } diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 7de83651ff..e7ea10598d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,6 +3,10 @@ import { RouterModule } from '@angular/router'; import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; +const ITEM_MODULE_PATH = 'items'; +export function getItemModulePath() { + return `/${ITEM_MODULE_PATH}`; +} @NgModule({ imports: [ RouterModule.forRoot([ @@ -10,7 +14,7 @@ import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; { path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' }, { path: 'communities', loadChildren: './+community-page/community-page.module#CommunityPageModule' }, { path: 'collections', loadChildren: './+collection-page/collection-page.module#CollectionPageModule' }, - { path: 'items', loadChildren: './+item-page/item-page.module#ItemPageModule' }, + { path: ITEM_MODULE_PATH, loadChildren: './+item-page/item-page.module#ItemPageModule' }, { path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' }, { path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' }, { path: 'admin', loadChildren: './+admin/admin.module#AdminModule' }, diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index f984dceb12..90311a6f82 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -1,21 +1,24 @@ -import { Inject, Injectable } from '@angular/core'; +import {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 { BrowseService } from '../browse/browse.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; -import { NormalizedItem } from '../cache/models/normalized-item.model'; -import { ResponseCacheService } from '../cache/response-cache.service'; -import { CoreState } from '../core.reducers'; -import { Item } from '../shared/item.model'; -import { URLCombiner } from '../url-combiner/url-combiner'; +import {Store} from '@ngrx/store'; +import {Observable} from 'rxjs/Observable'; +import {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'; +import {ResponseCacheService} from '../cache/response-cache.service'; +import {CoreState} from '../core.reducers'; +import {Item} from '../shared/item.model'; +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 {DataService} from './data.service'; +import {RequestService} from './request.service'; +import {HALEndpointService} from '../shared/hal-endpoint.service'; +import {FindAllOptions, PostRequest, RestRequest} from './request.models'; +import {distinctUntilChanged, map} from 'rxjs/operators'; +import {RestResponse} from '../cache/response-cache.models'; +import {configureRequest, getResponseFromSelflink} from '../shared/operators'; +import {ResponseCacheEntry} from '../cache/response-cache.reducer'; @Injectable() export class ItemDataService extends DataService { @@ -48,4 +51,22 @@ export class ItemDataService extends DataService { .distinctUntilChanged(); } + public getMoveItemEndpoint(itemId: string, collectionId?: string): Observable { + return this.halService.getEndpoint(this.linkPath).pipe( + map((endpoint: string) => this.getFindByIDHref(endpoint, itemId)), + map((endpoint: string) => `${endpoint}/owningCollection/move/${collectionId ? `/${collectionId}` : ''}`) + ); + } + + public moveToCollection(itemId: string, collectionId: string): Observable { + return this.getMoveItemEndpoint(itemId, collectionId).pipe( + // isNotEmptyOperator(), + distinctUntilChanged(), + map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)), + configureRequest(this.requestService), + map((request: RestRequest) => request.href), + getResponseFromSelflink(this.responseCache), + map((responseCacheEntry: ResponseCacheEntry) => responseCacheEntry.response) + ); + } } diff --git a/src/app/shared/input-suggestions/input-suggestions.component.ts b/src/app/shared/input-suggestions/input-suggestions.component.ts index eb28583eaa..ae15a805e9 100644 --- a/src/app/shared/input-suggestions/input-suggestions.component.ts +++ b/src/app/shared/input-suggestions/input-suggestions.component.ts @@ -159,6 +159,15 @@ export class InputSuggestionsComponent { this.show.next(false); } + /** + * Changes the show variable so the suggestion dropdown opens + */ + open() { + if (!this.blockReopen) { + this.show.next(true); + } + } + /** * For usage of the isNotEmpty function in the template */ From 4411c312e5861b4e3702e6325a34dfd306f692c7 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 26 Nov 2018 16:28:39 +0100 Subject: [PATCH 007/111] 55990: Add move item component --- .../item-move/item-move.component.html | 2 +- .../item-move/item-move.component.spec.ts | 179 ++++++++++++++++++ .../item-move/item-move.component.ts | 28 +-- .../item-operation.component.spec.ts | 44 +++++ .../item-operation.component.ts | 4 +- .../item-operation/itemOperation.model.ts | 9 + .../item-status/item-status.component.spec.ts | 3 +- .../search-hierarchy-filter.component.html | 2 +- .../search-text-filter.component.html | 2 +- 9 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts create mode 100644 src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.html b/src/app/+item-page/edit-item-page/item-move/item-move.component.html index fe27ed36a5..0c97628d4c 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.html +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.html @@ -6,7 +6,7 @@
; + +const mockItem = Object.assign(new Item(), { + id: 'fake-id', + handle: 'fake/handle', + lastModified: '2018' +}); + +const itemPageUrl = `fake-url/${mockItem.id}`; +const routerStub = Object.assign(new RouterStub(), { + url: `${itemPageUrl}/edit` +}); + +const mockItemDataService = jasmine.createSpyObj({ + moveToCollection: Observable.of(new RestResponse(true, '200')) +}); + +const mockItemDataServiceFail = jasmine.createSpyObj({ + moveToCollection: Observable.of(new RestResponse(false, '500')) +}); + +const routeStub = { + data: Observable.of({ + item: new RemoteData(false, false, true, null, { + id: 'item1' + }) + }) +}; + +const mockSearchService = { + search: () => { + return Observable.of(new RemoteData(false, false, true, null, + new PaginatedList(null, [ + { + dspaceObject: { + name: 'Test collection 1', + uuid: 'collection1' + }, hitHighlights: {} + }, { + dspaceObject: { + name: 'Test collection 2', + uuid: 'collection2' + }, hitHighlights: {} + } + ]))); + } +}; + +const notificationsServiceStub = new NotificationsServiceStub(); + +describe('ItemMoveComponent', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [ItemMoveComponent], + providers: [ + {provide: ActivatedRoute, useValue: routeStub}, + {provide: Router, useValue: routerStub}, + {provide: ItemDataService, useValue: mockItemDataService}, + {provide: NotificationsService, useValue: notificationsServiceStub}, + {provide: SearchService, useValue: mockSearchService}, + ], schemas: [ + CUSTOM_ELEMENTS_SCHEMA + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemMoveComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should load suggestions', () => { + const expected = [ + { + displayValue: 'Test collection 1', + value: { + name: 'Test collection 1', + id: 'collection1', + } + }, + { + displayValue: 'Test collection 2', + value: { + name: 'Test collection 2', + id: 'collection2', + } + } + ]; + + comp.CollectionSearchResults.subscribe((value) => { + expect(value).toEqual(expected); + } + ); + }); + it('should get current url ', () => { + expect(comp.getCurrentUrl()).toEqual('fake-url/fake-id/edit'); + }); + it('should on click select the correct collection name and id', () => { + const data = { + name: 'Test collection 1', + id: 'collection1', + }; + comp.onClick(data); + + expect(comp.selectedCollection).toEqual('Test collection 1'); + expect(comp.selectedCollectionId).toEqual('collection1'); + }); + describe('moveCollection', () => { + it('should call itemDataService.moveToCollection', () => { + comp.itemId = 'item-id'; + comp.selectedCollectionId = 'selected-collection-id'; + comp.moveCollection(); + + expect(mockItemDataService.moveToCollection).toHaveBeenCalledWith('item-id', 'selected-collection-id'); + }); + it('should call notificationsService success message on success', () => { + spyOn(notificationsServiceStub, 'success'); + + comp.moveCollection(); + + expect(notificationsServiceStub.success).toHaveBeenCalled(); + }); + }); +}); + +describe('ItemMoveComponent fail', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], + declarations: [ItemMoveComponent], + providers: [ + {provide: ActivatedRoute, useValue: routeStub}, + {provide: Router, useValue: routerStub}, + {provide: ItemDataService, useValue: mockItemDataServiceFail}, + {provide: NotificationsService, useValue: notificationsServiceStub}, + {provide: SearchService, useValue: mockSearchService}, + ], schemas: [ + CUSTOM_ELEMENTS_SCHEMA + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemMoveComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should call notificationsService error message on fail', () => { + spyOn(notificationsServiceStub, 'error'); + + comp.moveCollection(); + + expect(notificationsServiceStub.error).toHaveBeenCalled(); + }); +}); diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts index e0819257c2..338d4b96a3 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts @@ -8,12 +8,9 @@ import {RemoteData} from '../../../core/data/remote-data'; import {DSpaceObject} from '../../../core/shared/dspace-object.model'; import {PaginatedList} from '../../../core/data/paginated-list'; import {SearchResult} from '../../../+search-page/search-result.model'; -import {PaginatedSearchOptions} from '../../../+search-page/paginated-search-options.model'; import {Item} from '../../../core/shared/item.model'; import {ActivatedRoute, Router} from '@angular/router'; import {NotificationsService} from '../../../shared/notifications/notifications.service'; -import {CollectionDataService} from '../../../core/data/collection-data.service'; -import {SearchConfigurationService} from '../../../+search-page/search-service/search-configuration.service'; import {TranslateService} from '@ngx-translate/core'; import {getSucceededRemoteData} from '../../../core/shared/operators'; import {ItemDataService} from '../../../core/data/item-data.service'; @@ -24,15 +21,14 @@ import {getItemEditPath} from '../../item-page-routing.module'; selector: 'ds-item-move', templateUrl: './item-move.component.html' }) +/** + * Component that handles the moving of an item to a different collection + */ export class ItemMoveComponent implements OnInit { inheritPolicies = false; itemRD$: Observable>; - /** - * Search options - */ - searchOptions$: Observable; - filterSearchResults: Observable = Observable.of([]); + CollectionSearchResults: Observable = Observable.of([]); selectedCollection: string; selectedCollectionId: string; @@ -41,9 +37,7 @@ export class ItemMoveComponent implements OnInit { constructor(private route: ActivatedRoute, private router: Router, private notificationsService: NotificationsService, - private collectionDataService: CollectionDataService, private itemDataService: ItemDataService, - private searchConfigService: SearchConfigurationService, private searchService: SearchService, private translateService: TranslateService) { } @@ -54,10 +48,13 @@ export class ItemMoveComponent implements OnInit { this.itemId = rd.payload.id; } ); - this.searchOptions$ = this.searchConfigService.paginatedSearchOptions; this.loadSuggestions(''); } + /** + * Find suggestions based on entered query + * @param query - Search query + */ findSuggestions(query): void { this.loadSuggestions(query); } @@ -67,7 +64,7 @@ export class ItemMoveComponent implements OnInit { * TODO: When the API support it, only fetch collections where user has ADD rights to. */ loadSuggestions(query): void { - this.filterSearchResults = this.searchService.search(new SearchOptions({ + this.CollectionSearchResults = this.searchService.search(new SearchOptions({ dsoType: DSpaceObjectType.COLLECTION, query: query })).first().pipe( @@ -83,6 +80,10 @@ export class ItemMoveComponent implements OnInit { } + /** + * Set the collection name and id based on the selected value + * @param data - obtained from the ds-input-suggestions component + */ onClick(data: any): void { this.selectedCollection = data.name; this.selectedCollectionId = data.id; @@ -95,6 +96,9 @@ export class ItemMoveComponent implements OnInit { return this.router.url; } + /** + * Moves the item to a new collection based on the selected collection + */ moveCollection() { this.itemDataService.moveToCollection(this.itemId, this.selectedCollectionId).first().subscribe( (response: RestResponse) => { diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts new file mode 100644 index 0000000000..092f3af0ac --- /dev/null +++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts @@ -0,0 +1,44 @@ +import {ItemOperation} from './itemOperation.model'; +import {async, TestBed} from '@angular/core/testing'; +import {ItemOperationComponent} from './item-operation.component'; +import {TranslateModule} from '@ngx-translate/core'; +import {By} from '@angular/platform-browser'; + +describe('ItemOperationComponent', () => { + const itemOperation: ItemOperation = new ItemOperation('key1', 'url1'); + + let fixture; + let comp; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [ItemOperationComponent] + }).compileComponents(); + })); + + beforeEach(() => { + + fixture = TestBed.createComponent(ItemOperationComponent); + comp = fixture.componentInstance; + comp.operation = itemOperation; + fixture.detectChanges(); + }); + + it('should render operation row', () => { + const span = fixture.debugElement.query(By.css('span')).nativeElement; + expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label'); + const link = fixture.debugElement.query(By.css('a')).nativeElement; + expect(link.href).toContain('url1'); + expect(link.textContent).toContain('item.edit.tabs.status.buttons.key1.button'); + }); + it('should render disabled operation row', () => { + itemOperation.setDisabled(true); + fixture.detectChanges(); + + const span = fixture.debugElement.query(By.css('span')).nativeElement; + expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label'); + const span2 = fixture.debugElement.query(By.css('span.btn-danger')).nativeElement; + expect(span2.textContent).toContain('item.edit.tabs.status.buttons.key1.button'); + }); +}); diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts index 951d66cbd8..76d056df95 100644 --- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts +++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.ts @@ -5,7 +5,9 @@ import {ItemOperation} from './itemOperation.model'; selector: 'ds-item-operation', templateUrl: './item-operation.component.html' }) - +/** + * Operation that can be performed on an item + */ export class ItemOperationComponent { @Input() operation: ItemOperation; diff --git a/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts b/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts index 6a54744fcb..0104dfbdb3 100644 --- a/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts +++ b/src/app/+item-page/edit-item-page/item-operation/itemOperation.model.ts @@ -7,6 +7,15 @@ export class ItemOperation { constructor(operationKey: string, operationUrl: string) { this.operationKey = operationKey; this.operationUrl = operationUrl; + this.setDisabled(false); + } + + /** + * Set whether this operation should be disabled + * @param disabled + */ + setDisabled(disabled: boolean): void { + this.disabled = disabled; } } 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 index 2df4b977cb..319d4c47ae 100644 --- 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 @@ -10,6 +10,7 @@ import { Router } from '@angular/router'; import { RouterStub } from '../../../shared/testing/router-stub'; import { Item } from '../../../core/shared/item.model'; import { By } from '@angular/platform-browser'; +import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; describe('ItemStatusComponent', () => { let comp: ItemStatusComponent; @@ -33,7 +34,7 @@ describe('ItemStatusComponent', () => { providers: [ { provide: Router, useValue: routerStub }, { provide: HostWindowService, useValue: new HostWindowServiceStub(0) } - ] + ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); })); diff --git a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html index 812f543716..962d09e6c4 100644 --- a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html @@ -30,7 +30,7 @@ | translate}}
-
- Date: Tue, 27 Nov 2018 14:38:19 +0100 Subject: [PATCH 008/111] 55990: Fix param name --- .../edit-item-page/item-move/item-move.component.html | 2 +- .../edit-item-page/item-move/item-move.component.spec.ts | 2 +- .../edit-item-page/item-move/item-move.component.ts | 4 ++-- .../search-hierarchy-filter.component.html | 2 +- .../search-text-filter/search-text-filter.component.html | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.html b/src/app/+item-page/edit-item-page/item-move/item-move.component.html index 0c97628d4c..0a8e4b68b5 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.html +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.html @@ -6,7 +6,7 @@
{ } ]; - comp.CollectionSearchResults.subscribe((value) => { + comp.collectionSearchResults.subscribe((value) => { expect(value).toEqual(expected); } ); diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts index 338d4b96a3..359c04c8e7 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts @@ -28,7 +28,7 @@ export class ItemMoveComponent implements OnInit { inheritPolicies = false; itemRD$: Observable>; - CollectionSearchResults: Observable = Observable.of([]); + collectionSearchResults: Observable = Observable.of([]); selectedCollection: string; selectedCollectionId: string; @@ -64,7 +64,7 @@ export class ItemMoveComponent implements OnInit { * TODO: When the API support it, only fetch collections where user has ADD rights to. */ loadSuggestions(query): void { - this.CollectionSearchResults = this.searchService.search(new SearchOptions({ + this.collectionSearchResults = this.searchService.search(new SearchOptions({ dsoType: DSpaceObjectType.COLLECTION, query: query })).first().pipe( diff --git a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html index 962d09e6c4..812f543716 100644 --- a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html @@ -30,7 +30,7 @@ | translate}}
-
- Date: Thu, 29 Nov 2018 11:39:57 +0100 Subject: [PATCH 009/111] 55990: Move item component - fix message --- resources/i18n/en.json | 1 + .../edit-item-page/item-move/item-move.component.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 395838c289..3cdf0bc180 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -107,6 +107,7 @@ "move": { "head":"Move item: {{id}}", "description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.", + "search.placeholder": "Enter a search query to look for collections", "inheritpolicies": { "description": "Inherit the default policies of the destination collection", "checkbox": "Inherit policies" diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.html b/src/app/+item-page/edit-item-page/item-move/item-move.component.html index 0a8e4b68b5..b3627e65bb 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.html +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.html @@ -7,7 +7,7 @@
Date: Fri, 30 Nov 2018 11:18:57 +0100 Subject: [PATCH 010/111] 55990: Item move - tweaks --- resources/i18n/en.json | 4 +++- .../edit-item-page/item-move/item-move.component.html | 4 ++-- .../edit-item-page/item-move/item-move.component.ts | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 3cdf0bc180..cf87e423a7 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -113,7 +113,9 @@ "checkbox": "Inherit policies" }, "move": "Move", - "cancel": "Cancel" + "cancel": "Cancel", + "success": "The item has been moved succesfully", + "error": "An error occured when attempting to move the item" } } }, diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.html b/src/app/+item-page/edit-item-page/item-move/item-move.component.html index b3627e65bb..063028c719 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.html +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.html @@ -1,7 +1,7 @@
-

{{'item.edit.move.head' | translate: { id: (itemRD$ | async)?.payload?.id} }}

+

{{'item.edit.move.head' | translate: { id: (itemRD$ | async)?.payload?.handle} }}

{{'item.edit.move.description' | translate}}

@@ -33,7 +33,7 @@ -
diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts index 359c04c8e7..07894c4504 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts @@ -104,9 +104,9 @@ export class ItemMoveComponent implements OnInit { (response: RestResponse) => { this.router.navigate([getItemEditPath(this.itemId)]); if (response.isSuccessful) { - this.notificationsService.success(this.translateService.get('item.move.success')); + this.notificationsService.success(this.translateService.get('item.edit.move.success')); } else { - this.notificationsService.error(this.translateService.get('item.move.error')); + this.notificationsService.error(this.translateService.get('item.edit.move.error')); } } ); From a698b56ae804ab3a51726c1a4170942da4d561cb Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 6 Dec 2018 10:42:37 +0100 Subject: [PATCH 011/111] 55990: Fix issues to move item component after master merge --- .../edit-item-page.component.ts | 11 +++++----- .../item-move/item-move.component.spec.ts | 13 ++++++------ .../item-move/item-move.component.ts | 21 ++++++++++++------- .../item-operation.component.spec.ts | 8 +++---- src/app/core/data/item-data.service.ts | 7 +++---- 5 files changed, 32 insertions(+), 28 deletions(-) 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..de40239b3e 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 { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; +import {fadeIn, fadeInOut} from '../../shared/animations/fade'; +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-move/item-move.component.spec.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts index c61a9ed0d3..eaf8e15fa4 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts @@ -10,14 +10,13 @@ import {ItemMoveComponent} from './item-move.component'; import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub'; import {NotificationsService} from '../../../shared/notifications/notifications.service'; import {SearchService} from '../../../+search-page/search-service/search.service'; -import {Observable} from 'rxjs/Observable'; -import 'rxjs/add/observable/of'; -import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import {of as observableOf} from 'rxjs'; import {FormsModule} from '@angular/forms'; import {ItemDataService} from '../../../core/data/item-data.service'; import {RestResponse} from '../../../core/cache/response-cache.models'; import {RemoteData} from '../../../core/data/remote-data'; import {PaginatedList} from '../../../core/data/paginated-list'; +import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; let comp: ItemMoveComponent; let fixture: ComponentFixture; @@ -34,15 +33,15 @@ const routerStub = Object.assign(new RouterStub(), { }); const mockItemDataService = jasmine.createSpyObj({ - moveToCollection: Observable.of(new RestResponse(true, '200')) + moveToCollection: observableOf(new RestResponse(true, '200')) }); const mockItemDataServiceFail = jasmine.createSpyObj({ - moveToCollection: Observable.of(new RestResponse(false, '500')) + moveToCollection: observableOf(new RestResponse(false, '500')) }); const routeStub = { - data: Observable.of({ + data: observableOf({ item: new RemoteData(false, false, true, null, { id: 'item1' }) @@ -51,7 +50,7 @@ const routeStub = { const mockSearchService = { search: () => { - return Observable.of(new RemoteData(false, false, true, null, + return observableOf(new RemoteData(false, false, true, null, new PaginatedList(null, [ { dspaceObject: { diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts index 07894c4504..9147ae2238 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts @@ -1,7 +1,6 @@ import {Component, OnInit} from '@angular/core'; import {SearchService} from '../../../+search-page/search-service/search.service'; -import {Observable} from 'rxjs/Observable'; -import {map} from 'rxjs/operators'; +import {first, map} from 'rxjs/operators'; import {DSpaceObjectType} from '../../../core/shared/dspace-object-type.model'; import {SearchOptions} from '../../../+search-page/search-options.model'; import {RemoteData} from '../../../core/data/remote-data'; @@ -16,6 +15,8 @@ import {getSucceededRemoteData} from '../../../core/shared/operators'; import {ItemDataService} from '../../../core/data/item-data.service'; import {RestResponse} from '../../../core/cache/response-cache.models'; import {getItemEditPath} from '../../item-page-routing.module'; +import {Observable} from 'rxjs'; +import {of as observableOf} from 'rxjs'; @Component({ selector: 'ds-item-move', @@ -25,10 +26,13 @@ import {getItemEditPath} from '../../item-page-routing.module'; * Component that handles the moving of an item to a different collection */ export class ItemMoveComponent implements OnInit { - + /** + * TODO: There is currently no backend support to change the owningCollection and inherit policies, + * TODO: when this is added, the inherit policies option should be used. + */ inheritPolicies = false; itemRD$: Observable>; - collectionSearchResults: Observable = Observable.of([]); + collectionSearchResults: Observable = observableOf([]); selectedCollection: string; selectedCollectionId: string; @@ -43,8 +47,8 @@ export class ItemMoveComponent implements OnInit { } ngOnInit(): void { - this.itemRD$ = this.route.data.map((data) => data.item).pipe(getSucceededRemoteData()) as Observable>; - this.itemRD$.first().subscribe((rd) => { + this.itemRD$ = this.route.data.pipe(map((data) => data.item),getSucceededRemoteData()) as Observable>; + this.itemRD$.subscribe((rd) => { this.itemId = rd.payload.id; } ); @@ -67,7 +71,8 @@ export class ItemMoveComponent implements OnInit { this.collectionSearchResults = this.searchService.search(new SearchOptions({ dsoType: DSpaceObjectType.COLLECTION, query: query - })).first().pipe( + })).pipe( + first(), map((rd: RemoteData>>) => { return rd.payload.page.map((searchResult) => { return { @@ -100,7 +105,7 @@ export class ItemMoveComponent implements OnInit { * Moves the item to a new collection based on the selected collection */ moveCollection() { - this.itemDataService.moveToCollection(this.itemId, this.selectedCollectionId).first().subscribe( + this.itemDataService.moveToCollection(this.itemId, this.selectedCollectionId).pipe(first()).subscribe( (response: RestResponse) => { this.router.navigate([getItemEditPath(this.itemId)]); if (response.isSuccessful) { diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts index 092f3af0ac..15feb5aeda 100644 --- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts @@ -4,11 +4,11 @@ import {ItemOperationComponent} from './item-operation.component'; import {TranslateModule} from '@ngx-translate/core'; import {By} from '@angular/platform-browser'; -describe('ItemOperationComponent', () => { - const itemOperation: ItemOperation = new ItemOperation('key1', 'url1'); +const itemOperation: ItemOperation = new ItemOperation('key1', 'url1'); - let fixture; - let comp; +let fixture; +let comp; +describe('ItemOperationComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index d6c76bff2b..84eca23507 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -1,7 +1,6 @@ import {Injectable} from '@angular/core'; - import {distinctUntilChanged, map, filter} from 'rxjs/operators'; -import { Injectable } from '@angular/core';import {Store} from '@ngrx/store'; +import {Store} from '@ngrx/store'; import {Observable} from 'rxjs'; import {isNotEmpty, isNotEmptyOperator} from '../../shared/empty.util'; import {BrowseService} from '../browse/browse.service'; @@ -15,7 +14,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 {FindAllOptions, PostRequest, PutRequest, RestRequest} from './request.models'; import {RestResponse} from '../cache/response-cache.models'; import {configureRequest, getResponseFromSelflink} from '../shared/operators'; import {ResponseCacheEntry} from '../cache/response-cache.reducer'; @@ -62,7 +61,7 @@ export class ItemDataService extends DataService { return this.getMoveItemEndpoint(itemId, collectionId).pipe( // isNotEmptyOperator(), distinctUntilChanged(), - map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)), + map((endpointURL: string) => new PutRequest(this.requestService.generateRequestId(), endpointURL)), configureRequest(this.requestService), map((request: RestRequest) => request.href), getResponseFromSelflink(this.responseCache), From e2420c56d38cb3d34d8738985acb991d9dc9dd73 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 6 Dec 2018 15:59:45 +0100 Subject: [PATCH 012/111] Fix item opertation test issue --- .../item-operation/item-operation.component.spec.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts index 15feb5aeda..54d5a8fe4a 100644 --- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts @@ -4,11 +4,11 @@ import {ItemOperationComponent} from './item-operation.component'; import {TranslateModule} from '@ngx-translate/core'; import {By} from '@angular/platform-browser'; -const itemOperation: ItemOperation = new ItemOperation('key1', 'url1'); - -let fixture; -let comp; describe('ItemOperationComponent', () => { + let itemOperation: ItemOperation; + + let fixture; + let comp; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -19,6 +19,8 @@ describe('ItemOperationComponent', () => { beforeEach(() => { + itemOperation = new ItemOperation('key1', 'url1'); + fixture = TestBed.createComponent(ItemOperationComponent); comp = fixture.componentInstance; comp.operation = itemOperation; From 1e6226205083dcc02c50b2320c4d77be9c0f5933 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Wed, 19 Dec 2018 13:20:42 +0100 Subject: [PATCH 013/111] Remove unrelated ItemOperation --- .../edit-item-page/item-status/item-status.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 e92ae10b55..70f7c737f6 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 @@ -59,8 +59,7 @@ export class ItemStatusComponent implements OnInit { The value is supposed to be a href for the button */ this.operations = [ - new ItemOperation('mappedCollections',this.getCurrentUrl() + '/'), - new ItemOperation('move', this.getCurrentUrl() + '/move'), + new ItemOperation('move', this.getCurrentUrl() + '/move') ] } From 395a78c360aa927b515ebf61d2f80083340cac4e Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 28 May 2019 13:14:59 +0200 Subject: [PATCH 014/111] 62571: Update item move method --- .../item-move/item-move.component.ts | 5 ++++- src/app/core/data/item-data.service.ts | 22 ++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts index 7eb0e4c10e..4f6f9bbe08 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts @@ -17,6 +17,7 @@ import {getItemEditPath} from '../../item-page-routing.module'; import {Observable} from 'rxjs'; import {of as observableOf} from 'rxjs'; import { RestResponse } from '../../../core/cache/response.models'; +import { Collection } from '../../../core/shared/collection.model'; @Component({ selector: 'ds-item-move', @@ -34,6 +35,7 @@ export class ItemMoveComponent implements OnInit { itemRD$: Observable>; collectionSearchResults: Observable = observableOf([]); selectedCollection: string; + selectedCollectionObject: Collection; selectedCollectionId: string; itemId: string; @@ -92,6 +94,7 @@ export class ItemMoveComponent implements OnInit { onClick(data: any): void { this.selectedCollection = data.name; this.selectedCollectionId = data.id; + this.selectedCollectionObject = data; } /** @@ -105,7 +108,7 @@ export class ItemMoveComponent implements OnInit { * Moves the item to a new collection based on the selected collection */ moveCollection() { - this.itemDataService.moveToCollection(this.itemId, this.selectedCollectionId).pipe(first()).subscribe( + this.itemDataService.moveToCollection(this.itemId, this.selectedCollectionObject).pipe(first()).subscribe( (response: RestResponse) => { this.router.navigate([getItemEditPath(this.itemId)]); if (response.isSuccessful) { diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 1e6f3e50de..9d686d98d1 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -12,15 +12,17 @@ import { URLCombiner } from '../url-combiner/url-combiner'; import { DataService } from './data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { DeleteByIDRequest, FindAllOptions, PatchRequest, PutRequest, RestRequest } from './request.models'; +import { FindAllOptions, PatchRequest, PutRequest, RestRequest } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { configureRequest, getRequestFromRequestHref } from '../shared/operators'; import { RequestEntry } from './request.reducer'; import { RestResponse } from '../cache/response.models'; +import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; +import { Collection } from '../shared/collection.model'; @Injectable() export class ItemDataService extends DataService { @@ -120,22 +122,26 @@ export class ItemDataService extends DataService { ); } - public getMoveItemEndpoint(itemId: string, collectionId?: string): Observable { + public getMoveItemEndpoint(itemId: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getIDHref(endpoint, itemId)), - map((endpoint: string) => `${endpoint}/owningCollection/move/${collectionId ? `/${collectionId}` : ''}`) + map((endpoint: string) => `${endpoint}/owningCollection`) ); } - public moveToCollection(itemId: string, collectionId: string): Observable { - const requestId = this.requestService.generateRequestId(); + public moveToCollection(itemId: string, collection: Collection): Observable { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; - const hrefObs = this.getMoveItemEndpoint(itemId, collectionId); + const requestId = this.requestService.generateRequestId(); + const hrefObs = this.getMoveItemEndpoint(itemId); hrefObs.pipe( find((href: string) => hasValue(href)), map((href: string) => { - const request = new PutRequest(requestId, href); + const request = new PutRequest(requestId, href, collection.self, options); this.requestService.configure(request); }) ).subscribe(); From 74bf69f984688a8195e4a5f3b19472e537843763 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 28 May 2019 15:47:42 +0200 Subject: [PATCH 015/111] 62571: Move item update and test fixes --- .../edit-item-page/edit-item-page.module.ts | 2 + .../item-move/item-move.component.spec.ts | 78 ++++++++++--------- .../item-move/item-move.component.ts | 8 +- 3 files changed, 47 insertions(+), 41 deletions(-) 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 e2f63ac5fc..de672c9ea7 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 @@ -15,6 +15,7 @@ import { ItemMetadataComponent } from './item-metadata/item-metadata.component'; import { EditInPlaceFieldComponent } from './item-metadata/edit-in-place-field/edit-in-place-field.component'; import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component'; import { ItemMoveComponent } from './item-move/item-move.component'; +import { EditItemPageRoutingModule } from './edit-item-page.routing.module'; /** * Module that contains all components related to the Edit Item page administrator functionality @@ -23,6 +24,7 @@ import { ItemMoveComponent } from './item-move/item-move.component'; imports: [ CommonModule, SharedModule, + EditItemPageRoutingModule ], declarations: [ EditItemPageComponent, diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts index 8f95441bad..3d947fdabe 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts @@ -1,22 +1,23 @@ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {Item} from '../../../core/shared/item.model'; -import {RouterStub} from '../../../shared/testing/router-stub'; -import {CommonModule} from '@angular/common'; -import {RouterTestingModule} from '@angular/router/testing'; -import {TranslateModule} from '@ngx-translate/core'; -import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; -import {ActivatedRoute, Router} from '@angular/router'; -import {ItemMoveComponent} from './item-move.component'; -import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub'; -import {NotificationsService} from '../../../shared/notifications/notifications.service'; -import {SearchService} from '../../../+search-page/search-service/search.service'; -import {of as observableOf} from 'rxjs'; -import {FormsModule} from '@angular/forms'; -import {ItemDataService} from '../../../core/data/item-data.service'; -import {RemoteData} from '../../../core/data/remote-data'; -import {PaginatedList} from '../../../core/data/paginated-list'; -import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Item } from '../../../core/shared/item.model'; +import { RouterStub } from '../../../shared/testing/router-stub'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ActivatedRoute, Router } from '@angular/router'; +import { ItemMoveComponent } from './item-move.component'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { SearchService } from '../../../+search-page/search-service/search.service'; +import { of as observableOf } from 'rxjs'; +import { FormsModule } from '@angular/forms'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { RestResponse } from '../../../core/cache/response.models'; +import { Collection } from '../../../core/shared/collection.model'; describe('ItemMoveComponent', () => { let comp: ItemMoveComponent; @@ -49,20 +50,28 @@ describe('ItemMoveComponent', () => { }) }; + const collection1 = Object.assign(new Collection(),{ + uuid: 'collection-uuid-1', + name: 'Test collection 1', + self: 'self-link-1', + }); + + const collection2 = Object.assign(new Collection(),{ + uuid: 'collection-uuid-2', + name: 'Test collection 2', + self: 'self-link-2', + }); + const mockSearchService = { search: () => { return observableOf(new RemoteData(false, false, true, null, new PaginatedList(null, [ { - dspaceObject: { - name: 'Test collection 1', - uuid: 'collection1' - }, hitHighlights: {} + indexableObject: collection1, + hitHighlights: {} }, { - dspaceObject: { - name: 'Test collection 2', - uuid: 'collection2' - }, hitHighlights: {} + indexableObject: collection2, + hitHighlights: {} } ]))); } @@ -98,14 +107,14 @@ describe('ItemMoveComponent', () => { displayValue: 'Test collection 1', value: { name: 'Test collection 1', - id: 'collection1', + object: collection1, } }, { displayValue: 'Test collection 2', value: { name: 'Test collection 2', - id: 'collection2', + object: collection2, } } ]; @@ -121,24 +130,23 @@ describe('ItemMoveComponent', () => { it('should on click select the correct collection name and id', () => { const data = { name: 'Test collection 1', - id: 'collection1', + object: collection1, }; comp.onClick(data); expect(comp.selectedCollection).toEqual('Test collection 1'); - expect(comp.selectedCollectionId).toEqual('collection1'); + expect(comp.selectedCollectionObject).toEqual(collection1); }); describe('moveCollection', () => { it('should call itemDataService.moveToCollection', () => { comp.itemId = 'item-id'; - comp.selectedCollectionId = 'selected-collection-id'; + comp.selectedCollection = 'selected-collection-id'; + comp.selectedCollectionObject = collection1; comp.moveCollection(); - expect(mockItemDataService.moveToCollection).toHaveBeenCalledWith('item-id', 'selected-collection-id'); + expect(mockItemDataService.moveToCollection).toHaveBeenCalledWith('item-id', collection1); }); it('should call notificationsService success message on success', () => { - // spyOn(notificationsServiceStub, 'success'); - comp.moveCollection(); expect(notificationsServiceStub.success).toHaveBeenCalled(); @@ -170,8 +178,6 @@ describe('ItemMoveComponent', () => { }); it('should call notificationsService error message on fail', () => { - // spyOn(notificationsServiceStub, 'error'); - comp.moveCollection(); expect(notificationsServiceStub.error).toHaveBeenCalled(); diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts index 4f6f9bbe08..4cca0cd3a4 100644 --- a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts +++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts @@ -37,7 +37,6 @@ export class ItemMoveComponent implements OnInit { selectedCollection: string; selectedCollectionObject: Collection; - selectedCollectionId: string; itemId: string; constructor(private route: ActivatedRoute, @@ -78,8 +77,8 @@ export class ItemMoveComponent implements OnInit { map((rd: RemoteData>>) => { return rd.payload.page.map((searchResult) => { return { - displayValue: searchResult.dspaceObject.name, - value: {name: searchResult.dspaceObject.name, id: searchResult.dspaceObject.uuid} + displayValue: searchResult.indexableObject.name, + value: {name: searchResult.indexableObject.name, object: searchResult.indexableObject} }; }); }) @@ -93,8 +92,7 @@ export class ItemMoveComponent implements OnInit { */ onClick(data: any): void { this.selectedCollection = data.name; - this.selectedCollectionId = data.id; - this.selectedCollectionObject = data; + this.selectedCollectionObject = data.object; } /** From a419e64cef6f1e72448ef8d6130acd7466865f42 Mon Sep 17 00:00:00 2001 From: Philip Vissenaekens Date: Tue, 28 May 2019 17:13:18 +0200 Subject: [PATCH 016/111] 62571: removed duplicate line --- .../item-operation/item-operation.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts index 9b0c516083..1901bf5fb4 100644 --- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts @@ -20,8 +20,6 @@ describe('ItemOperationComponent', () => { beforeEach(() => { itemOperation = new ItemOperation('key1', 'url1'); - itemOperation = new ItemOperation('key1', 'url1'); - fixture = TestBed.createComponent(ItemOperationComponent); comp = fixture.componentInstance; comp.operation = itemOperation; From 37fd04593b861cf82e45f9e762ec9a568d3ee06a Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 14 Jun 2019 16:15:45 +0200 Subject: [PATCH 017/111] 62355: replaced all method calls in templates (except for metadata values...) --- .../search-authority-filter.component.html | 2 +- .../search-facet-option.component.html | 2 +- .../search-facet-option.component.ts | 4 +- .../search-facet-range-option.component.html | 2 +- .../search-facet-range-option.component.ts | 5 +- ...earch-facet-selected-option.component.html | 2 +- .../search-facet-selected-option.component.ts | 5 +- .../search-facet-filter.component.ts | 13 ++-- .../search-filter/search-filter.service.ts | 6 +- .../search-hierarchy-filter.component.html | 2 +- .../search-range-filter.component.html | 2 +- .../search-text-filter.component.html | 2 +- .../search-filters.component.html | 2 +- .../search-filters.component.ts | 5 +- .../search-label/search-label.component.html | 6 ++ .../search-label/search-label.component.ts | 75 +++++++++++++++++++ .../search-labels.component.html | 12 +-- .../search-labels/search-labels.component.ts | 45 +---------- .../+search-page/search-page.component.html | 6 +- src/app/+search-page/search-page.component.ts | 9 ++- src/app/+search-page/search-page.module.ts | 2 + .../item-type-switcher.component.html | 2 +- .../switcher/item-type-switcher.component.ts | 6 +- .../object-collection.component.html | 6 +- .../object-collection.component.spec.ts | 8 +- .../object-collection.component.ts | 38 +++------- .../wrapper-detail-element.component.html | 2 +- .../wrapper-detail-element.component.ts | 6 +- ...-search-result-grid-element.component.html | 2 +- .../search-result-grid-element.component.ts | 4 +- .../wrapper-grid-element.component.html | 2 +- .../wrapper-grid-element.component.ts | 3 +- .../search-result-list-element.component.ts | 2 + .../wrapper-list-element.component.html | 2 +- .../wrapper-list-element.component.ts | 4 +- 35 files changed, 166 insertions(+), 130 deletions(-) create mode 100644 src/app/+search-page/search-labels/search-label/search-label.component.html create mode 100644 src/app/+search-page/search-labels/search-label/search-label.component.ts diff --git a/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html index 76cdc6c8f5..63d034c6ea 100644 --- a/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html @@ -17,7 +17,7 @@
{{filterValue.value}} diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts index 1fccee3736..1488f7a1e1 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts @@ -50,6 +50,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { */ addQueryParams; + searchLink: string; /** * Subscription to unsubscribe from on destroy */ @@ -66,6 +67,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { * Initializes all observable instance variables and starts listening to them */ ngOnInit(): void { + this.searchLink = this.getSearchLink(); this.isVisible = this.isChecked().pipe(map((checked: boolean) => !checked)); this.sub = observableCombineLatest(this.selectedValues$, this.searchConfigService.searchOptions) .subscribe(([selectedValues, searchOptions]) => { @@ -83,7 +85,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html index 8e8ad9b4e3..577e3e3c1c 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html @@ -1,5 +1,5 @@ {{filterValue.label}} diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts index 54d5d535df..1c243adfee 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts @@ -55,6 +55,8 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy { */ sub: Subscription; + searchLink: string; + constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected searchConfigService: SearchConfigurationService, @@ -66,6 +68,7 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy { * Initializes all observable instance variables and starts listening to them */ ngOnInit(): void { + this.searchLink = this.getSearchLink(); this.isVisible = this.isChecked().pipe(map((checked: boolean) => !checked)); this.sub = this.searchConfigService.searchOptions.subscribe(() => { this.updateChangeParams() @@ -82,7 +85,7 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy { /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html index 5657bd224e..5198433207 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.html @@ -1,5 +1,5 @@ {{selectedValue.label}} diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts index 78dde92c2b..123a32dfb4 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts @@ -49,6 +49,8 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy { */ sub: Subscription; + searchLink: string; + constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected searchConfigService: SearchConfigurationService, @@ -64,12 +66,13 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy { .subscribe(([selectedValues, searchOptions]) => { this.updateRemoveParams(selectedValues) }); + this.searchLink = this.getSearchLink(); } /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } 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 ee980a0599..ccbda54f89 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 @@ -80,6 +80,11 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { */ searchOptions$: Observable; + /** + * The current URL + */ + currentUrl: string; + constructor(protected searchService: SearchService, protected filterService: SearchFilterService, protected rdbs: RemoteDataBuildService, @@ -93,6 +98,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { * Initializes all observable instance variables and starts listening to them */ ngOnInit(): void { + this.currentUrl = this.router.url; this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined)); this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged()); @@ -215,13 +221,6 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { return this.filterService.getPage(this.filterConfig.name); } - /** - * @returns {string} the current URL - */ - getCurrentUrl() { - return this.router.url; - } - /** * Submits a new active custom value to the filter from the input field * @param data The string from the input field diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 4b12417084..6024ad7249 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -1,5 +1,5 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { mergeMap, map, distinctUntilChanged } from 'rxjs/operators'; +import { distinctUntilChanged, map, mergeMap } from 'rxjs/operators'; import { Injectable, InjectionToken } from '@angular/core'; import { SearchFiltersState, SearchFilterState } from './search-filter.reducer'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; @@ -17,12 +17,8 @@ import { SearchFilterConfig } from '../../search-service/search-filter-config.mo import { RouteService } from '../../../shared/services/route.service'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; -import { SearchOptions } from '../../search-options.model'; -import { PaginatedSearchOptions } from '../../paginated-search-options.model'; import { SearchFixedFilterService } from './search-fixed-filter.service'; import { Params } from '@angular/router'; -import * as postcss from 'postcss'; -import prefix = postcss.vendor.prefix; // const spy = create(); const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; diff --git a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html index ac2a72f4b6..996fd7f751 100644 --- a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html @@ -17,7 +17,7 @@
+ [action]="currentUrl">
-
{{"search.filters.reset" | translate}} +{{"search.filters.reset" | translate}} diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts index e970647747..ba63d143c6 100644 --- a/src/app/+search-page/search-filters/search-filters.component.ts +++ b/src/app/+search-page/search-filters/search-filters.component.ts @@ -37,6 +37,8 @@ export class SearchFiltersComponent implements OnInit { */ @Input() inPlaceSearch; + searchLink: string; + /** * Initialize instance variables * @param {SearchService} searchService @@ -60,12 +62,13 @@ export class SearchFiltersComponent implements OnInit { Object.keys(filters).forEach((f) => filters[f] = null); return filters; })); + this.searchLink = this.getSearchLink(); } /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.html b/src/app/+search-page/search-labels/search-label/search-label.component.html new file mode 100644 index 0000000000..391efcb763 --- /dev/null +++ b/src/app/+search-page/search-labels/search-label/search-label.component.html @@ -0,0 +1,6 @@ + + {{('search.filters.applied.' + key) | translate}}: {{normalizeFilterValue(value)}} + × + \ No newline at end of file diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.ts b/src/app/+search-page/search-labels/search-label/search-label.component.ts new file mode 100644 index 0000000000..ab58e1bf4e --- /dev/null +++ b/src/app/+search-page/search-labels/search-label/search-label.component.ts @@ -0,0 +1,75 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { Params } from '@angular/router'; +import { SearchService } from '../../search-service/search.service'; +import { map } from 'rxjs/operators'; +import { hasValue, isNotEmpty } from '../../../shared/empty.util'; + +@Component({ + selector: 'ds-search-label', + templateUrl: './search-label.component.html', +}) + +/** + * Component that represents the labels containing the currently active filters + */ +export class SearchLabelComponent implements OnInit { + @Input() key: string; + @Input() value: string; + @Input() inPlaceSearch: boolean; + @Input() appliedFilters: Observable; + searchLink: string; + removeParameters: Observable; + + /** + * Initialize the instance variable + */ + constructor( + private searchService: SearchService) { + } + + ngOnInit(): void { + this.searchLink = this.getSearchLink(); + this.removeParameters = this.getRemoveParams(); + } + + /** + * Calculates the parameters that should change if a given value for the given filter would be removed from the active filters + * @returns {Observable} The changed filter parameters + */ + getRemoveParams(): Observable { + return this.appliedFilters.pipe( + map((filters) => { + const field: string = Object.keys(filters).find((f) => f === this.key); + const newValues = hasValue(filters[field]) ? filters[field].filter((v) => v !== this.value) : null; + return { + [field]: isNotEmpty(newValues) ? newValues : null, + page: 1 + }; + }) + ) + } + + /** + * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true + */ + private getSearchLink(): string { + if (this.inPlaceSearch) { + return './'; + } + return this.searchService.getSearchLink(); + } + + /** + * TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved + * Strips authority operator from filter value + * e.g. 'test ,authority' => 'test' + * + * @param value + */ + normalizeFilterValue(value: string) { + // const pattern = /,[^,]*$/g; + const pattern = /,authority*$/g; + return value.replace(pattern, ''); + } +} diff --git a/src/app/+search-page/search-labels/search-labels.component.html b/src/app/+search-page/search-labels/search-labels.component.html index cac81e8717..6a668826da 100644 --- a/src/app/+search-page/search-labels/search-labels.component.html +++ b/src/app/+search-page/search-labels/search-labels.component.html @@ -1,13 +1,7 @@ diff --git a/src/app/+search-page/search-labels/search-labels.component.ts b/src/app/+search-page/search-labels/search-labels.component.ts index 104ed5b08b..5f95525bed 100644 --- a/src/app/+search-page/search-labels/search-labels.component.ts +++ b/src/app/+search-page/search-labels/search-labels.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input } from '@angular/core'; +import { Component, Inject, Input, OnInit } from '@angular/core'; import { SearchService } from '../search-service/search.service'; import { Observable } from 'rxjs'; import { Params } from '@angular/router'; @@ -31,50 +31,7 @@ export class SearchLabelsComponent { * Initialize the instance variable */ constructor( - private searchService: SearchService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) { this.appliedFilters = this.searchConfigService.getCurrentFrontendFilters(); } - - /** - * Calculates the parameters that should change if a given value for the given filter would be removed from the active filters - * @param {string} filterField The filter field parameter name from which the value should be removed - * @param {string} filterValue The value that is removed for this given filter field - * @returns {Observable} The changed filter parameters - */ - getRemoveParams(filterField: string, filterValue: string): Observable { - return this.appliedFilters.pipe( - map((filters) => { - const field: string = Object.keys(filters).find((f) => f === filterField); - const newValues = hasValue(filters[field]) ? filters[field].filter((v) => v !== filterValue) : null; - return { - [field]: isNotEmpty(newValues) ? newValues : null, - page: 1 - }; - }) - ) - } - - /** - * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true - */ - public getSearchLink(): string { - if (this.inPlaceSearch) { - return './'; - } - return this.searchService.getSearchLink(); - } - - /** - * TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved - * Strips authority operator from filter value - * e.g. 'test ,authority' => 'test' - * - * @param value - */ - normalizeFilterValue(value: string) { - // const pattern = /,[^,]*$/g; - const pattern = /,authority*$/g; - return value.replace(pattern, ''); - } } diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html index b4d8c70f11..fc4c2dce09 100644 --- a/src/app/+search-page/search-page.component.html +++ b/src/app/+search-page/search-page.component.html @@ -7,7 +7,7 @@ @@ -15,12 +15,12 @@
+ [@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'"> + [ngClass]="{'active': !(isSidebarCollapsed$ | async)}">
diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index f23bff96f3..5e0a5ab9a2 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -91,6 +91,9 @@ export class SearchPageComponent implements OnInit { @Input() fixedFilter$: Observable; + searchLink: string; + isSidebarCollapsed$: Observable; + constructor(protected service: SearchService, protected sidebarService: SearchSidebarService, protected windowService: HostWindowService, @@ -107,6 +110,8 @@ export class SearchPageComponent implements OnInit { * If something changes, update the list of scopes for the dropdown */ ngOnInit(): void { + this.isSidebarCollapsed$ = this.isSidebarCollapsed(); + this.searchLink = this.getSearchLink(); this.searchOptions$ = this.getSearchOptions(); this.sub = this.searchOptions$.pipe( switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(observableOf(undefined))))) @@ -147,14 +152,14 @@ export class SearchPageComponent implements OnInit { * Check if the sidebar is collapsed * @returns {Observable} emits true if the sidebar is currently collapsed, false if it is expanded */ - public isSidebarCollapsed(): Observable { + private isSidebarCollapsed(): Observable { return this.sidebarService.isCollapsed; } /** * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true */ - public getSearchLink(): string { + private getSearchLink(): string { if (this.inPlaceSearch) { return './'; } diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 65558eae17..297bfc8b74 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -32,6 +32,7 @@ import { SearchFacetSelectedOptionComponent } from './search-filters/search-filt import { SearchFacetRangeOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component'; import { SearchSwitchConfigurationComponent } from './search-switch-configuration/search-switch-configuration.component'; import { SearchAuthorityFilterComponent } from './search-filters/search-filter/search-authority-filter/search-authority-filter.component'; +import { SearchLabelComponent } from './search-labels/search-label/search-label.component'; const effects = [ SearchSidebarEffects @@ -49,6 +50,7 @@ const components = [ SearchFilterComponent, SearchFacetFilterComponent, SearchLabelsComponent, + SearchLabelComponent, SearchFacetFilterComponent, SearchFacetFilterWrapperComponent, SearchRangeFilterComponent, diff --git a/src/app/shared/items/switcher/item-type-switcher.component.html b/src/app/shared/items/switcher/item-type-switcher.component.html index 4965359495..f2ea5784fc 100644 --- a/src/app/shared/items/switcher/item-type-switcher.component.html +++ b/src/app/shared/items/switcher/item-type-switcher.component.html @@ -1 +1 @@ - + diff --git a/src/app/shared/items/switcher/item-type-switcher.component.ts b/src/app/shared/items/switcher/item-type-switcher.component.ts index 21a045b8f4..cd061bc1dd 100644 --- a/src/app/shared/items/switcher/item-type-switcher.component.ts +++ b/src/app/shared/items/switcher/item-type-switcher.component.ts @@ -32,6 +32,8 @@ export class ItemTypeSwitcherComponent implements OnInit { */ objectInjector: Injector; + component: any; + constructor(private injector: Injector) { } @@ -40,14 +42,14 @@ export class ItemTypeSwitcherComponent implements OnInit { providers: [{ provide: ITEM, useFactory: () => this.object, deps:[] }], parent: this.injector }); - + this.component = this.getComponent(); } /** * Fetch the component depending on the item's relationship type * @returns {string} */ - getComponent(): string { + private getComponent(): string { if (hasValue((this.object as any).representationType)) { const metadataRepresentation = this.object as MetadataRepresentation; return getComponentByItemType(metadataRepresentation.itemType, this.viewMode, metadataRepresentation.representationType); diff --git a/src/app/shared/object-collection/object-collection.component.html b/src/app/shared/object-collection/object-collection.component.html index 5ba889892a..a0878efa24 100644 --- a/src/app/shared/object-collection/object-collection.component.html +++ b/src/app/shared/object-collection/object-collection.component.html @@ -8,7 +8,7 @@ (pageSizeChange)="onPageSizeChange($event)" (sortDirectionChange)="onSortDirectionChange($event)" (sortFieldChange)="onSortFieldChange($event)" - *ngIf="getViewMode()===viewModeEnum.List"> + *ngIf="(currentMode$ | async) === viewModeEnum.List"> + *ngIf="(currentMode$ | async) === viewModeEnum.Grid"> + *ngIf="(currentMode$ | async) === viewModeEnum.Detail"> diff --git a/src/app/shared/object-collection/object-collection.component.spec.ts b/src/app/shared/object-collection/object-collection.component.spec.ts index aed2b2598d..3b30666757 100644 --- a/src/app/shared/object-collection/object-collection.component.spec.ts +++ b/src/app/shared/object-collection/object-collection.component.spec.ts @@ -1,10 +1,8 @@ import { ObjectCollectionComponent } from './object-collection.component'; import { SetViewMode } from '../view-mode'; -import { element } from 'protractor'; import { By } from '@angular/platform-browser'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { Config } from '../../../config/config.interface'; -import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { RouterStub } from '../testing/router-stub'; @@ -38,14 +36,14 @@ describe('ObjectCollectionComponent', () => { })); it('should only show the grid component when the viewmode is set to grid', () => { - objectCollectionComponent.currentMode = SetViewMode.Grid; + objectCollectionComponent.currentMode$ = observableOf(SetViewMode.Grid); expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeDefined(); expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeNull(); }); it('should only show the list component when the viewmode is set to list', () => { - objectCollectionComponent.currentMode = SetViewMode.List; + objectCollectionComponent.currentMode$ = observableOf(SetViewMode.List); expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeDefined(); expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeNull(); diff --git a/src/app/shared/object-collection/object-collection.component.ts b/src/app/shared/object-collection/object-collection.component.ts index ccc1de1f2f..526fc95781 100644 --- a/src/app/shared/object-collection/object-collection.component.ts +++ b/src/app/shared/object-collection/object-collection.component.ts @@ -11,7 +11,7 @@ import { import { ActivatedRoute, Router } from '@angular/router'; import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { filter, map, startWith } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { PageInfo } from '../../core/shared/page-info.model'; @@ -19,14 +19,14 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { ListableObject } from './shared/listable-object.model'; import { SetViewMode } from '../view-mode'; -import { hasValue, isNotEmpty } from '../empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../empty.util'; @Component({ selector: 'ds-viewable-collection', styleUrls: ['./object-collection.component.scss'], templateUrl: './object-collection.component.html', }) -export class ObjectCollectionComponent implements OnChanges, OnInit { +export class ObjectCollectionComponent implements OnInit { @Input() objects: RemoteData; @Input() config?: PaginationComponentOptions; @@ -34,7 +34,6 @@ export class ObjectCollectionComponent implements OnChanges, OnInit { @Input() hasBorder = false; @Input() hideGear = false; pageInfo: Observable; - private sub; /** * An event fired when the page is changed. * Event's payload equals to the newly selected page. @@ -61,25 +60,17 @@ export class ObjectCollectionComponent implements OnChanges, OnInit { */ @Output() sortFieldChange: EventEmitter = new EventEmitter(); data: any = {}; - currentMode: SetViewMode = SetViewMode.List; + currentMode$: Observable; viewModeEnum = SetViewMode; - ngOnChanges(changes: SimpleChanges) { - if (changes.objects && !changes.objects.isFirstChange()) { - // this.pageInfo = this.objects.pageInfo; - } - } - ngOnInit(): void { - // this.pageInfo = this.objects.pageInfo; - - this.sub = this.route + this.currentMode$ = this.route .queryParams - .subscribe((params) => { - if (isNotEmpty(params.view)) { - this.currentMode = params.view; - } - }); + .pipe( + filter((params) => isNotEmpty(params.view)), + map((params) => params.view), + startWith(SetViewMode.List) + ); } /** @@ -96,15 +87,6 @@ export class ObjectCollectionComponent implements OnChanges, OnInit { private router: Router) { } - getViewMode(): SetViewMode { - this.route.queryParams.pipe(map((params) => { - if (isNotEmpty(params.view) && hasValue(params.view)) { - this.currentMode = params.view; - } - })); - return this.currentMode; - } - onPageChange(event) { this.pageChange.emit(event); } diff --git a/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.html b/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.html index 00a8ed2dc8..ef7254b97c 100644 --- a/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.html +++ b/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.html @@ -1 +1 @@ - + diff --git a/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.ts b/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.ts index 92b30f9ce7..2ca8069b16 100644 --- a/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.ts +++ b/src/app/shared/object-detail/wrapper-detail-element/wrapper-detail-element.component.ts @@ -26,6 +26,8 @@ export class WrapperDetailElementComponent implements OnInit { */ objectInjector: Injector; + detailElement: any; + /** * Initialize instance variables * @@ -42,13 +44,13 @@ export class WrapperDetailElementComponent implements OnInit { providers: [{ provide: 'objectElementProvider', useFactory: () => (this.object), deps:[] }], parent: this.injector }); - + this.detailElement = this.getDetailElement(); } /** * Return class name for the object to inject */ - getDetailElement(): string { + private getDetailElement(): string { const f: GenericConstructor = this.object.constructor as GenericConstructor; return rendersDSOType(f, SetViewMode.Detail); } diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html index c7e2f524f3..ecbf8f706e 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts index 0961dc96ee..2e19f2fdf9 100644 --- a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts @@ -15,10 +15,12 @@ import { Metadata } from '../../../core/shared/metadata.utils'; export class SearchResultGridElementComponent, K extends DSpaceObject> extends AbstractListableElementComponent { dso: K; + isCollapsed$: Observable; public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, private truncatableService: TruncatableService) { super(listableObject); this.dso = this.object.indexableObject; + this.isCollapsed$ = this.isCollapsed(); } /** @@ -41,7 +43,7 @@ export class SearchResultGridElementComponent, K exten return Metadata.firstValue([this.object.hitHighlights, this.dso.metadata], keyOrKeys); } - isCollapsed(): Observable { + private isCollapsed(): Observable { return this.truncatableService.isCollapsed(this.dso.id); } diff --git a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.html b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.html index b613b16055..d6fd1cf9aa 100644 --- a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.html +++ b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.html @@ -1 +1 @@ - + diff --git a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts index 84f9357b2d..0a7312484f 100644 --- a/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts +++ b/src/app/shared/object-grid/wrapper-grid-element/wrapper-grid-element.component.ts @@ -12,6 +12,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m export class WrapperGridElementComponent implements OnInit { @Input() object: ListableObject; objectInjector: Injector; + gridElement: any; constructor(private injector: Injector) { } @@ -21,7 +22,7 @@ export class WrapperGridElementComponent implements OnInit { providers: [{ provide: 'objectElementProvider', useFactory: () => (this.object), deps:[] }], parent: this.injector }); - + this.gridElement = this.getGridElement(); } getGridElement(): string { diff --git a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts index 227d375f2a..7017f3f48b 100644 --- a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts @@ -8,6 +8,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; import { TruncatableService } from '../../truncatable/truncatable.service'; import { Metadata } from '../../../core/shared/metadata.utils'; +import { MetadataMap } from '../../../core/shared/metadata.models'; @Component({ selector: 'ds-search-result-list-element', @@ -16,6 +17,7 @@ import { Metadata } from '../../../core/shared/metadata.utils'; export class SearchResultListElementComponent, K extends DSpaceObject> extends AbstractListableElementComponent { dso: K; + metadata: MetadataMap; public constructor(@Inject('objectElementProvider') public listable: ListableObject, protected truncatableService: TruncatableService) { super(listable); diff --git a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.html b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.html index d5cfebdfa5..db87596f31 100644 --- a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.html +++ b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts index 17e6f0fd85..29b1364a75 100644 --- a/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts +++ b/src/app/shared/object-list/wrapper-list-element/wrapper-list-element.component.ts @@ -13,6 +13,7 @@ export class WrapperListElementComponent implements OnInit { @Input() object: ListableObject; @Input() index: number; objectInjector: Injector; + listElement: any; constructor(private injector: Injector) {} @@ -24,9 +25,10 @@ export class WrapperListElementComponent implements OnInit { ], parent: this.injector }); + this.listElement = this.getListElement(); } - getListElement(): string { + private getListElement(): string { const f: GenericConstructor = this.object.constructor as GenericConstructor; return rendersDSOType(f, SetViewMode.List); } From bb76015aa12886337fb65154630d7fd925b8d7f7 Mon Sep 17 00:00:00 2001 From: lotte Date: Tue, 18 Jun 2019 08:58:35 +0200 Subject: [PATCH 018/111] Solved issue with non-existing search pages --- .../create-collection-page.component.spec.ts | 2 +- .../create-collection-page.component.ts | 2 +- .../create-community-page.component.spec.ts | 2 +- .../create-community-page.component.ts | 2 +- .../delete-community-page.component.spec.ts | 2 +- .../my-dspace-configuration.service.ts | 2 +- .../my-dspace-page.component.spec.ts | 2 +- .../filtered-search-page.component.ts | 7 +++- .../filtered-search-page.guard.ts | 37 +++++++++++++++++-- .../search-filter/search-filter.service.ts | 2 +- .../search-fixed-filter.service.spec.ts | 2 +- .../search-fixed-filter.service.ts | 4 +- .../search-range-filter.component.spec.ts | 2 +- .../search-range-filter.component.ts | 2 +- .../search-page-routing.module.ts | 9 ++++- .../search-page.component.spec.ts | 2 +- src/app/+search-page/search-page.component.ts | 10 ++--- .../search-configuration.service.ts | 2 +- .../search-service/search.service.spec.ts | 2 +- .../search-service/search.service.ts | 2 +- src/app/app.component.spec.ts | 4 +- src/app/app.component.ts | 4 +- src/app/core/auth/auth.service.spec.ts | 6 +-- src/app/core/auth/auth.service.ts | 4 +- src/app/core/core.effects.ts | 2 +- src/app/core/core.module.ts | 8 ++-- src/app/core/core.reducers.ts | 2 +- .../{shared => core}/services/api.service.ts | 0 .../services/client-cookie.service.ts | 0 .../services/cookie.service.spec.ts | 0 .../services/cookie.service.ts | 0 .../services/route.actions.ts | 0 .../services/route.effects.ts | 0 .../services/route.reducer.ts | 0 .../services/route.service.spec.ts | 4 +- .../services/route.service.ts | 10 ++--- .../services/server-cookie.service.ts | 0 .../services/server-response.service.ts | 0 .../services/window.service.ts | 0 .../pagenotfound/pagenotfound.component.ts | 2 +- .../create-comcol-page.component.spec.ts | 2 +- .../create-comcol-page.component.ts | 2 +- src/app/submission/submission.module.ts | 2 +- src/app/submission/submission.service.spec.ts | 2 +- src/app/submission/submission.service.ts | 2 +- src/modules/app/browser-app.module.ts | 4 +- src/modules/app/server-app.module.ts | 4 +- 47 files changed, 99 insertions(+), 62 deletions(-) rename src/app/{shared => core}/services/api.service.ts (100%) rename src/app/{shared => core}/services/client-cookie.service.ts (100%) rename src/app/{shared => core}/services/cookie.service.spec.ts (100%) rename src/app/{shared => core}/services/cookie.service.ts (100%) rename src/app/{shared => core}/services/route.actions.ts (100%) rename src/app/{shared => core}/services/route.effects.ts (100%) rename src/app/{shared => core}/services/route.reducer.ts (100%) rename src/app/{shared => core}/services/route.service.spec.ts (97%) rename src/app/{shared => core}/services/route.service.ts (95%) rename src/app/{shared => core}/services/server-cookie.service.ts (100%) rename src/app/{shared => core}/services/server-response.service.ts (100%) rename src/app/{shared => core}/services/window.service.ts (100%) diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts b/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts index 29350a83e0..e223b11c65 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts @@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { SharedModule } from '../../shared/shared.module'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { of as observableOf } from 'rxjs'; diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.ts b/src/app/+collection-page/create-collection-page/create-collection-page.component.ts index 94229b4932..2cab36d285 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.ts +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.ts @@ -1,6 +1,6 @@ import { Component } from '@angular/core'; import { CommunityDataService } from '../../core/data/community-data.service'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { Router } from '@angular/router'; import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component'; import { Collection } from '../../core/shared/collection.model'; diff --git a/src/app/+community-page/create-community-page/create-community-page.component.spec.ts b/src/app/+community-page/create-community-page/create-community-page.component.spec.ts index dba15dbe88..dead5a5c3b 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.spec.ts +++ b/src/app/+community-page/create-community-page/create-community-page.component.spec.ts @@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { SharedModule } from '../../shared/shared.module'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { of as observableOf } from 'rxjs'; diff --git a/src/app/+community-page/create-community-page/create-community-page.component.ts b/src/app/+community-page/create-community-page/create-community-page.component.ts index 828d8338af..fd5f18442a 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.ts +++ b/src/app/+community-page/create-community-page/create-community-page.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { Community } from '../../core/shared/community.model'; import { CommunityDataService } from '../../core/data/community-data.service'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { Router } from '@angular/router'; import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component'; diff --git a/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts b/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts index f18c4fb1f1..c23df93976 100644 --- a/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts +++ b/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts @@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { SharedModule } from '../../shared/shared.module'; import { of as observableOf } from 'rxjs'; import { NotificationsService } from '../../shared/notifications/notifications.service'; diff --git a/src/app/+my-dspace-page/my-dspace-configuration.service.ts b/src/app/+my-dspace-page/my-dspace-configuration.service.ts index 705ec897f8..39c7574407 100644 --- a/src/app/+my-dspace-page/my-dspace-configuration.service.ts +++ b/src/app/+my-dspace-page/my-dspace-configuration.service.ts @@ -8,7 +8,7 @@ import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value- import { RoleService } from '../core/roles/role.service'; import { SearchConfigurationOption } from '../+search-page/search-switch-configuration/search-configuration-option.model'; import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service'; diff --git a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts index 9658814a6a..d31d724b9e 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts @@ -17,7 +17,7 @@ import { HostWindowService } from '../shared/host-window.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { RemoteData } from '../core/data/remote-data'; import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from './my-dspace-page.component'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { routeServiceStub } from '../shared/testing/route-service-stub'; import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub'; import { SearchService } from '../+search-page/search-service/search.service'; diff --git a/src/app/+search-page/filtered-search-page.component.ts b/src/app/+search-page/filtered-search-page.component.ts index 66c619b823..85d521ee15 100644 --- a/src/app/+search-page/filtered-search-page.component.ts +++ b/src/app/+search-page/filtered-search-page.component.ts @@ -4,12 +4,14 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; import { SearchPageComponent } from './search-page.component'; import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; import { pushInOut } from '../shared/animations/push'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SearchConfigurationService } from './search-service/search-configuration.service'; import { Observable } from 'rxjs'; import { PaginatedSearchOptions } from './paginated-search-options.model'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; import { map } from 'rxjs/operators'; +import { isEmpty, isNotEmpty } from '../shared/empty.util'; +import { ActivatedRoute } from '@angular/router'; /** * This component renders a simple item page. @@ -53,6 +55,9 @@ export class FilteredSearchPageComponent extends SearchPageComponent implements * If something changes, update the list of scopes for the dropdown */ ngOnInit(): void { + if (isEmpty(this.fixedFilter$)) { + this.fixedFilter$ = this.routeService.getRouteParameterValue('filter'); + } super.ngOnInit(); } diff --git a/src/app/+search-page/filtered-search-page.guard.ts b/src/app/+search-page/filtered-search-page.guard.ts index 6d41d4965d..e28dadec71 100644 --- a/src/app/+search-page/filtered-search-page.guard.ts +++ b/src/app/+search-page/filtered-search-page.guard.ts @@ -1,6 +1,16 @@ import { Injectable } from '@angular/core'; -import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { + ActivatedRouteSnapshot, + CanActivate, + NavigationEnd, + Router, + RouterStateSnapshot +} from '@angular/router'; import { Observable } from 'rxjs'; +import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service'; +import { map, take, tap, filter } from 'rxjs/operators'; +import { isEmpty, isNotEmpty } from '../shared/empty.util'; +import { Location } from '@angular/common'; @Injectable() /** @@ -9,14 +19,33 @@ import { Observable } from 'rxjs'; * - filter: The current filter stored in route.params */ export class FilteredSearchPageGuard implements CanActivate { + constructor(private service: SearchFixedFilterService, private router: Router, private location: Location) { + } + canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean { - const filter = route.params.filter; + route.params = Object.assign({}, route.params, { filter: route.params.filter.toLowerCase() }); + const filterName = route.params.filter; - const newTitle = filter + '.search.title'; + const newTitle = filterName + '.search.title'; route.data = { title: newTitle }; - return true; + + return this.service.getQueryByFilterName(filterName).pipe( + tap((query) => { + if (isEmpty(query)) { + this.router.navigateByUrl('/404', { skipLocationChange: true }); + this.router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + take(1) + ) + .subscribe(() => this.location.replaceState(state.url)); + } + } + ), + map((query) => isNotEmpty(query)) + ); } } diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 6024ad7249..8482838101 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -14,7 +14,7 @@ import { } from './search-filter.actions'; import { hasValue, isNotEmpty, } from '../../../shared/empty.util'; import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; -import { RouteService } from '../../../shared/services/route.service'; +import { RouteService } from '../../../core/services/route.service'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { SearchFixedFilterService } from './search-fixed-filter.service'; diff --git a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts index 3f6c2ef133..a201d37d48 100644 --- a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts @@ -1,5 +1,5 @@ import { SearchFixedFilterService } from './search-fixed-filter.service'; -import { RouteService } from '../../../shared/services/route.service'; +import { RouteService } from '../../../core/services/route.service'; import { RequestService } from '../../../core/data/request.service'; import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; import { of as observableOf } from 'rxjs'; diff --git a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts index 0f17b508c9..85f637ce32 100644 --- a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts @@ -9,7 +9,6 @@ import { GenericConstructor } from '../../../core/shared/generic-constructor'; import { FilteredDiscoveryPageResponseParsingService } from '../../../core/data/filtered-discovery-page-response-parsing.service'; import { hasValue } from '../../../shared/empty.util'; import { configureRequest, getResponseFromEntry } from '../../../core/shared/operators'; -import { RouteService } from '../../../shared/services/route.service'; import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.models'; /** @@ -19,8 +18,7 @@ import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.mod export class SearchFixedFilterService { private queryByFilterPath = 'filtered-discovery-pages'; - constructor(private routeService: RouteService, - protected requestService: RequestService, + constructor(protected requestService: RequestService, private halService: HALEndpointService) { } diff --git a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts index 119f3f92a9..2b69fe7f55 100644 --- a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts @@ -16,7 +16,7 @@ import { RouterStub } from '../../../../shared/testing/router-stub'; import { Router } from '@angular/router'; import { PageInfo } from '../../../../core/shared/page-info.model'; import { SearchRangeFilterComponent } from './search-range-filter.component'; -import { RouteService } from '../../../../shared/services/route.service'; +import { RouteService } from '../../../../core/services/route.service'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component'; import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service-stub'; diff --git a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index 95d7441184..5ac59b65f8 100644 --- a/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -14,7 +14,7 @@ import { FILTER_CONFIG, IN_PLACE_SEARCH, SearchFilterService } from '../search-f import { SearchService } from '../../../search-service/search.service'; import { Router } from '@angular/router'; import * as moment from 'moment'; -import { RouteService } from '../../../../shared/services/route.service'; +import { RouteService } from '../../../../core/services/route.service'; import { hasValue } from '../../../../shared/empty.util'; import { SearchConfigurationService } from '../../../search-service/search-configuration.service'; import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component'; diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts index c3cf4e1343..e3a91c6f69 100644 --- a/src/app/+search-page/search-page-routing.module.ts +++ b/src/app/+search-page/search-page-routing.module.ts @@ -9,8 +9,13 @@ import { FilteredSearchPageGuard } from './filtered-search-page.guard'; imports: [ RouterModule.forChild([ { path: '', component: SearchPageComponent, data: { title: 'search.title' } }, - { path: ':filter', component: FilteredSearchPageComponent, canActivate: [FilteredSearchPageGuard]} + { + path: ':filter', + component: FilteredSearchPageComponent, + canActivate: [FilteredSearchPageGuard], + } ]) ] }) -export class SearchPageRoutingModule { } +export class SearchPageRoutingModule { +} diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts index 88c7c693d3..2bc3d4071d 100644 --- a/src/app/+search-page/search-page.component.spec.ts +++ b/src/app/+search-page/search-page.component.spec.ts @@ -21,7 +21,7 @@ import { SearchFilterService } from './search-filters/search-filter/search-filte import { SearchConfigurationService } from './search-service/search-configuration.service'; import { RemoteData } from '../core/data/remote-data'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub'; import { PaginatedSearchOptions } from './paginated-search-options.model'; import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service'; diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 5e0a5ab9a2..8fe38eaebb 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -13,7 +13,7 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { SearchConfigurationService } from './search-service/search-configuration.service'; import { getSucceededRemoteData } from '../core/shared/operators'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; export const SEARCH_ROUTE = '/search'; @@ -114,16 +114,16 @@ export class SearchPageComponent implements OnInit { this.searchLink = this.getSearchLink(); this.searchOptions$ = this.getSearchOptions(); this.sub = this.searchOptions$.pipe( - switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(observableOf(undefined))))) + switchMap((options) => this.service.search(options).pipe( + getSucceededRemoteData(), + startWith(undefined) + ))) .subscribe((results) => { this.resultsRD$.next(results); }); this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe( switchMap((scopeId) => this.service.getScopes(scopeId)) ); - if (!isNotEmpty(this.fixedFilter$)) { - this.fixedFilter$ = this.routeService.getRouteParameterValue('filter'); - } } /** diff --git a/src/app/+search-page/search-service/search-configuration.service.ts b/src/app/+search-page/search-service/search-configuration.service.ts index 14fcdd8d60..fa43b27e66 100644 --- a/src/app/+search-page/search-service/search-configuration.service.ts +++ b/src/app/+search-page/search-service/search-configuration.service.ts @@ -14,7 +14,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { SearchOptions } from '../search-options.model'; import { PaginatedSearchOptions } from '../paginated-search-options.model'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { RemoteData } from '../../core/data/remote-data'; import { getSucceededRemoteData } from '../../core/shared/operators'; diff --git a/src/app/+search-page/search-service/search.service.spec.ts b/src/app/+search-page/search-service/search.service.spec.ts index 9ec5bc35f2..b505504870 100644 --- a/src/app/+search-page/search-service/search.service.spec.ts +++ b/src/app/+search-page/search-service/search.service.spec.ts @@ -26,7 +26,7 @@ import { CommunityDataService } from '../../core/data/community-data.service'; import { ViewMode } from '../../core/shared/view-mode.model'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { map } from 'rxjs/operators'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; import { routeServiceStub } from '../../shared/testing/route-service-stub'; @Component({ template: '' }) diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 52be0417a8..6685e7b715 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -42,7 +42,7 @@ 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 { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../core/services/route.service'; /** * Service that performs all general actions that have to do with the search page diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index bd2d832c67..b7b34f9c55 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -26,7 +26,7 @@ import { HostWindowResizeAction } from './shared/host-window.actions'; import { MetadataService } from './core/metadata/metadata.service'; import { GLOBAL_CONFIG, ENV_CONFIG } from '../config'; -import { NativeWindowRef, NativeWindowService } from './shared/services/window.service'; +import { NativeWindowRef, NativeWindowService } from './core/services/window.service'; import { MockTranslateLoader } from './shared/mocks/mock-translate-loader'; import { MockMetadataService } from './shared/mocks/mock-metadata-service'; @@ -41,7 +41,7 @@ import { MenuServiceStub } from './shared/testing/menu-service-stub'; import { HostWindowService } from './shared/host-window.service'; import { HostWindowServiceStub } from './shared/testing/host-window-service-stub'; import { ActivatedRoute, Router } from '@angular/router'; -import { RouteService } from './shared/services/route.service'; +import { RouteService } from './core/services/route.service'; import { MockActivatedRoute } from './shared/mocks/mock-active-router'; import { MockRouter } from './shared/mocks/mock-router'; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 37cc791558..836c20208d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -19,11 +19,11 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../config'; import { MetadataService } from './core/metadata/metadata.service'; import { HostWindowResizeAction } from './shared/host-window.actions'; import { HostWindowState } from './shared/host-window.reducer'; -import { NativeWindowRef, NativeWindowService } from './shared/services/window.service'; +import { NativeWindowRef, NativeWindowService } from './core/services/window.service'; import { isAuthenticated } from './core/auth/selectors'; import { AuthService } from './core/auth/auth.service'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; -import { RouteService } from './shared/services/route.service'; +import { RouteService } from './core/services/route.service'; import variables from '../styles/_exposed_variables.scss'; import { CSSVariableService } from './shared/sass-helper/sass-helper.service'; import { MenuService } from './shared/menu/menu.service'; diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts index e766a45e48..ab2e6fd86b 100644 --- a/src/app/core/auth/auth.service.spec.ts +++ b/src/app/core/auth/auth.service.spec.ts @@ -7,12 +7,12 @@ import { REQUEST } from '@nguniversal/express-engine/tokens'; import { of as observableOf } from 'rxjs'; import { authReducer, AuthState } from './auth.reducer'; -import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service'; +import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { AuthService } from './auth.service'; import { RouterStub } from '../../shared/testing/router-stub'; import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; -import { CookieService } from '../../shared/services/cookie.service'; +import { CookieService } from '../services/cookie.service'; import { AuthRequestServiceStub } from '../../shared/testing/auth-request-service-stub'; import { AuthRequestService } from './auth-request.service'; import { AuthStatus } from './models/auth-status.model'; @@ -20,7 +20,7 @@ import { AuthTokenInfo } from './models/auth-token-info.model'; import { EPerson } from '../eperson/models/eperson.model'; import { EPersonMock } from '../../shared/testing/eperson-mock'; import { AppState } from '../../app.reducer'; -import { ClientCookieService } from '../../shared/services/client-cookie.service'; +import { ClientCookieService } from '../services/client-cookie.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index a01768e687..08c94b02f2 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -15,11 +15,11 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { AuthStatus } from './models/auth-status.model'; import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model'; import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util'; -import { CookieService } from '../../shared/services/cookie.service'; +import { CookieService } from '../services/cookie.service'; import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors'; import { AppState, routerStateSelector } from '../../app.reducer'; import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions'; -import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service'; +import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts index 9ade23e6c5..f657b5d449 100644 --- a/src/app/core/core.effects.ts +++ b/src/app/core/core.effects.ts @@ -6,7 +6,7 @@ import { AuthEffects } from './auth/auth.effects'; import { JsonPatchOperationsEffects } from './json-patch/json-patch-operations.effects'; import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects'; import { ObjectUpdatesEffects } from './data/object-updates/object-updates.effects'; -import { RouteEffects } from '../shared/services/route.effects'; +import { RouteEffects } from './services/route.effects'; export const coreEffects = [ RequestEffects, diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 6550435aa3..31d1da1ede 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -15,7 +15,7 @@ import { coreReducers } from './core.reducers'; import { isNotEmpty } from '../shared/empty.util'; -import { ApiService } from '../shared/services/api.service'; +import { ApiService } from './services/api.service'; import { BrowseEntriesResponseParsingService } from './data/browse-entries-response-parsing.service'; import { CollectionDataService } from './data/collection-data.service'; import { CommunityDataService } from './data/community-data.service'; @@ -35,12 +35,12 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; import { RequestService } from './data/request.service'; import { EndpointMapResponseParsingService } from './data/endpoint-map-response-parsing.service'; -import { ServerResponseService } from '../shared/services/server-response.service'; -import { NativeWindowFactory, NativeWindowService } from '../shared/services/window.service'; +import { ServerResponseService } from './services/server-response.service'; +import { NativeWindowFactory, NativeWindowService } from './services/window.service'; import { BrowseService } from './browse/browse.service'; import { BrowseResponseParsingService } from './data/browse-response-parsing.service'; import { ConfigResponseParsingService } from './config/config-response-parsing.service'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from './services/route.service'; import { SubmissionDefinitionsConfigService } from './config/submission-definitions-config.service'; import { SubmissionFormsConfigService } from './config/submission-forms-config.service'; import { SubmissionSectionsConfigService } from './config/submission-sections-config.service'; diff --git a/src/app/core/core.reducers.ts b/src/app/core/core.reducers.ts index 7aecb91a7a..4fcf36f9cc 100644 --- a/src/app/core/core.reducers.ts +++ b/src/app/core/core.reducers.ts @@ -13,7 +13,7 @@ import { objectUpdatesReducer, ObjectUpdatesState } from './data/object-updates/object-updates.reducer'; -import { routeReducer, RouteState } from '../shared/services/route.reducer'; +import { routeReducer, RouteState } from './services/route.reducer'; export interface CoreState { 'cache/object': ObjectCacheState, diff --git a/src/app/shared/services/api.service.ts b/src/app/core/services/api.service.ts similarity index 100% rename from src/app/shared/services/api.service.ts rename to src/app/core/services/api.service.ts diff --git a/src/app/shared/services/client-cookie.service.ts b/src/app/core/services/client-cookie.service.ts similarity index 100% rename from src/app/shared/services/client-cookie.service.ts rename to src/app/core/services/client-cookie.service.ts diff --git a/src/app/shared/services/cookie.service.spec.ts b/src/app/core/services/cookie.service.spec.ts similarity index 100% rename from src/app/shared/services/cookie.service.spec.ts rename to src/app/core/services/cookie.service.spec.ts diff --git a/src/app/shared/services/cookie.service.ts b/src/app/core/services/cookie.service.ts similarity index 100% rename from src/app/shared/services/cookie.service.ts rename to src/app/core/services/cookie.service.ts diff --git a/src/app/shared/services/route.actions.ts b/src/app/core/services/route.actions.ts similarity index 100% rename from src/app/shared/services/route.actions.ts rename to src/app/core/services/route.actions.ts diff --git a/src/app/shared/services/route.effects.ts b/src/app/core/services/route.effects.ts similarity index 100% rename from src/app/shared/services/route.effects.ts rename to src/app/core/services/route.effects.ts diff --git a/src/app/shared/services/route.reducer.ts b/src/app/core/services/route.reducer.ts similarity index 100% rename from src/app/shared/services/route.reducer.ts rename to src/app/core/services/route.reducer.ts diff --git a/src/app/shared/services/route.service.spec.ts b/src/app/core/services/route.service.spec.ts similarity index 97% rename from src/app/shared/services/route.service.spec.ts rename to src/app/core/services/route.service.spec.ts index c6003521a7..ae31f28384 100644 --- a/src/app/shared/services/route.service.spec.ts +++ b/src/app/core/services/route.service.spec.ts @@ -6,9 +6,9 @@ import { Store } from '@ngrx/store'; import { getTestScheduler, hot } from 'jasmine-marbles'; import { RouteService } from './route.service'; -import { MockRouter } from '../mocks/mock-router'; +import { MockRouter } from '../../shared/mocks/mock-router'; import { TestScheduler } from 'rxjs/testing'; -import { AddUrlToHistoryAction } from '../history/history.actions'; +import { AddUrlToHistoryAction } from '../../shared/history/history.actions'; describe('RouteService', () => { let scheduler: TestScheduler; diff --git a/src/app/shared/services/route.service.ts b/src/app/core/services/route.service.ts similarity index 95% rename from src/app/shared/services/route.service.ts rename to src/app/core/services/route.service.ts index dc626484c1..65aa858945 100644 --- a/src/app/shared/services/route.service.ts +++ b/src/app/core/services/route.service.ts @@ -12,12 +12,12 @@ import { combineLatest, Observable } from 'rxjs'; import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; import { isEqual } from 'lodash'; -import { AddUrlToHistoryAction } from '../history/history.actions'; -import { historySelector } from '../history/selectors'; +import { AddUrlToHistoryAction } from '../../shared/history/history.actions'; +import { historySelector } from '../../shared/history/selectors'; import { SetParametersAction, SetQueryParametersAction } from './route.actions'; -import { CoreState } from '../../core/core.reducers'; -import { hasValue } from '../empty.util'; -import { coreSelector } from '../../core/core.selectors'; +import { CoreState } from '../core.reducers'; +import { hasValue } from '../../shared/empty.util'; +import { coreSelector } from '../core.selectors'; /** * Selector to select all route parameters from the store diff --git a/src/app/shared/services/server-cookie.service.ts b/src/app/core/services/server-cookie.service.ts similarity index 100% rename from src/app/shared/services/server-cookie.service.ts rename to src/app/core/services/server-cookie.service.ts diff --git a/src/app/shared/services/server-response.service.ts b/src/app/core/services/server-response.service.ts similarity index 100% rename from src/app/shared/services/server-response.service.ts rename to src/app/core/services/server-response.service.ts diff --git a/src/app/shared/services/window.service.ts b/src/app/core/services/window.service.ts similarity index 100% rename from src/app/shared/services/window.service.ts rename to src/app/core/services/window.service.ts diff --git a/src/app/pagenotfound/pagenotfound.component.ts b/src/app/pagenotfound/pagenotfound.component.ts index 6e173b4139..b11de58269 100644 --- a/src/app/pagenotfound/pagenotfound.component.ts +++ b/src/app/pagenotfound/pagenotfound.component.ts @@ -1,4 +1,4 @@ -import { ServerResponseService } from '../shared/services/server-response.service'; +import { ServerResponseService } from '../core/services/server-response.service'; import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { AuthService } from '../core/auth/auth.service'; diff --git a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts index 4dad4a703f..c53c45fbe9 100644 --- a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts +++ b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CommunityDataService } from '../../../core/data/community-data.service'; -import { RouteService } from '../../services/route.service'; +import { RouteService } from '../../../core/services/route.service'; import { Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; diff --git a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts index c9fcfecb97..e07f2a5a0a 100644 --- a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts +++ b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { Community } from '../../../core/shared/community.model'; import { CommunityDataService } from '../../../core/data/community-data.service'; import { Observable } from 'rxjs'; -import { RouteService } from '../../services/route.service'; +import { RouteService } from '../../../core/services/route.service'; import { Router } from '@angular/router'; import { RemoteData } from '../../../core/data/remote-data'; import { isNotEmpty, isNotUndefined } from '../../empty.util'; diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index e6c24226e2..82f57ea970 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -31,7 +31,7 @@ import { SubmissionSubmitComponent } from './submit/submission-submit.component' @NgModule({ imports: [ CommonModule, - CoreModule, + CoreModule.forRoot(), SharedModule, StoreModule.forFeature('submission', submissionReducers, {}), EffectsModule.forFeature(submissionEffects), diff --git a/src/app/submission/submission.service.spec.ts b/src/app/submission/submission.service.spec.ts index d764f09538..80ad3b606a 100644 --- a/src/app/submission/submission.service.spec.ts +++ b/src/app/submission/submission.service.spec.ts @@ -12,7 +12,7 @@ import { MockRouter } from '../shared/mocks/mock-router'; import { SubmissionService } from './submission.service'; import { submissionReducers } from './submission.reducers'; import { SubmissionRestService } from '../core/submission/submission-rest.service'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SubmissionRestServiceStub } from '../shared/testing/submission-rest-service-stub'; import { MockActivatedRoute } from '../shared/mocks/mock-active-router'; import { GLOBAL_CONFIG } from '../../config'; diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index 82185a8eae..36aedaaab6 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -35,7 +35,7 @@ import { SubmissionRestService } from '../core/submission/submission-rest.servic import { SectionDataObject } from './sections/models/section-data.model'; import { SubmissionScopeType } from '../core/submission/submission-scope-type'; import { SubmissionObject } from '../core/submission/models/submission-object.model'; -import { RouteService } from '../shared/services/route.service'; +import { RouteService } from '../core/services/route.service'; import { SectionsType } from './sections/sections-type'; import { NotificationsService } from '../shared/notifications/notifications.service'; import { SubmissionDefinitionsModel } from '../core/config/models/config-submission-definitions.model'; diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index b20894880b..7ff70457bb 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -15,8 +15,8 @@ import { AppComponent } from '../../app/app.component'; import { AppModule } from '../../app/app.module'; import { DSpaceBrowserTransferStateModule } from '../transfer-state/dspace-browser-transfer-state.module'; import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; -import { ClientCookieService } from '../../app/shared/services/client-cookie.service'; -import { CookieService } from '../../app/shared/services/cookie.service'; +import { ClientCookieService } from '../../app/core/services/client-cookie.service'; +import { CookieService } from '../../app/core/services/cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; import { Angulartics2Module } from 'angulartics2'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index d809d3cced..bd3379c8de 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -13,8 +13,8 @@ import { DSpaceServerTransferStateModule } from '../transfer-state/dspace-server import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; import { TranslateUniversalLoader } from '../translate-universal-loader'; -import { CookieService } from '../../app/shared/services/cookie.service'; -import { ServerCookieService } from '../../app/shared/services/server-cookie.service'; +import { CookieService } from '../../app/core/services/cookie.service'; +import { ServerCookieService } from '../../app/core/services/server-cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; import { ServerAuthService } from '../../app/core/auth/server-auth.service'; From ed959e492a27213fc97b11a986c5778b85addbd2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 4 Jul 2019 11:17:56 +0200 Subject: [PATCH 019/111] Show only authorized collections list during submission --- src/app/core/data/collection-data.service.ts | 17 +++++++++++++++++ ...submission-form-collection.component.spec.ts | 16 +++++++++++++++- .../submission-form-collection.component.ts | 11 +++++++++-- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 993954a360..762838e9ae 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -19,6 +19,7 @@ import { Observable } from 'rxjs/internal/Observable'; import { FindAllOptions } from './request.models'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; +import { SearchParam } from '../cache/models/search-param.model'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -40,6 +41,22 @@ export class CollectionDataService extends ComColDataService { super(); } + /** + * Get all collections whom user has authorization to submit to by community + * + * @return boolean + * true if the user has at least one collection to submit to + */ + getAuthorizedCollectionByCommunity(communityId): Observable>> { + const searchHref = 'findAuthorizedByCommunity'; + const options = new FindAllOptions(); + options.elementsPerPage = 1000; + options.searchParams = [new SearchParam('uuid', communityId)]; + + return this.searchBy(searchHref, options).pipe( + filter((collections: RemoteData>) => !collections.isResponsePending)); + } + /** * Find whether there is a collection whom user has authorization to submit to * diff --git a/src/app/submission/form/collection/submission-form-collection.component.spec.ts b/src/app/submission/form/collection/submission-form-collection.component.spec.ts index 679500a670..fc34094ce0 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.spec.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.spec.ts @@ -8,6 +8,7 @@ import { filter } from 'rxjs/operators'; import { TranslateModule } from '@ngx-translate/core'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { Store } from '@ngrx/store'; +import { cold } from 'jasmine-marbles'; import { SubmissionServiceStub } from '../../../shared/testing/submission-service-stub'; import { mockSubmissionId, mockSubmissionRestResponse } from '../../../shared/mocks/mock-submission'; @@ -24,7 +25,7 @@ import { PaginatedList } from '../../../core/data/paginated-list'; import { PageInfo } from '../../../core/shared/page-info.model'; import { Collection } from '../../../core/shared/collection.model'; import { createTestComponent } from '../../../shared/testing/utils'; -import { cold } from 'jasmine-marbles'; +import { CollectionDataService } from '../../../core/data/collection-data.service'; const subcommunities = [Object.assign(new Community(), { name: 'SubCommunity 1', @@ -125,6 +126,12 @@ const mockCommunity2 = Object.assign(new Community(), { const mockCommunityList = observableOf(new RemoteData(true, true, true, undefined, new PaginatedList(new PageInfo(), [mockCommunity, mockCommunity2]))); +const mockCommunityCollectionList = observableOf(new RemoteData(true, true, true, + undefined, new PaginatedList(new PageInfo(), [mockCommunity1Collection1, mockCommunity1Collection2]))); + +const mockCommunity2CollectionList = observableOf(new RemoteData(true, true, true, + undefined, new PaginatedList(new PageInfo(), [mockCommunity2Collection1, mockCommunity2Collection2]))); + const mockCollectionList = [ { communities: [ @@ -193,6 +200,11 @@ describe('SubmissionFormCollectionComponent Component', () => { const communityDataService: any = jasmine.createSpyObj('communityDataService', { findAll: jasmine.createSpy('findAll') }); + + const collectionDataService: any = jasmine.createSpyObj('collectionDataService', { + getAuthorizedCollectionByCommunity: jasmine.createSpy('getAuthorizedCollectionByCommunity') + }); + const store: any = jasmine.createSpyObj('store', { dispatch: jasmine.createSpy('dispatch'), select: jasmine.createSpy('select') @@ -214,6 +226,7 @@ describe('SubmissionFormCollectionComponent Component', () => { TestComponent ], providers: [ + { provide: CollectionDataService, useValue: collectionDataService }, { provide: SubmissionJsonPatchOperationsService, useClass: SubmissionJsonPatchOperationsServiceStub }, { provide: SubmissionService, useClass: SubmissionServiceStub }, { provide: CommunityDataService, useValue: communityDataService }, @@ -284,6 +297,7 @@ describe('SubmissionFormCollectionComponent Component', () => { it('should init collection list properly', () => { communityDataService.findAll.and.returnValue(mockCommunityList); + collectionDataService.getAuthorizedCollectionByCommunity.and.returnValues(mockCommunityCollectionList, mockCommunity2CollectionList); comp.ngOnChanges({ currentCollectionId: new SimpleChange(null, collectionId, true) diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index b576834091..eb7459eaf4 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -35,6 +35,8 @@ import { PaginatedList } from '../../../core/data/paginated-list'; import { SubmissionService } from '../../submission.service'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; +import { CollectionDataService } from '../../../core/data/collection-data.service'; +import { FindAllOptions } from '../../../core/data/request.models'; /** * An interface to represent a collection entry @@ -145,12 +147,14 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { * * @param {ChangeDetectorRef} cdr * @param {CommunityDataService} communityDataService + * @param {CollectionDataService} collectionDataService * @param {JsonPatchOperationsBuilder} operationsBuilder * @param {SubmissionJsonPatchOperationsService} operationsService * @param {SubmissionService} submissionService */ constructor(protected cdr: ChangeDetectorRef, private communityDataService: CommunityDataService, + private collectionDataService: CollectionDataService, private operationsBuilder: JsonPatchOperationsBuilder, private operationsService: SubmissionJsonPatchOperationsService, private submissionService: SubmissionService) { @@ -189,16 +193,19 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { if (hasValue(changes.currentCollectionId) && hasValue(changes.currentCollectionId.currentValue)) { this.selectedCollectionId = this.currentCollectionId; + const findOptions: FindAllOptions = { + elementsPerPage: 100 + }; // @TODO replace with search/top browse endpoint // @TODO implement community/subcommunity hierarchy - const communities$ = this.communityDataService.findAll().pipe( + const communities$ = this.communityDataService.findAll(findOptions).pipe( find((communities: RemoteData>) => isNotEmpty(communities.payload)), mergeMap((communities: RemoteData>) => communities.payload.page)); const listCollection$ = communities$.pipe( flatMap((communityData: Community) => { - return communityData.collections.pipe( + return this.collectionDataService.getAuthorizedCollectionByCommunity(communityData.uuid).pipe( find((collections: RemoteData>) => !collections.isResponsePending && collections.hasSucceeded), mergeMap((collections: RemoteData>) => collections.payload.page), filter((collectionData: Collection) => isNotEmpty(collectionData)), From b665456b9d8bf30c8d559863316127b126225fd7 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 4 Jul 2019 13:03:18 +0200 Subject: [PATCH 020/111] Fixed issues with relation group field --- src/app/shared/chips/models/chips-item.model.ts | 8 +++++++- .../ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts | 7 +++++++ .../relation-group/dynamic-relation-group.components.ts | 4 +++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/app/shared/chips/models/chips-item.model.ts b/src/app/shared/chips/models/chips-item.model.ts index 540f94166f..913232fa71 100644 --- a/src/app/shared/chips/models/chips-item.model.ts +++ b/src/app/shared/chips/models/chips-item.model.ts @@ -2,6 +2,7 @@ import { isObject, uniqueId } from 'lodash'; import { hasValue, isNotEmpty } from '../../empty.util'; import { FormFieldMetadataValueObject } from '../../form/builder/models/form-field-metadata-value.model'; import { ConfidenceType } from '../../../core/integration/models/confidence-type'; +import { PLACEHOLDER_PARENT_METADATA } from '../../form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model'; export interface ChipsItemIcon { metadata: string; @@ -62,7 +63,7 @@ export class ChipsItem { if (this._item.hasOwnProperty(icon.metadata) && (((typeof this._item[icon.metadata] === 'string') && hasValue(this._item[icon.metadata])) || (this._item[icon.metadata] as FormFieldMetadataValueObject).hasValue()) - && !(this._item[icon.metadata] as FormFieldMetadataValueObject).hasPlaceholder()) { + && !this.hasPlaceholder(this._item[icon.metadata])) { if ((icon.visibleWhenAuthorityEmpty || (this._item[icon.metadata] as FormFieldMetadataValueObject).confidence !== ConfidenceType.CF_UNSET) && isNotEmpty(icon.style)) { @@ -109,4 +110,9 @@ export class ChipsItem { this.display = value; } + + private hasPlaceholder(value: any) { + return (typeof value === 'string') ? (value === PLACEHOLDER_PARENT_METADATA) : + (value as FormFieldMetadataValueObject).hasPlaceholder() + } } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts index fc618023f9..66bdf97dad 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts @@ -1,4 +1,7 @@ import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicFormGroupModelConfig, serializable } from '@ng-dynamic-forms/core'; + +import { Subject } from 'rxjs'; + import { isNotEmpty } from '../../../../empty.util'; import { DsDynamicInputModel } from './ds-dynamic-input.model'; import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model'; @@ -16,12 +19,16 @@ export class DynamicConcatModel extends DynamicFormGroupModel { @serializable() separator: string; @serializable() hasLanguages = false; isCustomGroup = true; + valueUpdates: Subject; constructor(config: DynamicConcatModelConfig, layout?: DynamicFormControlLayout) { super(config, layout); this.separator = config.separator + ' '; + + this.valueUpdates = new Subject(); + this.valueUpdates.subscribe((value: string) => this.value = value); } get value() { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.components.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.components.ts index fde8d4b7bf..1485993375 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.components.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.components.ts @@ -130,7 +130,9 @@ export class DsDynamicRelationGroupComponent extends DynamicFormControlComponent ? null : this.selectedChipItem.item[model.name]; if (isNotNull(value)) { - model.valueUpdates.next(this.formBuilderService.isInputModel(model) ? value.value : value); + const nextValue = (this.formBuilderService.isInputModel(model) && (typeof value !== 'string')) ? + value.value : value; + model.valueUpdates.next(nextValue); } }); }); From fe1e4931c32fd6c00e2b120f4be257f24ebec45b Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 20 May 2019 12:53:07 +0200 Subject: [PATCH 021/111] hide upload section on init when is not mandatory and there a no file uploaded in the submission --- .../core/submission/submission-response-parsing.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/core/submission/submission-response-parsing.service.ts b/src/app/core/submission/submission-response-parsing.service.ts index 21135be463..5206227d4e 100644 --- a/src/app/core/submission/submission-response-parsing.service.ts +++ b/src/app/core/submission/submission-response-parsing.service.ts @@ -131,7 +131,10 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService // Iterate over all workspaceitem's sections Object.keys(item.sections) .forEach((sectionId) => { - if (typeof item.sections[sectionId] === 'object' && isNotEmpty(item.sections[sectionId])) { + if (typeof item.sections[sectionId] === 'object' && (isNotEmpty(item.sections[sectionId]) && + // When Upload section is disabled, add to submission only if there are files + (!item.sections[sectionId].hasOwnProperty('files') || isNotEmpty((item.sections[sectionId] as any).files)))) { + const normalizedSectionData = Object.create({}); // Iterate over all sections property Object.keys(item.sections[sectionId]) From 4a1530eea5e31d327b4350299831b9a4d38bbc67 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 15 May 2019 13:15:10 +0200 Subject: [PATCH 022/111] fixed issue with submission footer z-index --- src/styles/_custom_variables.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/_custom_variables.scss b/src/styles/_custom_variables.scss index be03d719c5..b4de04ad9e 100644 --- a/src/styles/_custom_variables.scss +++ b/src/styles/_custom_variables.scss @@ -11,7 +11,7 @@ $drop-zone-area-inner-z-index: 1021; $login-logo-height:72px; $login-logo-width:72px; $submission-header-z-index: 1001; -$submission-footer-z-index: 1000; +$submission-footer-z-index: 999; $main-z-index: 0; $nav-z-index: 10; From 6856f95cb94ca7fdc911a79c9455317656b33389 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 5 Jul 2019 13:10:51 +0200 Subject: [PATCH 023/111] Fixed an issue when submission metadata auto save is triggered on a typeahead field --- .../dynamic-typeahead.component.html | 3 +- .../dynamic-typeahead.component.spec.ts | 47 +++++++++++++++++-- .../typeahead/dynamic-typeahead.component.ts | 30 ++++++++---- 3 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.html index 51e7667200..449481152d 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.html @@ -28,7 +28,8 @@ aria-hidden="true" [authorityValue]="currentValue" (whenClickOnConfidenceNotAccepted)="whenClickOnConfidenceNotAccepted($event)"> - { inputElement.value = 'test value'; inputElement.dispatchEvent(new Event('input')); - expect((typeaheadComp.model as any).value).toEqual(new FormFieldMetadataValueObject('test value')) + expect(typeaheadComp.inputValue).toEqual(new FormFieldMetadataValueObject('test value')) }); @@ -173,19 +173,56 @@ describe('DsDynamicTypeaheadComponent test suite', () => { }); - it('should emit blur Event onBlur', () => { + it('should emit blur Event onBlur when popup is closed', () => { spyOn(typeaheadComp.blur, 'emit'); + spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(false); typeaheadComp.onBlur(new Event('blur')); expect(typeaheadComp.blur.emit).toHaveBeenCalled(); }); - it('should emit change Event onBlur when AuthorityOptions.closed is false', () => { + it('should not emit blur Event onBlur when popup is opened', () => { + spyOn(typeaheadComp.blur, 'emit'); + spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(true); + const input = typeaheadFixture.debugElement.query(By.css('input')); + + input.nativeElement.blur(); + expect(typeaheadComp.blur.emit).not.toHaveBeenCalled(); + }); + + it('should emit change Event onBlur when AuthorityOptions.closed is false and inputValue is changed', () => { typeaheadComp.inputValue = 'test value'; typeaheadFixture.detectChanges(); spyOn(typeaheadComp.blur, 'emit'); spyOn(typeaheadComp.change, 'emit'); - typeaheadComp.onBlur(new Event('blur')); - // expect(typeaheadComp.change.emit).toHaveBeenCalled(); + spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(false); + typeaheadComp.onBlur(new Event('blur', )); + expect(typeaheadComp.change.emit).toHaveBeenCalled(); + expect(typeaheadComp.blur.emit).toHaveBeenCalled(); + }); + + it('should not emit change Event onBlur when AuthorityOptions.closed is false and inputValue is not changed', () => { + typeaheadComp.inputValue = 'test value'; + typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG); + (typeaheadComp.model as any).value = 'test value'; + typeaheadFixture.detectChanges(); + spyOn(typeaheadComp.blur, 'emit'); + spyOn(typeaheadComp.change, 'emit'); + spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(false); + typeaheadComp.onBlur(new Event('blur', )); + expect(typeaheadComp.change.emit).not.toHaveBeenCalled(); + expect(typeaheadComp.blur.emit).toHaveBeenCalled(); + }); + + it('should not emit change Event onBlur when AuthorityOptions.closed is false and inputValue is null', () => { + typeaheadComp.inputValue = null; + typeaheadComp.model = new DynamicTypeaheadModel(TYPEAHEAD_TEST_MODEL_CONFIG); + (typeaheadComp.model as any).value = 'test value'; + typeaheadFixture.detectChanges(); + spyOn(typeaheadComp.blur, 'emit'); + spyOn(typeaheadComp.change, 'emit'); + spyOn(typeaheadComp.instance, 'isPopupOpen').and.returnValue(false); + typeaheadComp.onBlur(new Event('blur', )); + expect(typeaheadComp.change.emit).not.toHaveBeenCalled(); expect(typeaheadComp.blur.emit).toHaveBeenCalled(); }); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.ts index ace6812858..136d1db1c2 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { @@ -8,14 +8,13 @@ import { } from '@ng-dynamic-forms/core'; import { catchError, debounceTime, distinctUntilChanged, filter, map, merge, switchMap, tap } from 'rxjs/operators'; import { Observable, of as observableOf, Subject } from 'rxjs'; -import { NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; +import { NgbTypeahead, NgbTypeaheadSelectItemEvent } from '@ng-bootstrap/ng-bootstrap'; import { AuthorityService } from '../../../../../../core/integration/authority.service'; import { DynamicTypeaheadModel } from './dynamic-typeahead.model'; import { IntegrationSearchOptions } from '../../../../../../core/integration/models/integration-options.model'; -import { isEmpty, isNotEmpty } from '../../../../../empty.util'; +import { isEmpty, isNotEmpty, isNotNull } from '../../../../../empty.util'; import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model'; - import { ConfidenceType } from '../../../../../../core/integration/models/confidence-type'; @Component({ @@ -32,6 +31,8 @@ export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent imp @Output() change: EventEmitter = new EventEmitter(); @Output() focus: EventEmitter = new EventEmitter(); + @ViewChild('instance') instance: NgbTypeahead; + searching = false; searchOptions: IntegrationSearchOptions; searchFailed = false; @@ -105,16 +106,26 @@ export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent imp onInput(event) { if (!this.model.authorityOptions.closed && isNotEmpty(event.target.value)) { this.inputValue = new FormFieldMetadataValueObject(event.target.value); - this.model.valueUpdates.next(this.inputValue); } } onBlur(event: Event) { - if (!this.model.authorityOptions.closed && isNotEmpty(this.inputValue)) { - this.change.emit(this.inputValue); - this.inputValue = null; + if (!this.instance.isPopupOpen()) { + if (!this.model.authorityOptions.closed && isNotEmpty(this.inputValue)) { + if (isNotNull(this.inputValue) && this.model.value !== this.inputValue) { + this.model.valueUpdates.next(this.inputValue); + this.change.emit(this.inputValue); + } + this.inputValue = null; + } + this.blur.emit(event); + } else { + // prevent on blur propagation if typeahed suggestions are showed + event.preventDefault(); + event.stopImmediatePropagation(); + // set focus on input again, this is to avoid to lose changes when no suggestion is selected + (event.target as HTMLInputElement).focus(); } - this.blur.emit(event); } onChange(event: Event) { @@ -141,4 +152,5 @@ export class DsDynamicTypeaheadComponent extends DynamicFormControlComponent imp this.click$.next(this.formatter(this.currentValue)); } } + } From 016afd84e466638213a4a6f5386367db6ba61f24 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 8 Jul 2019 10:40:41 +0200 Subject: [PATCH 024/111] Fixed edit link for workflow --- .../claimed-task/claimed-task-actions.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html index 4b9b93e7e3..3a8cb0cded 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html @@ -2,7 +2,7 @@ {{'submission.workflow.tasks.claimed.edit' | translate}} From 7667cab772fd51ca5ebe8381cf144553bc7a9bd9 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 15 May 2019 17:58:37 +0200 Subject: [PATCH 025/111] Added hint message to form fields --- .../ds-dynamic-form-ui/models/ds-dynamic-input.model.ts | 7 ++----- .../models/ds-dynamic-qualdrop.model.ts | 8 ++++++-- src/app/shared/form/builder/parsers/field-parser.ts | 2 ++ .../shared/form/builder/parsers/onebox-field-parser.ts | 4 ++++ src/app/shared/form/form.component.scss | 5 +++++ 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts index 860c481820..4e4a944319 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts @@ -28,6 +28,7 @@ export class DsDynamicInputModel extends DynamicInputModel { constructor(config: DsDynamicInputModelConfig, layout?: DynamicFormControlLayout) { super(config, layout); + this.hint = config.hint; this.readOnly = config.readOnly; this.value = config.value; this.language = config.language; @@ -57,11 +58,7 @@ export class DsDynamicInputModel extends DynamicInputModel { } get hasLanguages(): boolean { - if (this.languageCodes && this.languageCodes.length > 1) { - return true; - } else { - return false; - } + return this.languageCodes && this.languageCodes.length > 1; } get language(): string { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts index 6bd5a604a0..5d2cbc58b7 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts @@ -1,5 +1,5 @@ -import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicInputModelConfig, serializable } from '@ng-dynamic-forms/core'; -import { DsDynamicInputModel, DsDynamicInputModelConfig } from './ds-dynamic-input.model'; +import { DynamicFormControlLayout, DynamicFormGroupModel, serializable } from '@ng-dynamic-forms/core'; +import { DsDynamicInputModel } from './ds-dynamic-input.model'; import { Subject } from 'rxjs'; import { DynamicFormGroupModelConfig } from '@ng-dynamic-forms/core/src/model/form-group/dynamic-form-group.model'; import { LanguageCode } from '../../models/form-field-language-value.model'; @@ -12,6 +12,7 @@ export interface DsDynamicQualdropModelConfig extends DynamicFormGroupModelConfi languageCodes?: LanguageCode[]; language?: string; readOnly: boolean; + hint?: string; } export class DynamicQualdropModel extends DynamicFormGroupModel { @@ -20,6 +21,7 @@ export class DynamicQualdropModel extends DynamicFormGroupModel { @serializable() languageUpdates: Subject; @serializable() hasLanguages = false; @serializable() readOnly: boolean; + @serializable() hint: string; isCustomGroup = true; constructor(config: DsDynamicQualdropModelConfig, layout?: DynamicFormControlLayout) { @@ -33,6 +35,8 @@ export class DynamicQualdropModel extends DynamicFormGroupModel { this.languageUpdates.subscribe((lang: string) => { this.language = lang; }); + + this.hint = config.hint; } get value() { diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts index 28e3fb8fb5..dd37a45fba 100644 --- a/src/app/shared/form/builder/parsers/field-parser.ts +++ b/src/app/shared/form/builder/parsers/field-parser.ts @@ -190,6 +190,8 @@ export abstract class FieldParser { controlModel.placeholder = this.configData.label; + controlModel.hint = this.configData.hints; + if (this.configData.mandatory && setErrors) { this.markAsRequired(controlModel); } diff --git a/src/app/shared/form/builder/parsers/onebox-field-parser.ts b/src/app/shared/form/builder/parsers/onebox-field-parser.ts index d347f38eee..284656cc95 100644 --- a/src/app/shared/form/builder/parsers/onebox-field-parser.ts +++ b/src/app/shared/form/builder/parsers/onebox-field-parser.ts @@ -24,6 +24,7 @@ export class OneboxFieldParser extends FieldParser { const clsGroup = { element: { control: 'form-row', + hint: 'ds-form-qualdrop-hint' } }; @@ -54,8 +55,10 @@ export class OneboxFieldParser extends FieldParser { inputSelectGroup.id = newId.replace(/\./g, '_') + QUALDROP_GROUP_SUFFIX; inputSelectGroup.group = []; inputSelectGroup.legend = this.configData.label; + inputSelectGroup.hint = this.configData.hints; const selectModelConfig: DynamicSelectModelConfig = this.initModel(newId + QUALDROP_METADATA_SUFFIX, label); + selectModelConfig.hint = null; this.setOptions(selectModelConfig); if (isNotEmpty(fieldValue)) { selectModelConfig.value = fieldValue.metadata; @@ -63,6 +66,7 @@ export class OneboxFieldParser extends FieldParser { inputSelectGroup.group.push(new DynamicSelectModel(selectModelConfig, clsSelect)); const inputModelConfig: DsDynamicInputModelConfig = this.initModel(newId + QUALDROP_VALUE_SUFFIX, label, true); + inputModelConfig.hint = null; this.setValues(inputModelConfig, fieldValue); inputSelectGroup.readOnly = selectModelConfig.disabled && inputModelConfig.readOnly; diff --git a/src/app/shared/form/form.component.scss b/src/app/shared/form/form.component.scss index 1d5e034290..ed10941f09 100644 --- a/src/app/shared/form/form.component.scss +++ b/src/app/shared/form/form.component.scss @@ -44,3 +44,8 @@ .right-addon input { padding-right: $spacer * 2.25; } + +.ds-form-qualdrop-hint { + top: -$spacer; + position: relative; +} From cf73625830af5215b013b97e7366009e6329ffe4 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 11 Jul 2019 19:12:31 +0200 Subject: [PATCH 026/111] Fixed comments --- .../objects/submission-objects.reducer.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/submission/objects/submission-objects.reducer.ts b/src/app/submission/objects/submission-objects.reducer.ts index 1a65783945..8c111dde67 100644 --- a/src/app/submission/objects/submission-objects.reducer.ts +++ b/src/app/submission/objects/submission-objects.reducer.ts @@ -361,7 +361,7 @@ const addError = (state: SubmissionObjectState, action: InertSectionErrorsAction * @param state * the current state * @param action - * an RemoveSectionErrorsAction + * a RemoveSectionErrorsAction * @return SubmissionObjectState * the new state, with the section's errors updated. */ @@ -416,7 +416,7 @@ function initSubmission(state: SubmissionObjectState, action: InitSubmissionForm * @param state * the current state * @param action - * an ResetSubmissionFormAction + * a ResetSubmissionFormAction * @return SubmissionObjectState * the new state, with the section removed. */ @@ -439,7 +439,7 @@ function resetSubmission(state: SubmissionObjectState, action: ResetSubmissionFo * @param state * the current state * @param action - * an CompleteInitSubmissionFormAction + * a CompleteInitSubmissionFormAction * @return SubmissionObjectState * the new state, with the section removed. */ @@ -461,7 +461,7 @@ function completeInit(state: SubmissionObjectState, action: CompleteInitSubmissi * @param state * the current state * @param action - * an SaveSubmissionFormAction | SaveSubmissionSectionFormAction + * a SaveSubmissionFormAction | SaveSubmissionSectionFormAction * | SaveForLaterSubmissionFormAction | SaveAndDepositSubmissionAction * @return SubmissionObjectState * the new state, with the flag set to true. @@ -491,7 +491,7 @@ function saveSubmission(state: SubmissionObjectState, * @param state * the current state * @param action - * an SaveSubmissionFormSuccessAction | SaveForLaterSubmissionFormSuccessAction + * a SaveSubmissionFormSuccessAction | SaveForLaterSubmissionFormSuccessAction * | SaveSubmissionSectionFormSuccessAction | SaveSubmissionFormErrorAction * | SaveForLaterSubmissionFormErrorAction | SaveSubmissionSectionFormErrorAction * @return SubmissionObjectState @@ -521,7 +521,7 @@ function completeSave(state: SubmissionObjectState, * @param state * the current state * @param action - * an DepositSubmissionAction + * a DepositSubmissionAction * @return SubmissionObjectState * the new state, with the deposit flag changed. */ @@ -544,7 +544,7 @@ function startDeposit(state: SubmissionObjectState, action: DepositSubmissionAct * @param state * the current state * @param action - * an DepositSubmissionSuccessAction or DepositSubmissionErrorAction + * a DepositSubmissionSuccessAction or a DepositSubmissionErrorAction * @return SubmissionObjectState * the new state, with the deposit flag changed. */ @@ -586,7 +586,7 @@ function changeCollection(state: SubmissionObjectState, action: ChangeSubmission * @param state * the current state * @param action - * an SetActiveSectionAction + * a SetActiveSectionAction * @return SubmissionObjectState * the new state, with the active section. */ @@ -676,7 +676,7 @@ function updateSectionData(state: SubmissionObjectState, action: UpdateSectionDa * @param state * the current state * @param action - * an DisableSectionAction + * a DisableSectionAction * @param enabled * enabled or disabled section. * @return SubmissionObjectState @@ -705,7 +705,7 @@ function changeSectionState(state: SubmissionObjectState, action: EnableSectionA * @param state * the current state * @param action - * an SectionStatusChangeAction + * a SectionStatusChangeAction * @return SubmissionObjectState * the new state, with the section new validity status. */ @@ -769,7 +769,7 @@ function newFile(state: SubmissionObjectState, action: NewUploadedFileAction): S * @param state * the current state * @param action - * a EditFileDataAction action + * an EditFileDataAction action * @return SubmissionObjectState * the new state, with the edited file. */ From ec105b35a9c937daf1690e30438001cbac04bf3a Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 25 Jul 2019 10:03:49 +0200 Subject: [PATCH 027/111] Disabled search button on lookup field on edit mode --- .../lookup/dynamic-lookup.component.spec.ts | 40 +++++++++++++++++++ .../models/lookup/dynamic-lookup.component.ts | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts index df2252163d..4a1d636adb 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.spec.ts @@ -237,6 +237,12 @@ describe('Dynamic Lookup component', () => { it('should init component properly', () => { expect(lookupComp.firstInputValue).toBe(''); + const de = lookupFixture.debugElement.queryAll(By.css('button')); + const searchBtnEl = de[0].nativeElement; + const editBtnEl = de[1].nativeElement; + expect(searchBtnEl.disabled).toBe(true); + expect(editBtnEl.disabled).toBe(true); + expect(editBtnEl.textContent.trim()).toBe('form.edit'); }); it('should return search results', fakeAsync(() => { @@ -297,6 +303,7 @@ describe('Dynamic Lookup component', () => { expect(lookupComp.model.value).not.toBeDefined(); }); + }); describe('and init model value is not empty', () => { @@ -318,6 +325,19 @@ describe('Dynamic Lookup component', () => { it('should init component properly', () => { expect(lookupComp.firstInputValue).toBe('test'); }); + + it('should have search button disabled on edit mode', () => { + lookupComp.editMode = true; + lookupFixture.detectChanges(); + + const de = lookupFixture.debugElement.queryAll(By.css('button')); + const searchBtnEl = de[0].nativeElement; + const saveBtnEl = de[1].nativeElement; + expect(searchBtnEl.disabled).toBe(true); + expect(saveBtnEl.disabled).toBe(false); + expect(saveBtnEl.textContent.trim()).toBe('form.save'); + + }); }); }); @@ -340,7 +360,14 @@ describe('Dynamic Lookup component', () => { }); it('should render two input element', () => { const de = lookupFixture.debugElement.queryAll(By.css('input.form-control')); + const deBtn = lookupFixture.debugElement.queryAll(By.css('button')); + const searchBtnEl = deBtn[0].nativeElement; + const editBtnEl = deBtn[1].nativeElement; + expect(de.length).toBe(2); + expect(searchBtnEl.disabled).toBe(true); + expect(editBtnEl.disabled).toBe(true); + expect(editBtnEl.textContent.trim()).toBe('form.edit'); }); }); @@ -418,6 +445,19 @@ describe('Dynamic Lookup component', () => { expect(lookupComp.firstInputValue).toBe('Name'); expect(lookupComp.secondInputValue).toBe('Lastname'); }); + + it('should have search button disabled on edit mode', () => { + lookupComp.editMode = true; + lookupFixture.detectChanges(); + + const de = lookupFixture.debugElement.queryAll(By.css('button')); + const searchBtnEl = de[0].nativeElement; + const saveBtnEl = de[1].nativeElement; + expect(searchBtnEl.disabled).toBe(true); + expect(saveBtnEl.disabled).toBe(false); + expect(saveBtnEl.textContent.trim()).toBe('form.save'); + + }); }); }); }); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts index cba352484d..597f39b271 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.ts @@ -159,7 +159,7 @@ export class DsDynamicLookupComponent extends DynamicFormControlComponent implem } public isSearchDisabled() { - return isEmpty(this.firstInputValue); + return isEmpty(this.firstInputValue) || this.editMode; } public onBlurEvent(event: Event) { From 93b415a6bed431b220e2cd4f716beced838127d8 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 25 Jul 2019 10:05:14 +0200 Subject: [PATCH 028/111] Checked there is no pending save when a new save operation is dispatched --- src/app/submission/submission.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/submission/submission.service.ts b/src/app/submission/submission.service.ts index 82185a8eae..f35536d560 100644 --- a/src/app/submission/submission.service.ts +++ b/src/app/submission/submission.service.ts @@ -184,7 +184,11 @@ export class SubmissionService { * The submission id */ dispatchSave(submissionId) { - this.store.dispatch(new SaveSubmissionFormAction(submissionId)); + this.getSubmissionSaveProcessingStatus(submissionId).pipe( + find((isPending: boolean) => !isPending) + ).subscribe(() => { + this.store.dispatch(new SaveSubmissionFormAction(submissionId)); + }) } /** From a2180c19ac2dc0d425d934d46252f911f206041a Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 25 Jul 2019 11:58:21 +0200 Subject: [PATCH 029/111] during submission retrieve surrent collection name by current collection id --- .../submission-form-collection.component.html | 3 +-- ...bmission-form-collection.component.spec.ts | 10 +++++++--- .../submission-form-collection.component.ts | 19 ++++++------------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/app/submission/form/collection/submission-form-collection.component.html b/src/app/submission/form/collection/submission-form-collection.component.html index 37ada35155..2cd1e928f7 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.html +++ b/src/app/submission/form/collection/submission-form-collection.component.html @@ -12,8 +12,7 @@ (click)="onClose()" [disabled]="(disabled$ | async)" ngbDropdownToggle> - - {{ selectedCollectionName$ | async }} + {{ selectedCollectionName$ | async }}
{ }); const collectionDataService: any = jasmine.createSpyObj('collectionDataService', { + findById: jasmine.createSpy('findById'), getAuthorizedCollectionByCommunity: jasmine.createSpy('getAuthorizedCollectionByCommunity') }); @@ -297,6 +301,7 @@ describe('SubmissionFormCollectionComponent Component', () => { it('should init collection list properly', () => { communityDataService.findAll.and.returnValue(mockCommunityList); + collectionDataService.findById.and.returnValue(mockCommunity1Collection1Rd); collectionDataService.getAuthorizedCollectionByCommunity.and.returnValues(mockCommunityCollectionList, mockCommunity2CollectionList); comp.ngOnChanges({ @@ -308,9 +313,8 @@ describe('SubmissionFormCollectionComponent Component', () => { b: mockCollectionList })); - expect(comp.selectedCollectionName$).toBeObservable(cold('(ab|)', { - a: '', - b: 'Community 1-Collection 1' + expect(comp.selectedCollectionName$).toBeObservable(cold('(a|)', { + a: 'Community 1-Collection 1' })); }); diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index eb7459eaf4..9cf01b173c 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -193,6 +193,12 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { if (hasValue(changes.currentCollectionId) && hasValue(changes.currentCollectionId.currentValue)) { this.selectedCollectionId = this.currentCollectionId; + + this.selectedCollectionName$ = this.collectionDataService.findById(this.currentCollectionId).pipe( + find((collectionRD: RemoteData) => isNotEmpty(collectionRD.payload)), + map((collectionRD: RemoteData) => collectionRD.payload.name) + ); + const findOptions: FindAllOptions = { elementsPerPage: 100 }; @@ -219,19 +225,6 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { startWith([]) ); - this.selectedCollectionName$ = communities$.pipe( - flatMap((communityData: Community) => { - return communityData.collections.pipe( - find((collections: RemoteData>) => !collections.isResponsePending && collections.hasSucceeded), - mergeMap((collections: RemoteData>) => collections.payload.page), - filter((collectionData: Collection) => isNotEmpty(collectionData)), - filter((collectionData: Collection) => collectionData.id === this.selectedCollectionId), - map((collectionData: Collection) => collectionData.name) - ); - }), - startWith('') - ); - const searchTerm$ = this.searchField.valueChanges.pipe( debounceTime(200), distinctUntilChanged(), From 49c341b05aa1cff73aeba34a2fb403394e9e4341 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 25 Jul 2019 12:54:23 +0200 Subject: [PATCH 030/111] Fixed concat field hint message --- src/app/shared/form/builder/parsers/concat-field-parser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/form/builder/parsers/concat-field-parser.ts b/src/app/shared/form/builder/parsers/concat-field-parser.ts index 135dda5de3..6323905555 100644 --- a/src/app/shared/form/builder/parsers/concat-field-parser.ts +++ b/src/app/shared/form/builder/parsers/concat-field-parser.ts @@ -47,6 +47,7 @@ export class ConcatFieldParser extends FieldParser { const input1ModelConfig: DynamicInputModelConfig = this.initModel(id + CONCAT_FIRST_INPUT_SUFFIX, label, false, false); const input2ModelConfig: DynamicInputModelConfig = this.initModel(id + CONCAT_SECOND_INPUT_SUFFIX, label, true, false); + input2ModelConfig.hint = ' '; if (this.configData.mandatory) { input1ModelConfig.required = true; From 0ab2ca18809423b5a420399a916f513cb3c1e2e4 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 25 Jul 2019 12:59:16 +0200 Subject: [PATCH 031/111] added variable representing collection change processing status --- .../submission-form-collection.component.html | 5 +++-- .../collection/submission-form-collection.component.ts | 10 ++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/app/submission/form/collection/submission-form-collection.component.html b/src/app/submission/form/collection/submission-form-collection.component.html index 2cd1e928f7..6f4a8a864c 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.html +++ b/src/app/submission/form/collection/submission-form-collection.component.html @@ -10,9 +10,10 @@ class="btn btn-outline-primary" (blur)="onClose()" (click)="onClose()" - [disabled]="(disabled$ | async)" + [disabled]="(disabled$ | async) || (processingChange$ | async)" ngbDropdownToggle> - {{ selectedCollectionName$ | async }} + + {{ selectedCollectionName$ | async }}
(true); + /** + * A boolean representing if a collection change operation is processing + * @type {BehaviorSubject} + */ + public processingChange$ = new BehaviorSubject(false); + /** * The search form control * @type {FormControl} @@ -265,7 +271,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { */ onSelect(event) { this.searchField.reset(); - this.disabled$.next(true); + this.processingChange$.next(true); this.operationsBuilder.replace(this.pathCombiner.getPath(), event.collection.id, true); this.subs.push(this.operationsService.jsonPatchByResourceID( this.submissionService.getSubmissionObjectLinkName(), @@ -277,7 +283,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { this.selectedCollectionName$ = observableOf(event.collection.name); this.collectionChange.emit(submissionObject[0]); this.submissionService.changeSubmissionCollection(this.submissionId, event.collection.id); - this.disabled$.next(false); + this.processingChange$.next(false); this.cdr.detectChanges(); }) ); From 82426700fdaf3c4c9d09137c0a8b3635b8334db5 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 25 Jul 2019 13:00:26 +0200 Subject: [PATCH 032/111] Added getAuthorizedCollection method to CollectionDataService --- src/app/core/data/collection-data.service.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 762838e9ae..643eb55404 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -41,11 +41,26 @@ export class CollectionDataService extends ComColDataService { super(); } + /** + * Get all collections whom user has authorization to submit + * + * @return Observable>> + * collection list + */ + getAuthorizedCollection(): Observable>> { + const searchHref = 'findAuthorized'; + const options = new FindAllOptions(); + options.elementsPerPage = 1000; + + return this.searchBy(searchHref, options).pipe( + filter((collections: RemoteData>) => !collections.isResponsePending)); + } + /** * Get all collections whom user has authorization to submit to by community * - * @return boolean - * true if the user has at least one collection to submit to + * @return Observable>> + * collection list */ getAuthorizedCollectionByCommunity(communityId): Observable>> { const searchHref = 'findAuthorizedByCommunity'; From fa6fe668c32f45905b52c8ce3ca510ad61216f41 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 25 Jul 2019 14:41:47 +0200 Subject: [PATCH 033/111] solved issues with tests --- .../search-fixed-filter.service.spec.ts | 16 ++-- .../search-label.component.spec.ts | 87 +++++++++++++++++++ .../search-labels.component.spec.ts | 55 ++++-------- .../search-page.component.spec.ts | 4 +- 4 files changed, 111 insertions(+), 51 deletions(-) create mode 100644 src/app/+search-page/search-labels/search-label/search-label.component.spec.ts diff --git a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts index a201d37d48..37a386d51a 100644 --- a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.spec.ts @@ -1,17 +1,15 @@ -import { SearchFixedFilterService } from './search-fixed-filter.service'; -import { RouteService } from '../../../core/services/route.service'; -import { RequestService } from '../../../core/data/request.service'; -import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; -import { of as observableOf } from 'rxjs'; -import { RequestEntry } from '../../../core/data/request.reducer'; -import { FilteredDiscoveryQueryResponse, RestResponse } from '../../../core/cache/response.models'; +import {SearchFixedFilterService} from './search-fixed-filter.service'; +import {RequestService} from '../../../core/data/request.service'; +import {HALEndpointService} from '../../../core/shared/hal-endpoint.service'; +import {of as observableOf} from 'rxjs'; +import {RequestEntry} from '../../../core/data/request.reducer'; +import {FilteredDiscoveryQueryResponse} from '../../../core/cache/response.models'; describe('SearchFixedFilterService', () => { let service: SearchFixedFilterService; const filterQuery = 'filter:query'; - const routeServiceStub = {} as RouteService; const requestServiceStub = Object.assign({ /* tslint:disable:no-empty */ configure: () => {}, @@ -26,7 +24,7 @@ describe('SearchFixedFilterService', () => { }); beforeEach(() => { - service = new SearchFixedFilterService(routeServiceStub, requestServiceStub, halServiceStub); + service = new SearchFixedFilterService(requestServiceStub, halServiceStub); }); describe('when getQueryByFilterName is called with a filterName', () => { diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts b/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts new file mode 100644 index 0000000000..329c286ce3 --- /dev/null +++ b/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts @@ -0,0 +1,87 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TranslateModule } from '@ngx-translate/core'; +import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { Observable, of as observableOf } from 'rxjs'; +import { Params } from '@angular/router'; +import {SearchLabelComponent} from './search-label.component'; +import {ObjectKeysPipe} from '../../../shared/utils/object-keys-pipe'; +import {SearchService} from '../../search-service/search.service'; +import {SEARCH_CONFIG_SERVICE} from '../../../+my-dspace-page/my-dspace-page.component'; +import {SearchServiceStub} from '../../../shared/testing/search-service-stub'; +import {SearchConfigurationServiceStub} from '../../../shared/testing/search-configuration-service-stub'; + +describe('SearchLabelComponent', () => { + let comp: SearchLabelComponent; + let fixture: ComponentFixture; + + const searchLink = '/search'; + let searchService; + + const key1 = 'author'; + const key2 = 'subject'; + const value1 = 'Test, Author'; + const normValue1 = 'Test, Author'; + const value2 = 'TestSubject'; + const value3 = 'Test, Authority,authority'; + const normValue3 = 'Test, Authority'; + const filter1 = [key1, value1]; + const filter2 = [key2, value2]; + const mockFilters = [ + filter1, + filter2 + ]; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], + declarations: [SearchLabelComponent, ObjectKeysPipe], + providers: [ + { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, + { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() } + // { provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} } + ], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(SearchLabelComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchLabelComponent); + comp = fixture.componentInstance; + searchService = (comp as any).searchService; + comp.key = key1; + comp.value = value1; + (comp as any).appliedFilters = observableOf(mockFilters); + fixture.detectChanges(); + }); + + describe('when getRemoveParams is called', () => { + let obs: Observable; + + beforeEach(() => { + obs = comp.getRemoveParams(); + }); + + it('should return all params but the provided filter', () => { + obs.subscribe((params) => { + // Should contain only filter2 and page: length == 2 + expect(Object.keys(params).length).toBe(2); + }); + }) + }); + + describe('when normalizeFilterValue is called', () => { + it('should return properly filter value', () => { + let result: string; + + result = comp.normalizeFilterValue(value1); + expect(result).toBe(normValue1); + + result = comp.normalizeFilterValue(value3); + expect(result).toBe(normValue3); + }) + }); +}); diff --git a/src/app/+search-page/search-labels/search-labels.component.spec.ts b/src/app/+search-page/search-labels/search-labels.component.spec.ts index d28698764c..5a554e42d6 100644 --- a/src/app/+search-page/search-labels/search-labels.component.spec.ts +++ b/src/app/+search-page/search-labels/search-labels.component.spec.ts @@ -1,16 +1,14 @@ -import { SearchLabelsComponent } from './search-labels.component'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { TranslateModule } from '@ngx-translate/core'; -import { SearchService } from '../search-service/search.service'; -import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { FormsModule } from '@angular/forms'; -import { SearchServiceStub } from '../../shared/testing/search-service-stub'; -import { Observable, of as observableOf } from 'rxjs'; -import { Params } from '@angular/router'; -import { ObjectKeysPipe } from '../../shared/utils/object-keys-pipe'; -import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; -import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service-stub'; +import {SearchLabelsComponent} from './search-labels.component'; +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {NoopAnimationsModule} from '@angular/platform-browser/animations'; +import {TranslateModule} from '@ngx-translate/core'; +import {SearchService} from '../search-service/search.service'; +import {ChangeDetectionStrategy, NO_ERRORS_SCHEMA} from '@angular/core'; +import {FormsModule} from '@angular/forms'; +import {SearchServiceStub} from '../../shared/testing/search-service-stub'; +import {of as observableOf} from 'rxjs'; +import {ObjectKeysPipe} from '../../shared/utils/object-keys-pipe'; +import {SEARCH_CONFIG_SERVICE} from '../../+my-dspace-page/my-dspace-page.component'; describe('SearchLabelsComponent', () => { let comp: SearchLabelsComponent; @@ -22,10 +20,7 @@ describe('SearchLabelsComponent', () => { const field1 = 'author'; const field2 = 'subject'; const value1 = 'Test, Author'; - const normValue1 = 'Test, Author'; const value2 = 'TestSubject'; - const value3 = 'Test, Authority,authority'; - const normValue3 = 'Test, Authority'; const filter1 = [field1, value1]; const filter2 = [field2, value2]; const mockFilters = [ @@ -39,8 +34,7 @@ describe('SearchLabelsComponent', () => { declarations: [SearchLabelsComponent, ObjectKeysPipe], providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, - { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() } - // { provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} } + { provide: SEARCH_CONFIG_SERVICE, useValue: {getCurrentFrontendFilters : () => observableOf(mockFilters)} } ], schemas: [NO_ERRORS_SCHEMA] }).overrideComponent(SearchLabelsComponent, { @@ -56,30 +50,11 @@ describe('SearchLabelsComponent', () => { fixture.detectChanges(); }); - describe('when getRemoveParams is called', () => { - let obs: Observable; - - beforeEach(() => { - obs = comp.getRemoveParams(filter1[0], filter1[1]); - }); - + describe('when the component has been initialized', () => { it('should return all params but the provided filter', () => { - obs.subscribe((params) => { - // Should contain only filter2 and page: length == 2 - expect(Object.keys(params).length).toBe(2); + comp.appliedFilters.subscribe((filters) => { + expect(filters).toBe(mockFilters); }); }) }); - - describe('when normalizeFilterValue is called', () => { - it('should return properly filter value', () => { - let result: string; - - result = comp.normalizeFilterValue(value1); - expect(result).toBe(normValue1); - - result = comp.normalizeFilterValue(value3); - expect(result).toBe(normValue3); - }) - }); }); diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts index 2bc3d4071d..9ed5bb8590 100644 --- a/src/app/+search-page/search-page.component.spec.ts +++ b/src/app/+search-page/search-page.component.spec.ts @@ -200,7 +200,7 @@ describe('SearchPageComponent', () => { beforeEach(() => { menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement; - comp.isSidebarCollapsed = () => observableOf(true); + (comp as any).isSidebarCollapsed$ = observableOf(true); fixture.detectChanges(); }); @@ -215,7 +215,7 @@ describe('SearchPageComponent', () => { beforeEach(() => { menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement; - comp.isSidebarCollapsed = () => observableOf(false); + (comp as any).isSidebarCollapsed$ = observableOf(false); fixture.detectChanges(); }); From d335ab71af0dd8828764dcc5dcf9b82593d4017e Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 25 Jul 2019 15:23:29 +0200 Subject: [PATCH 034/111] added some typedoc --- .../search-facet-option/search-facet-option.component.ts | 3 +++ .../search-facet-range-option.component.ts | 3 +++ .../search-facet-selected-option.component.ts | 3 +++ .../search-filter/search-fixed-filter.service.ts | 2 +- .../search-filters/search-filters.component.ts | 3 +++ .../search-labels/search-label/search-label.component.ts | 2 +- src/app/+search-page/search-page.component.ts | 7 +++++++ 7 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts index 1488f7a1e1..eae9a8cc7f 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.ts @@ -50,6 +50,9 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy { */ addQueryParams; + /** + * Link to the search page + */ searchLink: string; /** * Subscription to unsubscribe from on destroy diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts index 72a7c98852..39b24b36fd 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.ts @@ -56,6 +56,9 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy { */ sub: Subscription; + /** + * Link to the search page + */ searchLink: string; constructor(protected searchService: SearchService, diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts index 123a32dfb4..3f84b7c4e5 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.ts @@ -49,6 +49,9 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy { */ sub: Subscription; + /** + * Link to the search page + */ searchLink: string; constructor(protected searchService: SearchService, diff --git a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts index 85f637ce32..1e99ca9bd5 100644 --- a/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-fixed-filter.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { flatMap, map, switchMap, tap } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import { Observable, of as observableOf } from 'rxjs'; import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; import { GetRequest, RestRequest } from '../../../core/data/request.models'; diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts index ba63d143c6..9d0dfceb15 100644 --- a/src/app/+search-page/search-filters/search-filters.component.ts +++ b/src/app/+search-page/search-filters/search-filters.component.ts @@ -37,6 +37,9 @@ export class SearchFiltersComponent implements OnInit { */ @Input() inPlaceSearch; + /** + * Link to the search page + */ searchLink: string; /** diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.ts b/src/app/+search-page/search-labels/search-label/search-label.component.ts index ab58e1bf4e..2f44f91a35 100644 --- a/src/app/+search-page/search-labels/search-label/search-label.component.ts +++ b/src/app/+search-page/search-labels/search-label/search-label.component.ts @@ -11,7 +11,7 @@ import { hasValue, isNotEmpty } from '../../../shared/empty.util'; }) /** - * Component that represents the labels containing the currently active filters + * Component that represents the label containing the currently active filters */ export class SearchLabelComponent implements OnInit { @Input() key: string; diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 8fe38eaebb..6493ec6623 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -91,7 +91,14 @@ export class SearchPageComponent implements OnInit { @Input() fixedFilter$: Observable; + /** + * Link to the search page + */ searchLink: string; + + /** + * Observable for whether or not the sidebar is currently collapsed + */ isSidebarCollapsed$: Observable; constructor(protected service: SearchService, From 2ff1b88bac89f40c6cb6eeee25944490a39e5929 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 25 Jul 2019 19:14:57 +0200 Subject: [PATCH 035/111] Fixed issue with collection change --- .../submission/sections/upload/section-upload.component.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/submission/sections/upload/section-upload.component.ts b/src/app/submission/sections/upload/section-upload.component.ts index 826385af45..9dbd1079f4 100644 --- a/src/app/submission/sections/upload/section-upload.component.ts +++ b/src/app/submission/sections/upload/section-upload.component.ts @@ -155,14 +155,14 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent { filter((submissionObject: SubmissionObjectEntry) => isUndefined(this.collectionId) || this.collectionId !== submissionObject.collection), tap((submissionObject: SubmissionObjectEntry) => this.collectionId = submissionObject.collection), flatMap((submissionObject: SubmissionObjectEntry) => this.collectionDataService.findById(submissionObject.collection)), - find((rd: RemoteData) => isNotUndefined((rd.payload))), + filter((rd: RemoteData) => isNotUndefined((rd.payload))), tap((collectionRemoteData: RemoteData) => this.collectionName = collectionRemoteData.payload.name), flatMap((collectionRemoteData: RemoteData) => { return this.resourcePolicyService.findByHref( (collectionRemoteData.payload as any)._links.defaultAccessConditions ); }), - find((defaultAccessConditionsRemoteData: RemoteData) => + filter((defaultAccessConditionsRemoteData: RemoteData) => defaultAccessConditionsRemoteData.hasSucceeded), tap((defaultAccessConditionsRemoteData: RemoteData) => { if (isNotEmpty(defaultAccessConditionsRemoteData.payload)) { @@ -171,7 +171,6 @@ export class SubmissionSectionUploadComponent extends SectionModelComponent { } }), flatMap(() => config$), - take(1), flatMap((config: SubmissionUploadsModel) => { this.availableAccessConditionOptions = isNotEmpty(config.accessConditionOptions) ? config.accessConditionOptions : []; From 50024c8c559216053fc8323c91a4a2b8a816ae4e Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 25 Jul 2019 19:16:08 +0200 Subject: [PATCH 036/111] During submission retrieve collection list only at first load --- .../submission-form-collection.component.ts | 71 ++++++++++--------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index 5a0e86103c..1fbccb6958 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -209,43 +209,46 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { elementsPerPage: 100 }; - // @TODO replace with search/top browse endpoint - // @TODO implement community/subcommunity hierarchy - const communities$ = this.communityDataService.findAll(findOptions).pipe( - find((communities: RemoteData>) => isNotEmpty(communities.payload)), - mergeMap((communities: RemoteData>) => communities.payload.page)); + // Retrieve collection list only when is the first change + if (changes.currentCollectionId.isFirstChange()) { + // @TODO replace with search/top browse endpoint + // @TODO implement community/subcommunity hierarchy + const communities$ = this.communityDataService.findAll(findOptions).pipe( + find((communities: RemoteData>) => isNotEmpty(communities.payload)), + mergeMap((communities: RemoteData>) => communities.payload.page)); - const listCollection$ = communities$.pipe( - flatMap((communityData: Community) => { - return this.collectionDataService.getAuthorizedCollectionByCommunity(communityData.uuid).pipe( - find((collections: RemoteData>) => !collections.isResponsePending && collections.hasSucceeded), - mergeMap((collections: RemoteData>) => collections.payload.page), - filter((collectionData: Collection) => isNotEmpty(collectionData)), - map((collectionData: Collection) => ({ - communities: [{ id: communityData.id, name: communityData.name }], - collection: { id: collectionData.id, name: collectionData.name } - })) - ); - }), - reduce((acc: any, value: any) => [...acc, ...value], []), - startWith([]) - ); + const listCollection$ = communities$.pipe( + flatMap((communityData: Community) => { + return this.collectionDataService.getAuthorizedCollectionByCommunity(communityData.uuid).pipe( + find((collections: RemoteData>) => !collections.isResponsePending && collections.hasSucceeded), + mergeMap((collections: RemoteData>) => collections.payload.page), + filter((collectionData: Collection) => isNotEmpty(collectionData)), + map((collectionData: Collection) => ({ + communities: [{ id: communityData.id, name: communityData.name }], + collection: { id: collectionData.id, name: collectionData.name } + })) + ); + }), + reduce((acc: any, value: any) => [...acc, ...value], []), + startWith([]) + ); - const searchTerm$ = this.searchField.valueChanges.pipe( - debounceTime(200), - distinctUntilChanged(), - startWith('') - ); + const searchTerm$ = this.searchField.valueChanges.pipe( + debounceTime(200), + distinctUntilChanged(), + startWith('') + ); - this.searchListCollection$ = combineLatest(searchTerm$, listCollection$).pipe( - map(([searchTerm, listCollection]) => { - this.disabled$.next(isEmpty(listCollection)); - if (isEmpty(searchTerm)) { - return listCollection; - } else { - return listCollection.filter((v) => v.collection.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1).slice(0, 5); - } - })); + this.searchListCollection$ = combineLatest(searchTerm$, listCollection$).pipe( + map(([searchTerm, listCollection]) => { + this.disabled$.next(isEmpty(listCollection)); + if (isEmpty(searchTerm)) { + return listCollection; + } else { + return listCollection.filter((v) => v.collection.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1).slice(0, 5); + } + })); + } } } From 3fe67b8da1a01d0688d07f1b7b53ab41b7312c38 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 25 Jul 2019 19:18:11 +0200 Subject: [PATCH 037/111] changed elementsPerPage on CollectionDataService --- src/app/core/data/collection-data.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 643eb55404..626ddf233d 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -42,7 +42,7 @@ export class CollectionDataService extends ComColDataService { } /** - * Get all collections whom user has authorization to submit + * Get all collections the user is authorized to submit to * * @return Observable>> * collection list @@ -50,14 +50,14 @@ export class CollectionDataService extends ComColDataService { getAuthorizedCollection(): Observable>> { const searchHref = 'findAuthorized'; const options = new FindAllOptions(); - options.elementsPerPage = 1000; + options.elementsPerPage = 100; return this.searchBy(searchHref, options).pipe( filter((collections: RemoteData>) => !collections.isResponsePending)); } /** - * Get all collections whom user has authorization to submit to by community + * Get all collections the user is authorized to submit to, by community * * @return Observable>> * collection list @@ -65,7 +65,7 @@ export class CollectionDataService extends ComColDataService { getAuthorizedCollectionByCommunity(communityId): Observable>> { const searchHref = 'findAuthorizedByCommunity'; const options = new FindAllOptions(); - options.elementsPerPage = 1000; + options.elementsPerPage = 100; options.searchParams = [new SearchParam('uuid', communityId)]; return this.searchBy(searchHref, options).pipe( From 45ab326355d48d7b1cc1e204920585f309b3b198 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 26 Jul 2019 15:29:00 +0200 Subject: [PATCH 038/111] Added FindAllOptions param to getAuthorizedCollection and getAuthorizedCollectionByCommunity --- src/app/core/data/collection-data.service.ts | 11 +++++------ .../submission-form-collection.component.ts | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 626ddf233d..04e483604c 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -44,13 +44,12 @@ export class CollectionDataService extends ComColDataService { /** * Get all collections the user is authorized to submit to * + * @param options The [[FindAllOptions]] object * @return Observable>> * collection list */ - getAuthorizedCollection(): Observable>> { + getAuthorizedCollection(options: FindAllOptions = {}): Observable>> { const searchHref = 'findAuthorized'; - const options = new FindAllOptions(); - options.elementsPerPage = 100; return this.searchBy(searchHref, options).pipe( filter((collections: RemoteData>) => !collections.isResponsePending)); @@ -59,13 +58,13 @@ export class CollectionDataService extends ComColDataService { /** * Get all collections the user is authorized to submit to, by community * + * @param communityId The community id + * @param options The [[FindAllOptions]] object * @return Observable>> * collection list */ - getAuthorizedCollectionByCommunity(communityId): Observable>> { + getAuthorizedCollectionByCommunity(communityId: string, options: FindAllOptions = {}): Observable>> { const searchHref = 'findAuthorizedByCommunity'; - const options = new FindAllOptions(); - options.elementsPerPage = 100; options.searchParams = [new SearchParam('uuid', communityId)]; return this.searchBy(searchHref, options).pipe( diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index 1fbccb6958..79d2f2a7bc 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -206,7 +206,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { ); const findOptions: FindAllOptions = { - elementsPerPage: 100 + elementsPerPage: 1000 }; // Retrieve collection list only when is the first change @@ -219,7 +219,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { const listCollection$ = communities$.pipe( flatMap((communityData: Community) => { - return this.collectionDataService.getAuthorizedCollectionByCommunity(communityData.uuid).pipe( + return this.collectionDataService.getAuthorizedCollectionByCommunity(communityData.uuid, findOptions).pipe( find((collections: RemoteData>) => !collections.isResponsePending && collections.hasSucceeded), mergeMap((collections: RemoteData>) => collections.payload.page), filter((collectionData: Collection) => isNotEmpty(collectionData)), From 359cb10fa34a26a0bdd6d2d298dc860a659eda62 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 26 Jul 2019 17:21:57 +0200 Subject: [PATCH 039/111] add json5 support --- package.json | 2 + resources/i18n/{cs.json => cs.json5} | 31 ++++++- resources/i18n/{de.json => de.json5} | 30 ++++++- resources/i18n/{en.json => en.json5} | 80 +++++++++++++++++-- resources/i18n/{nl.json => nl.json5} | 30 ++++++- src/modules/app/browser-app.module.ts | 5 +- src/modules/app/server-app.module.ts | 4 +- .../translate-json5-http.loader.ts | 15 ++++ .../translate-json5-universal.loader.ts} | 5 +- yarn.lock | 12 +++ 10 files changed, 196 insertions(+), 18 deletions(-) rename resources/i18n/{cs.json => cs.json5} (99%) rename resources/i18n/{de.json => de.json5} (99%) rename resources/i18n/{en.json => en.json5} (99%) rename resources/i18n/{nl.json => nl.json5} (99%) create mode 100644 src/ngx-translate-loaders/translate-json5-http.loader.ts rename src/{modules/translate-universal-loader.ts => ngx-translate-loaders/translate-json5-universal.loader.ts} (65%) diff --git a/package.json b/package.json index 5ec7b2d694..d53a36dac3 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "https": "1.0.0", "js-cookie": "2.2.0", "js.clone": "0.0.3", + "json5": "^2.1.0", "jsonschema": "1.2.2", "jwt-decode": "^2.2.0", "methods": "1.1.2", @@ -152,6 +153,7 @@ "@types/hammerjs": "2.0.35", "@types/jasmine": "^2.8.6", "@types/js-cookie": "2.1.0", + "@types/json5": "^0.0.30", "@types/lodash": "^4.14.110", "@types/memory-cache": "0.2.0", "@types/mime": "2.0.0", diff --git a/resources/i18n/cs.json b/resources/i18n/cs.json5 similarity index 99% rename from resources/i18n/cs.json rename to resources/i18n/cs.json5 index d658030b6b..fea79b3667 100644 --- a/resources/i18n/cs.json +++ b/resources/i18n/cs.json5 @@ -2,6 +2,7 @@ "404.help": "Nepodařilo se najít stránku, kterou hledáte. Je možné, že stránka byla přesunuta nebo smazána. Pomocí tlačítka níže můžete přejít na domovskou stránku. ", "404.link.home-page": "Přejít na domovskou stránku", "404.page-not-found": "stránka nenalezena", + "admin.registries.bitstream-formats.description": "Tento seznam formátů souborů poskytuje informace o známých formátech a o úrovni jejich podpory.", "admin.registries.bitstream-formats.formats.no-items": "Žádné formáty souborů.", "admin.registries.bitstream-formats.formats.table.internal": "interní", @@ -13,6 +14,7 @@ "admin.registries.bitstream-formats.formats.table.supportLevel.head": "Úroveň podpory", "admin.registries.bitstream-formats.head": "Registr formátů souborů", "admin.registries.bitstream-formats.title": "DSpace Angular :: Registr formátů souborů", + "admin.registries.metadata.description": "Registr metadat je seznam všech metadatových polí dostupných v repozitáři. Tyto pole mohou být rozdělena do více schémat. DSpace však vyžaduje použití schématu kvalifikový Dublin Core.", "admin.registries.metadata.head": "Registr metadat", "admin.registries.metadata.schemas.no-items": "Žádná schémata metadat.", @@ -20,6 +22,7 @@ "admin.registries.metadata.schemas.table.name": "Název", "admin.registries.metadata.schemas.table.namespace": "Jmenný prostor", "admin.registries.metadata.title": "DSpace Angular :: Registr metadat", + "admin.registries.schema.description": "Toto je schéma metadat pro „{{namespace}}“.", "admin.registries.schema.fields.head": "Pole schématu metadat", "admin.registries.schema.fields.no-items": "Žádná metadatová pole.", @@ -27,15 +30,20 @@ "admin.registries.schema.fields.table.scopenote": "Poznámka o rozsahu", "admin.registries.schema.head": "Metadata Schema", "admin.registries.schema.title": "DSpace Angular :: Registr schémat metadat", + "auth.errors.invalid-user": "Neplatná e-mailová adresa nebo heslo.", "auth.messages.expired": "Vaše relace vypršela. Prosím, znova se přihlaste.", + "browse.title": "Prohlížíte {{ collection }} dle {{ field }} {{ value }}", + "collection.page.browse.recent.head": "Poslední příspěvky", "collection.page.license": "Licence", "collection.page.news": "Novinky", + "community.page.license": "Licence", "community.page.news": "Novinky", "community.sub-collection-list.head": "Kolekce v této komunitě", + "error.browse-by": "Chyba během stahování záznamů", "error.collection": "Chyba během stahování kolekce", "error.community": "Chyba během stahování komunity", @@ -48,9 +56,11 @@ "error.top-level-communities": "Chyba během stahování komunit nejvyšší úrovně", "error.validation.license.notgranted": "Pro dokončení zaslání Musíte udělit licenci. Pokud v tuto chvíli tuto licenci nemůžete udělit, můžete svou práci uložit a později se k svému příspěveku vrátit nebo jej smazat.", "error.validation.pattern": "Tento vstup je omezen dle vzoru: {{ pattern }}.", + "footer.copyright": "copyright © 2002-{{ year }}", "footer.link.dspace": "software DSpace", "footer.link.duraspace": "DuraSpace", + "form.cancel": "Zrušit", "form.first-name": "Křestní jméno", "form.group-collapse": "Sbalit", @@ -64,10 +74,12 @@ "form.remove": "Smazat", "form.search": "Hledat", "form.submit": "Odeslat", + "home.description": "", "home.title": "DSpace Angular :: Domů", "home.top-level-communities.head": "Komunity v DSpace", "home.top-level-communities.help": "Vybráním komunity můžete prohlížet její kolekce.", + "item.page.abstract": "Abstract", "item.page.author": "Autor", "item.page.collections": "Kolekce", @@ -81,6 +93,7 @@ "item.page.link.full": "Úplný záznam", "item.page.link.simple": "Minimální záznam", "item.page.uri": "URI", + "loading.browse-by": "Načítají se záznamy...", "loading.collection": "Načítá se kolekce...", "loading.community": "Načítá se komunita...", @@ -91,6 +104,7 @@ "loading.search-results": "Načítají se výsledky hledání...", "loading.sub-collections": "Načítají se subkolekce...", "loading.top-level-communities": "Načítají se komunity nejvyšší úrovně...", + "login.form.email": "E-mailová adresa", "login.form.forgot-password": "Zapomněli jste své heslo?", "login.form.header": "Prosím, přihlaste se do DSpace", @@ -98,22 +112,29 @@ "login.form.password": "Heslo", "login.form.submit": "Přihlásit se", "login.title": "Přihlásit se", + "logout.form.header": "Odhlásit se z DSpace", "logout.form.submit": "Odhlásit se", "logout.title": "Odhlásit se", + "nav.home": "Domů", "nav.login": "Přihlásit se", "nav.logout": "Odhlásit se", + "pagination.results-per-page": "Výsledků na stránku", "pagination.showing.detail": "{{ range }} z {{ total }}", "pagination.showing.label": "Zobrazují se záznamy ", "pagination.sort-direction": "Seřazení", + "search.description": "", + "search.title": "DSpace Angular :: Hledat", + "search.filters.applied.f.author": "Autor", "search.filters.applied.f.dateIssued.max": "Do data", "search.filters.applied.f.dateIssued.min": "Od data", "search.filters.applied.f.has_content_in_original_bundle": "Má soubory", "search.filters.applied.f.subject": "Předmět", + "search.filters.filter.author.head": "Autor", "search.filters.filter.author.placeholder": "Jméno autora", "search.filters.filter.dateIssued.head": "Datum", @@ -126,12 +147,16 @@ "search.filters.filter.show-more": "Zobrazit více", "search.filters.filter.subject.head": "Předmět", "search.filters.filter.subject.placeholder": "Předmět", + "search.filters.head": "Filtry", "search.filters.reset": "Obnovit filtry", + "search.form.search": "Hledat", "search.form.search_dspace": "Hledat v DSpace", + "search.results.head": "Výsledky hledání", "search.results.no-results": "Nebyli nalezeny žádné výsledky", + "search.sidebar.close": "Zpět na výsledky", "search.sidebar.filters.title": "Filtry", "search.sidebar.open": "Vyhledávací nástroje", @@ -139,11 +164,13 @@ "search.sidebar.settings.rpp": "Výsledků na stránku", "search.sidebar.settings.sort-by": "Řadit dle", "search.sidebar.settings.title": "Nastavení", - "search.title": "DSpace Angular :: Hledat", + "search.view-switch.show-grid": "Zobrazit mřížku", "search.view-switch.show-list": "Zobrazit seznam", + "sorting.dc.title.ASC": "Název vzestupně", "sorting.dc.title.DESC": "Název sestupně", "sorting.score.DESC": "Relevance", - "title": "DSpace" + + "title": "DSpace", } diff --git a/resources/i18n/de.json b/resources/i18n/de.json5 similarity index 99% rename from resources/i18n/de.json rename to resources/i18n/de.json5 index d184e7d091..29e3073592 100644 --- a/resources/i18n/de.json +++ b/resources/i18n/de.json5 @@ -2,6 +2,7 @@ "404.help": "Die Seite, die Sie aufrufen wollten, konnte nicht gefunden werden. Sie könnte verschoben oder gelöscht worden sein. Mit dem Link unten kommen Sie zurück zur Startseite. ", "404.link.home-page": "Zurück zur Startseite", "404.page-not-found": "Seite nicht gefunden", + "admin.registries.bitstream-formats.description": "Diese Liste enhtält die in diesem Repositorium zulässigen Dateiformate und den jeweiligen Unterstützungsgrad.", "admin.registries.bitstream-formats.formats.no-items": "Es gibt keine Formate in dieser Referenzliste.", "admin.registries.bitstream-formats.formats.table.internal": "intern", @@ -13,6 +14,7 @@ "admin.registries.bitstream-formats.formats.table.supportLevel.head": "Unterstützungsgrad", "admin.registries.bitstream-formats.head": "Referenzliste der Dateiformate", "admin.registries.bitstream-formats.title": "DSpace Angular :: Referenzliste der Dateiformate", + "admin.registries.metadata.description": "Die Metadatenreferenzliste beinhaltet alle Metadatenfelder, die zur Verfügung stehen. Die Felder können in unterschiedlichen Schemata enthalten sein. Nichtsdestotrotz benötigt DSpace mindestens qualifiziertes Dublin Core.", "admin.registries.metadata.head": "Metadatenreferenzliste", "admin.registries.metadata.schemas.no-items": "Es gbit keine Metadatenschemata.", @@ -20,6 +22,7 @@ "admin.registries.metadata.schemas.table.name": "Name", "admin.registries.metadata.schemas.table.namespace": "Namensraum", "admin.registries.metadata.title": "DSpace Angular :: Metadatenreferenzliste", + "admin.registries.schema.description": "Dies ist das Metadatenschema für \"{{namespace}}\".", "admin.registries.schema.fields.head": "Felder in diesem Schema", "admin.registries.schema.fields.no-items": "Es gibt keine Felder in diesem Schema.", @@ -27,15 +30,19 @@ "admin.registries.schema.fields.table.scopenote": "Gültigkeitsbereich", "admin.registries.schema.head": "Metadatenschemata", "admin.registries.schema.title": "DSpace Angular :: Referenzliste der Metadatenschemata", + "auth.errors.invalid-user": "Ungültige E-Mail-Adresse oder Passwort.", "auth.messages.expired": "Ihre Sitzung ist abgelaufen, bitte melden Sie sich erneut an.", + "browse.title": "Anzeige {{ collection }} nach {{ field }} {{ value }}", "collection.page.browse.recent.head": "Aktuellste Veröffentlichungen", "collection.page.license": "Lizenz", "collection.page.news": "Neuigkeiten", + "community.page.license": "Lizenz", "community.page.news": "Neuigkeiten", "community.sub-collection-list.head": "Sammlungen in diesem Bereich", + "error.browse-by": "Fehler beim Laden der Ressourcen", "error.collection": "Fehler beim Laden der Sammlung.", "error.community": "Fehler beim Laden des Bereiches.", @@ -48,9 +55,11 @@ "error.top-level-communities": "Fehler beim Laden der Hauptbereiche.", "error.validation.license.notgranted": "Sie müssen der Lizenz zustimmen, um die Ressource einzureichen. Wenn dies zur Zeit nicht geht, können Sie die Einreichung speichern und später wiederaufnehmen oder löschen.", "error.validation.pattern": "Die Eingabe kann nur folgendes Muster haben: {{ pattern }}.", + "footer.copyright": "Copyright © 2002-{{ year }}", "footer.link.dspace": "DSpace Software", "footer.link.duraspace": "DuraSpace", + "form.cancel": "Abbrechen", "form.first-name": "Vorname", "form.group-collapse": "Weniger", @@ -64,10 +73,12 @@ "form.remove": "Löschen", "form.search": "Suchen", "form.submit": "Los", + "home.description": "", "home.title": "DSpace Angular :: Startseite", "home.top-level-communities.head": "Bereiche in DSpace", "home.top-level-communities.help": "Wählen Sie einen Bereich, um seine Sammlungen einzusehen.", + "item.page.abstract": "Kurzfassung", "item.page.author": "Autor", "item.page.collections": "Sammlungen", @@ -81,6 +92,7 @@ "item.page.link.full": "Vollanzeige", "item.page.link.simple": "Kurzanzeige", "item.page.uri": "URI", + "loading.browse-by": "Die Ressourcen werden geladen ...", "loading.collection": "Die Sammlung wird geladen ...", "loading.community": "Der Bereich wird geladen ...", @@ -91,6 +103,7 @@ "loading.search-results": "Die Suchergebnisse werden geladen ...", "loading.sub-collections": "Die untergeordneten Sammlungen werden geladen ...", "loading.top-level-communities": "Die Hauptbereiche werden geladen ...", + "login.form.email": "E-Mail-Adresse", "login.form.forgot-password": "Haben Sie Ihr Passwort vergessen?", "login.form.header": "Bitte Loggen Sie sich ein.", @@ -98,22 +111,29 @@ "login.form.password": "Passwort", "login.form.submit": "Einloggen", "login.title": "Einloggen", + "logout.form.header": "Ausloggen aus DSpace", "logout.form.submit": "Ausloggen", "logout.title": "Ausloggen", + "nav.home": "Zur Startseite", "nav.login": "Anmelden", "nav.logout": "Abmelden", + "pagination.results-per-page": "Ergebnisse pro Seite", "pagination.showing.detail": "{{ range }} bis {{ total }}", "pagination.showing.label": "Anzeige der Treffer ", "pagination.sort-direction": "Sortiermöglichkeiten", + "search.description": "", + "search.title": "DSpace Angular :: Suche", + "search.filters.applied.f.author": "Autor", "search.filters.applied.f.dateIssued.max": "Enddatum", "search.filters.applied.f.dateIssued.min": "Anfangsdatum", "search.filters.applied.f.has_content_in_original_bundle": "Besitzt Dateien", "search.filters.applied.f.subject": "Thema", + "search.filters.filter.author.head": "Autor", "search.filters.filter.author.placeholder": "Autor", "search.filters.filter.dateIssued.head": "Datum", @@ -126,12 +146,16 @@ "search.filters.filter.show-more": "Zeige mehr", "search.filters.filter.subject.head": "Schlagwort", "search.filters.filter.subject.placeholder": "Schlagwort", + "search.filters.head": "Filter", "search.filters.reset": "Filter zurücksetzen", + "search.form.search": "Suche", "search.form.search_dspace": "DSpace durchsuchen", + "search.results.head": "Suchergebnisse", "search.results.no-results": "Zu dieser Suche gibt es keine Treffer.", + "search.sidebar.close": "Zurück zu den Ergebnissen", "search.sidebar.filters.title": "Filter", "search.sidebar.open": "Suchwerkzeuge", @@ -139,11 +163,13 @@ "search.sidebar.settings.rpp": "Treffer pro Seite", "search.sidebar.settings.sort-by": "Sortiere nach", "search.sidebar.settings.title": "Einstellungen", - "search.title": "DSpace Angular :: Suche", + "search.view-switch.show-grid": "Zeige als Raster", "search.view-switch.show-list": "Zeige als Liste", + "sorting.dc.title.ASC": "Titel aufsteigend", "sorting.dc.title.DESC": "Titel absteigend", "sorting.score.DESC": "Relevanz", - "title": "DSpace" + + "title": "DSpace", } diff --git a/resources/i18n/en.json b/resources/i18n/en.json5 similarity index 99% rename from resources/i18n/en.json rename to resources/i18n/en.json5 index 63bc598304..1ae31109c8 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json5 @@ -2,6 +2,7 @@ "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", "404.link.home-page": "Take me to the home page", "404.page-not-found": "page not found", + "admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.", "admin.registries.bitstream-formats.formats.no-items": "No bitstream formats to show.", "admin.registries.bitstream-formats.formats.table.internal": "internal", @@ -13,6 +14,7 @@ "admin.registries.bitstream-formats.formats.table.supportLevel.head": "Support Level", "admin.registries.bitstream-formats.head": "Bitstream Format Registry", "admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry", + "admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.", "admin.registries.metadata.form.create": "Create metadata schema", "admin.registries.metadata.form.edit": "Edit metadata schema", @@ -25,6 +27,7 @@ "admin.registries.metadata.schemas.table.name": "Name", "admin.registries.metadata.schemas.table.namespace": "Namespace", "admin.registries.metadata.title": "DSpace Angular :: Metadata Registry", + "admin.registries.schema.description": "This is the metadata schema for \"{{namespace}}\".", "admin.registries.schema.fields.head": "Schema metadata fields", "admin.registries.schema.fields.no-items": "No metadata fields to show.", @@ -49,8 +52,10 @@ "admin.registries.schema.notification.success": "Success", "admin.registries.schema.return": "Return", "admin.registries.schema.title": "DSpace Angular :: Metadata Schema Registry", + "auth.errors.invalid-user": "Invalid email address or password.", "auth.messages.expired": "Your session has expired. Please log in again.", + "browse.comcol.by.author": "By Author", "browse.comcol.by.dateissued": "By Issue Date", "browse.comcol.by.subject": "By Subject", @@ -81,7 +86,9 @@ "browse.startsWith.type_date": "Or type in a date (year-month):", "browse.startsWith.type_text": "Or enter first few letters:", "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}", + "chips.remove": "Remove chip", + "collection.create.head": "Create a Collection", "collection.create.sub-head": "Create a Collection for Community {{ parent }}", "collection.delete.cancel": "Cancel", @@ -103,6 +110,7 @@ "collection.page.browse.recent.head": "Recent Submissions", "collection.page.license": "License", "collection.page.news": "News", + "community.create.head": "Create a Community", "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}", "community.delete.cancel": "Cancel", @@ -123,6 +131,7 @@ "community.page.news": "News", "community.sub-collection-list.head": "Collections of this Community", "community.sub-community-list.head": "Communities of this Community", + "dso-selector.create.collection.head": "New collection", "dso-selector.create.community.head": "New community", "dso-selector.create.community.sub-level": "Create a new community in", @@ -133,6 +142,7 @@ "dso-selector.edit.item.head": "Edit item", "dso-selector.no-results": "No {{ type }} found", "dso-selector.placeholder": "Search for a {{ type }}", + "error.browse-by": "Error fetching items", "error.collection": "Error fetching collection", "error.community": "Error fetching community", @@ -147,9 +157,11 @@ "error.top-level-communities": "Error fetching top-level communities", "error.validation.license.notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission.", "error.validation.pattern": "This input is restricted by the current pattern: {{ pattern }}.", + "footer.copyright": "copyright © 2002-{{ year }}", "footer.link.dspace": "DSpace software", "footer.link.duraspace": "DuraSpace", + "form.cancel": "Cancel", "form.clear": "Clear", "form.clear-help": "Click here to remove the selected value", @@ -171,10 +183,12 @@ "form.search": "Search", "form.search-help": "Click here to looking for an existing correspondence", "form.submit": "Submit", + "home.description": "", "home.title": "DSpace Angular :: Home", "home.top-level-communities.head": "Communities in DSpace", "home.top-level-communities.help": "Select a community to browse its collections.", + "item.edit.delete.cancel": "Cancel", "item.edit.delete.confirm": "Delete", "item.edit.delete.description": "Are you sure this item should be completely deleted? Caution: At present, no tombstone would be left.", @@ -182,6 +196,7 @@ "item.edit.delete.header": "Delete item: {{ id }}", "item.edit.delete.success": "The item has been deleted", "item.edit.head": "Edit Item", + "item.edit.metadata.add-button": "Add", "item.edit.metadata.discard-button": "Discard", "item.edit.metadata.edit.buttons.edit": "Edit", @@ -203,27 +218,32 @@ "item.edit.metadata.notifications.saved.title": "Metadata saved", "item.edit.metadata.reinstate-button": "Undo", "item.edit.metadata.save-button": "Save", + "item.edit.modify.overview.field": "Field", "item.edit.modify.overview.language": "Language", "item.edit.modify.overview.value": "Value", + "item.edit.private.cancel": "Cancel", "item.edit.private.confirm": "Make it Private", "item.edit.private.description": "Are you sure this item should be made private in the archive?", "item.edit.private.error": "An error occurred while making the item private", "item.edit.private.header": "Make item private: {{ id }}", "item.edit.private.success": "The item is now private", + "item.edit.public.cancel": "Cancel", "item.edit.public.confirm": "Make it Public", "item.edit.public.description": "Are you sure this item should be made public in the archive?", "item.edit.public.error": "An error occurred while making the item public", "item.edit.public.header": "Make item public: {{ id }}", "item.edit.public.success": "The item is now public", + "item.edit.reinstate.cancel": "Cancel", "item.edit.reinstate.confirm": "Reinstate", "item.edit.reinstate.description": "Are you sure this item should be reinstated to the archive?", "item.edit.reinstate.error": "An error occurred while reinstating the item", "item.edit.reinstate.header": "Reinstate item: {{ id }}", "item.edit.reinstate.success": "The item was reinstated successfully", + "item.edit.tabs.bitstreams.head": "Item Bitstreams", "item.edit.tabs.bitstreams.title": "Item Edit - Bitstreams", "item.edit.tabs.curate.head": "Curate", @@ -255,12 +275,14 @@ "item.edit.tabs.status.title": "Item Edit - Status", "item.edit.tabs.view.head": "View Item", "item.edit.tabs.view.title": "Item Edit - View", + "item.edit.withdraw.cancel": "Cancel", "item.edit.withdraw.confirm": "Withdraw", "item.edit.withdraw.description": "Are you sure this item should be withdrawn from the archive?", "item.edit.withdraw.error": "An error occurred while withdrawing the item", "item.edit.withdraw.header": "Withdraw item: {{ id }}", "item.edit.withdraw.success": "The item was withdrawn successfully", + "item.page.abstract": "Abstract", "item.page.author": "Authors", "item.page.citation": "Citation", @@ -278,10 +300,12 @@ "item.page.person.search.title": "Articles by this author", "item.page.subject": "Keywords", "item.page.uri": "URI", + "item.select.confirm": "Confirm selected", "item.select.table.author": "Author", "item.select.table.collection": "Collection", "item.select.table.title": "Title", + "journal.listelement.badge": "Journal", "journal.page.description": "Description", "journal.page.editor": "Editor-in-Chief", @@ -290,6 +314,7 @@ "journal.page.titleprefix": "Journal: ", "journal.search.results.head": "Journal Search Results", "journal.search.title": "DSpace Angular :: Journal Search", + "journalissue.listelement.badge": "Journal Issue", "journalissue.page.description": "Description", "journalissue.page.issuedate": "Issue Date", @@ -298,11 +323,13 @@ "journalissue.page.keyword": "Keywords", "journalissue.page.number": "Number", "journalissue.page.titleprefix": "Journal Issue: ", + "journalvolume.listelement.badge": "Journal Volume", "journalvolume.page.description": "Description", "journalvolume.page.issuedate": "Issue Date", "journalvolume.page.titleprefix": "Journal Volume: ", "journalvolume.page.volume": "Volume", + "loading.browse-by": "Loading items...", "loading.browse-by-page": "Loading page...", "loading.collection": "Loading collection...", @@ -316,6 +343,7 @@ "loading.sub-collections": "Loading sub-collections...", "loading.sub-communities": "Loading sub-communities...", "loading.top-level-communities": "Loading top-level communities...", + "login.form.email": "Email address", "login.form.forgot-password": "Have you forgotten your password?", "login.form.header": "Please log in to DSpace", @@ -323,15 +351,19 @@ "login.form.password": "Password", "login.form.submit": "Log in", "login.title": "Login", + "logout.form.header": "Log out from DSpace", "logout.form.submit": "Log out", "logout.title": "Logout", + "menu.header.admin": "Admin", "menu.header.image.logo": "Repository logo", + "menu.section.access_control": "Access Control", "menu.section.access_control_authorizations": "Authorizations", "menu.section.access_control_groups": "Groups", "menu.section.access_control_people": "People", + "menu.section.browse_community": "This Community", "menu.section.browse_community_by_author": "By Author", "menu.section.browse_community_by_issue_date": "By Issue Date", @@ -342,21 +374,26 @@ "menu.section.browse_global_by_subject": "By Subject", "menu.section.browse_global_by_title": "By Title", "menu.section.browse_global_communities_and_collections": "Communities & Collections", + "menu.section.control_panel": "Control Panel", "menu.section.curation_task": "Curation Task", + "menu.section.edit": "Edit", "menu.section.edit_collection": "Collection", "menu.section.edit_community": "Community", "menu.section.edit_item": "Item", + "menu.section.export": "Export", "menu.section.export_collection": "Collection", "menu.section.export_community": "Community", "menu.section.export_item": "Item", "menu.section.export_metadata": "Metadata", + "menu.section.find": "Find", "menu.section.find_items": "Items", "menu.section.find_private_items": "Private Items", "menu.section.find_withdrawn_items": "Withdrawn Items", + "menu.section.icon.access_control": "Access Control menu section", "menu.section.icon.control_panel": "Control Panel menu section", "menu.section.icon.curation_task": "Curation Task menu section", @@ -369,20 +406,27 @@ "menu.section.icon.registries": "Registries menu section", "menu.section.icon.statistics_task": "Statistics Task menu section", "menu.section.icon.unpin": "Unpin sidebar", + "menu.section.import": "Import", "menu.section.import_batch": "Batch Import (ZIP)", "menu.section.import_metadata": "Metadata", + "menu.section.new": "New", "menu.section.new_collection": "Collection", "menu.section.new_community": "Community", "menu.section.new_item": "Item", "menu.section.new_item_version": "Item Version", + "menu.section.pin": "Pin sidebar", + "menu.section.unpin": "Unpin sidebar", + "menu.section.registries": "Registries", "menu.section.registries_format": "Format", "menu.section.registries_metadata": "Metadata", + "menu.section.statistics": "Statistics", "menu.section.statistics_task": "Statistics Task", + "menu.section.toggle.access_control": "Toggle Access Control section", "menu.section.toggle.control_panel": "Toggle Control Panel section", "menu.section.toggle.curation_task": "Toggle Curation Task section", @@ -393,7 +437,7 @@ "menu.section.toggle.new": "Toggle New section", "menu.section.toggle.registries": "Toggle Registries section", "menu.section.toggle.statistics_task": "Toggle Statistics Task section", - "menu.section.unpin": "Unpin sidebar", + "mydspace.description": "", "mydspace.general.text-here": "HERE", "mydspace.messages.controller-help": "Select this option to send a message to item's submitter.", @@ -431,6 +475,7 @@ "mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.", "mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.", "mydspace.view-btn": "View", + "nav.browse.header": "All of DSpace", "nav.community-browse.header": "By Community", "nav.language": "Language switch", @@ -439,6 +484,7 @@ "nav.mydspace": "MyDSpace", "nav.search": "Search", "nav.statistics.header": "Statistics", + "orgunit.listelement.badge": "Organizational Unit", "orgunit.page.city": "City", "orgunit.page.country": "Country", @@ -446,10 +492,12 @@ "orgunit.page.description": "Description", "orgunit.page.id": "ID", "orgunit.page.titleprefix": "Organizational Unit: ", + "pagination.results-per-page": "Results Per Page", "pagination.showing.detail": "{{ range }} of {{ total }}", "pagination.showing.label": "Now showing ", "pagination.sort-direction": "Sort Options", + "person.listelement.badge": "Person", "person.page.birthdate": "Birth Date", "person.page.email": "Email Address", @@ -462,6 +510,7 @@ "person.page.titleprefix": "Person: ", "person.search.results.head": "Person Search Results", "person.search.title": "DSpace Angular :: Person Search", + "project.listelement.badge": "Research Project", "project.page.contributor": "Contributors", "project.page.description": "Description", @@ -471,6 +520,7 @@ "project.page.keyword": "Keywords", "project.page.status": "Status", "project.page.titleprefix": "Research Project: ", + "publication.listelement.badge": "Publication", "publication.page.description": "Description", "publication.page.journal-issn": "Journal ISSN", @@ -480,6 +530,7 @@ "publication.page.volume-title": "Volume Title", "publication.search.results.head": "Publication Search Results", "publication.search.title": "DSpace Angular :: Publication Search", + "relationships.isAuthorOf": "Authors", "relationships.isIssueOf": "Journal Issues", "relationships.isJournalIssueOf": "Journal Issue", @@ -492,7 +543,11 @@ "relationships.isSingleJournalOf": "Journal", "relationships.isSingleVolumeOf": "Journal Volume", "relationships.isVolumeOf": "Journal Volumes", + "search.description": "", + "search.switch-configuration.title": "Show", + "search.title": "DSpace Angular :: Search", + "search.filters.applied.f.author": "Author", "search.filters.applied.f.dateIssued.max": "End date", "search.filters.applied.f.dateIssued.min": "Start date", @@ -503,6 +558,7 @@ "search.filters.applied.f.namedresourcetype": "Status", "search.filters.applied.f.subject": "Subject", "search.filters.applied.f.submitter": "Submitter", + "search.filters.filter.author.head": "Author", "search.filters.filter.author.placeholder": "Author name", "search.filters.filter.birthDate.head": "Birth Date", @@ -547,14 +603,18 @@ "search.filters.filter.subject.placeholder": "Subject", "search.filters.filter.submitter.head": "Submitter", "search.filters.filter.submitter.placeholder": "Submitter", + "search.filters.head": "Filters", "search.filters.reset": "Reset filters", + "search.form.search": "Search", "search.form.search_dspace": "Search DSpace", "search.form.search_mydspace": "Search MyDSpace", + "search.results.head": "Search Results", "search.results.no-results": "Your search returned no results. Having trouble finding what you're looking for? Try putting", "search.results.no-results-link": "quotes around it", + "search.sidebar.close": "Back to results", "search.sidebar.filters.title": "Filters", "search.sidebar.open": "Search Tools", @@ -562,14 +622,15 @@ "search.sidebar.settings.rpp": "Results per page", "search.sidebar.settings.sort-by": "Sort By", "search.sidebar.settings.title": "Settings", - "search.switch-configuration.title": "Show", - "search.title": "DSpace Angular :: Search", + "search.view-switch.show-detail": "Show detail", "search.view-switch.show-grid": "Show as grid", "search.view-switch.show-list": "Show as list", + "sorting.dc.title.ASC": "Title Ascending", "sorting.dc.title.DESC": "Title Descending", "sorting.score.DESC": "Relevance", + "submission.edit.title": "Edit Submission", "submission.general.cannot_submit": "You have not the privilege to make a new submission.", "submission.general.deposit": "Deposit", @@ -580,7 +641,7 @@ "submission.general.discard.submit": "Discard", "submission.general.save": "Save", "submission.general.save-later": "Save for later", - "submission.mydspace": {}, + "submission.sections.general.add-more": "Add more", "submission.sections.general.collection": "Collection", "submission.sections.general.deposit_error_notice": "There was an issue when submitting the item, please try again later.", @@ -595,6 +656,7 @@ "submission.sections.general.save_success_notice": "Submission saved successfully.", "submission.sections.general.search-collection": "Search for a collection", "submission.sections.general.sections_not_valid": "There are incomplete sections.", + "submission.sections.submit.progressbar.cclicense": "Creative commons license", "submission.sections.submit.progressbar.describe.recycle": "Recycle", "submission.sections.submit.progressbar.describe.stepcustom": "Describe", @@ -603,6 +665,7 @@ "submission.sections.submit.progressbar.detect-duplicate": "Potential duplicates", "submission.sections.submit.progressbar.license": "Deposit license", "submission.sections.submit.progressbar.upload": "Upload files", + "submission.sections.upload.delete.confirm.cancel": "Cancel", "submission.sections.upload.delete.confirm.info": "This operation can't be undone. Are you sure?", "submission.sections.upload.delete.confirm.submit": "Yes, I'm sure", @@ -626,13 +689,16 @@ "submission.sections.upload.undo": "Cancel", "submission.sections.upload.upload-failed": "Upload failed", "submission.sections.upload.upload-successful": "Upload successful", + "submission.submit.title": "Submission", + "submission.workflow.generic.delete": "Delete", "submission.workflow.generic.delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.", "submission.workflow.generic.edit": "Edit", "submission.workflow.generic.edit-help": "Select this option to change the item's metadata.", "submission.workflow.generic.view": "View", "submission.workflow.generic.view-help": "Select this option to view the item's metadata.", + "submission.workflow.tasks.claimed.approve": "Approve", "submission.workflow.tasks.claimed.approve_help": "If you have reviewed the item and it is suitable for inclusion in the collection, select \"Approve\".", "submission.workflow.tasks.claimed.edit": "Edit", @@ -645,18 +711,22 @@ "submission.workflow.tasks.claimed.reject_help": "If you have reviewed the item and found it is not suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.", "submission.workflow.tasks.claimed.return": "Return to pool", "submission.workflow.tasks.claimed.return_help": "Return the task to the pool so that another user may perform the task.", + "submission.workflow.tasks.generic.error": "Error occurred during operation...", "submission.workflow.tasks.generic.processing": "Processing...", "submission.workflow.tasks.generic.submitter": "Submitter", "submission.workflow.tasks.generic.success": "Operation successful", + "submission.workflow.tasks.pool.claim": "Claim", "submission.workflow.tasks.pool.claim_help": "Assign this task to yourself.", "submission.workflow.tasks.pool.hide-detail": "Hide detail", "submission.workflow.tasks.pool.show-detail": "Show detail", + "title": "DSpace", + "uploader.browse": "browse", "uploader.drag-message": "Drag & Drop your files here", "uploader.or": ", or", "uploader.processing": "Processing", - "uploader.queue-lenght": "Queue length" + "uploader.queue-lenght": "Queue length", } diff --git a/resources/i18n/nl.json b/resources/i18n/nl.json5 similarity index 99% rename from resources/i18n/nl.json rename to resources/i18n/nl.json5 index da12ff0518..a195e13e01 100644 --- a/resources/i18n/nl.json +++ b/resources/i18n/nl.json5 @@ -2,6 +2,7 @@ "404.help": "De pagina die u zoekt kan niet gevonden worden. De pagina werd mogelijk verplaatst of verwijderd. U kan onderstaande knop gebruiken om terug naar de homepagina te gaan. ", "404.link.home-page": "Terug naar de homepagina", "404.page-not-found": "Pagina niet gevonden", + "admin.registries.bitstream-formats.description": "Deze lijst van Bitstream formaten biedt informatie over de formaten die in deze repository zijn toegelaten en op welke manier ze ondersteund worden. De term Bitstream wordt in DSpace gebruikt om een bestand aan te duiden dat samen met metadata onderdeel uitmaakt van een item. De naam bitstream duidt op het feit dat het bestand achterliggend wordt opgeslaan zonder bestandsextensie.", "admin.registries.bitstream-formats.formats.no-items": "Er kunnen geen bitstreamformaten getoond worden.", "admin.registries.bitstream-formats.formats.table.internal": "intern", @@ -13,6 +14,7 @@ "admin.registries.bitstream-formats.formats.table.supportLevel.head": "Ondersteuning", "admin.registries.bitstream-formats.head": "Bitstream Formaat Register", "admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Formaat Register", + "admin.registries.metadata.description": "Het metadataregister omvat de lijst van alle metadatavelden die beschikbaar zijn in het systeem. Deze velden kunnen verspreid zijn over verschillende metadataschema's. Het qualified Dublin Core schema (dc) is een verplicht schema en kan niet worden verwijderd.", "admin.registries.metadata.head": "Metadata Register", "admin.registries.metadata.schemas.no-items": "Er kunnen geen metadataschema's getoond worden.", @@ -20,6 +22,7 @@ "admin.registries.metadata.schemas.table.name": "Naam", "admin.registries.metadata.schemas.table.namespace": "Naamruimte", "admin.registries.metadata.title": "DSpace Angular :: Metadata Register", + "admin.registries.schema.description": "Dit is het metadataschema voor \"{{namespace}}\".", "admin.registries.schema.fields.head": "Schema metadatavelden", "admin.registries.schema.fields.no-items": "Er kunnen geen metadatavelden getoond worden.", @@ -27,15 +30,20 @@ "admin.registries.schema.fields.table.scopenote": "Opmerking over bereik", "admin.registries.schema.head": "Metadata Schema", "admin.registries.schema.title": "DSpace Angular :: Metadata Schema Register", + "auth.errors.invalid-user": "Ongeldig e-mailadres of wachtwoord.", "auth.messages.expired": "Uw sessie is vervallen. Gelieve opnieuw aan te melden.", + "browse.title": "Verken {{ collection }} volgens {{ field }} {{ value }}", + "collection.page.browse.recent.head": "Recent toegevoegd", "collection.page.license": "Licentie", "collection.page.news": "Nieuws", + "community.page.license": "Licentie", "community.page.news": "Nieuws", "community.sub-collection-list.head": "Collecties in deze Community", + "error.browse-by": "Fout bij het ophalen van items", "error.collection": "Fout bij het ophalen van een collectie", "error.community": "Fout bij het ophalen van een community", @@ -48,9 +56,11 @@ "error.top-level-communities": "Fout bij het inladen van communities op het hoogste niveau", "error.validation.license.notgranted": "U moet de invoerlicentie goedkeuren om de invoer af te werken. Indien u deze licentie momenteel niet kan of mag goedkeuren, kan u uw werk opslaan en de invoer later afwerken. U kunt dit nieuwe item ook verwijderen indien u niet voldoet aan de vereisten van de invoerlicentie.", "error.validation.pattern": "Deze invoer is niet toegelaten volgens dit patroon: {{ pattern }}.", + "footer.copyright": "copyright © 2002-{{ year }}", "footer.link.dspace": "DSpace software", "footer.link.duraspace": "DuraSpace", + "form.cancel": "Annuleer", "form.first-name": "Voornaam", "form.group-collapse": "Inklappen", @@ -64,10 +74,12 @@ "form.remove": "Verwijder", "form.search": "Zoek", "form.submit": "Verstuur", + "home.description": "", "home.title": "DSpace Angular :: Home", "home.top-level-communities.head": "Communities in DSpace", "home.top-level-communities.help": "Selecteer een community om diens collecties te verkennen.", + "item.page.abstract": "Abstract", "item.page.author": "Auteur", "item.page.collections": "Collecties", @@ -81,6 +93,7 @@ "item.page.link.full": "Volledige itemweergave", "item.page.link.simple": "Eenvoudige itemweergave", "item.page.uri": "URI", + "loading.browse-by": "Items worden ingeladen...", "loading.collection": "Collectie wordt ingeladen...", "loading.community": "Community wordt ingeladen...", @@ -91,6 +104,7 @@ "loading.search-results": "Zoekresultaten worden ingeladen...", "loading.sub-collections": "De sub-collecties worden ingeladen...", "loading.top-level-communities": "Inladen van de Communities op het hoogste niveau...", + "login.form.email": "Email adres", "login.form.forgot-password": "Bent u uw wachtwoord vergeten?", "login.form.header": "Gelieve in te loggen in DSpace", @@ -98,17 +112,23 @@ "login.form.password": "Wachtwoord", "login.form.submit": "Aanmelden", "login.title": "Aanmelden", + "logout.form.header": "Afmelden in DSpace", "logout.form.submit": "Afmelden", "logout.title": "Afmelden", + "nav.home": "Home", "nav.login": "Log In", "nav.logout": "Log Uit", + "pagination.results-per-page": "Resultaten per pagina", "pagination.showing.detail": "{{ range }} van {{ total }}", "pagination.showing.label": "Resultaten ", "pagination.sort-direction": "Sorteermogelijkheden", + "search.description": "", + "search.title": "DSpace Angular :: Zoek", + "search.filters.applied.f.author": "Auteur", "search.filters.applied.f.dateIssued.max": "Einddatum", "search.filters.applied.f.dateIssued.min": "Startdatum", @@ -126,12 +146,16 @@ "search.filters.filter.show-more": "Toon meer", "search.filters.filter.subject.head": "Onderwerp", "search.filters.filter.subject.placeholder": "Onderwerp", + "search.filters.head": "Filters", "search.filters.reset": "Filters verwijderen", + "search.form.search": "Zoek", "search.form.search_dspace": "Zoek in DSpace", + "search.results.head": "Zoekresultaten", "search.results.no-results": "Er waren geen resultaten voor deze zoekopdracht", + "search.sidebar.close": "Terug naar de resultaten", "search.sidebar.filters.title": "Filters", "search.sidebar.open": "Zoek Tools", @@ -139,11 +163,13 @@ "search.sidebar.settings.rpp": "Resultaten per pagina", "search.sidebar.settings.sort-by": "Sorteer volgens", "search.sidebar.settings.title": "Instellingen", - "search.title": "DSpace Angular :: Zoek", + "search.view-switch.show-grid": "Toon in raster", "search.view-switch.show-list": "Toon als lijst", + "sorting.dc.title.ASC": "Oplopend op titel", "sorting.dc.title.DESC": "Aflopend op titel", "sorting.score.DESC": "Relevantie", - "title": "DSpace" + + "title": "DSpace", } diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index b20894880b..a90887e987 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -6,7 +6,7 @@ import { RouterModule } from '@angular/router'; import { REQUEST } from '@nguniversal/express-engine/tokens'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { TranslateHttpLoader } from '@ngx-translate/http-loader'; +import { TranslateJson5HttpLoader } from '../../ngx-translate-loaders/translate-json5-http.loader'; import { IdlePreload, IdlePreloadModule } from 'angular-idle-preload'; @@ -20,13 +20,12 @@ import { CookieService } from '../../app/shared/services/cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; import { Angulartics2Module } from 'angulartics2'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; -import { ServerSubmissionService } from '../../app/submission/server-submission.service'; import { SubmissionService } from '../../app/submission/submission.service'; export const REQ_KEY = makeStateKey('req'); export function createTranslateLoader(http: HttpClient) { - return new TranslateHttpLoader(http, 'assets/i18n/', '.json'); + return new TranslateJson5HttpLoader(http, 'assets/i18n/', '.json5'); } export function getRequest(transferState: TransferState): any { diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index d809d3cced..4a4efe2bdd 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -12,7 +12,7 @@ import { AppModule } from '../../app/app.module'; import { DSpaceServerTransferStateModule } from '../transfer-state/dspace-server-transfer-state.module'; import { DSpaceTransferState } from '../transfer-state/dspace-transfer-state.service'; -import { TranslateUniversalLoader } from '../translate-universal-loader'; +import { TranslateJson5UniversalLoader } from '../../ngx-translate-loaders/translate-json5-universal.loader'; import { CookieService } from '../../app/shared/services/cookie.service'; import { ServerCookieService } from '../../app/shared/services/server-cookie.service'; import { AuthService } from '../../app/core/auth/auth.service'; @@ -24,7 +24,7 @@ import { SubmissionService } from '../../app/submission/submission.service'; import { ServerSubmissionService } from '../../app/submission/server-submission.service'; export function createTranslateLoader() { - return new TranslateUniversalLoader('dist/assets/i18n/', '.json'); + return new TranslateJson5UniversalLoader('dist/assets/i18n/', '.json5'); } @NgModule({ diff --git a/src/ngx-translate-loaders/translate-json5-http.loader.ts b/src/ngx-translate-loaders/translate-json5-http.loader.ts new file mode 100644 index 0000000000..edc467d99f --- /dev/null +++ b/src/ngx-translate-loaders/translate-json5-http.loader.ts @@ -0,0 +1,15 @@ +import { HttpClient } from '@angular/common/http'; +import { TranslateLoader } from '@ngx-translate/core'; +import { map } from 'rxjs/operators'; +import JSON5 from 'json5' + +export class TranslateJson5HttpLoader implements TranslateLoader { + constructor(private http: HttpClient, public prefix?: string, public suffix?: string) { + } + + getTranslation(lang: string): any { + return this.http.get('' + this.prefix + lang + this.suffix, {responseType: 'text'}).pipe( + map((json: any) => JSON5.parse(json)) + ); + } +} diff --git a/src/modules/translate-universal-loader.ts b/src/ngx-translate-loaders/translate-json5-universal.loader.ts similarity index 65% rename from src/modules/translate-universal-loader.ts rename to src/ngx-translate-loaders/translate-json5-universal.loader.ts index b2efaf2395..ca266f0153 100644 --- a/src/modules/translate-universal-loader.ts +++ b/src/ngx-translate-loaders/translate-json5-universal.loader.ts @@ -1,14 +1,15 @@ import { TranslateLoader } from '@ngx-translate/core'; import { Observable } from 'rxjs'; +import JSON5 from 'json5' import * as fs from 'fs'; -export class TranslateUniversalLoader implements TranslateLoader { +export class TranslateJson5UniversalLoader implements TranslateLoader { constructor(private prefix: string = 'dist/assets/i18n/', private suffix: string = '.json') { } public getTranslation(lang: string): Observable { return Observable.create((observer: any) => { - observer.next(JSON.parse(fs.readFileSync(`${this.prefix}${lang}${this.suffix}`, 'utf8'))); + observer.next(JSON5.parse(fs.readFileSync(`${this.prefix}${lang}${this.suffix}`, 'utf8'))); observer.complete(); }); } diff --git a/yarn.lock b/yarn.lock index eb0733e695..2d9aa7b0d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -371,6 +371,11 @@ resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.1.0.tgz#a8916246aa994db646c66d54c854916213300a51" integrity sha512-vPT5MV1pD71RFUD0ytp6Yw51W6zKJ9Qn2AcJXSD2TZqYKaXUtCxB3WZIXXFZtbAEVMgC59nmvVPSOH0EIvkaZg== +"@types/json5@^0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.30.tgz#44cb52f32a809734ca562e685c6473b5754a7818" + integrity sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA== + "@types/lodash@4.14.74": version "4.14.74" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.74.tgz#ac3bd8db988e7f7038e5d22bd76a7ba13f876168" @@ -5651,6 +5656,13 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +json5@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" + integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + dependencies: + minimist "^1.2.0" + jsonfile@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" From 861f8ddb6c770f0a31caf53a0e9aa11b64c7605e Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 26 Jul 2019 19:41:28 +0200 Subject: [PATCH 040/111] Show hint message only when there is an error message --- .../ds-dynamic-form-control-container.component.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index cead04f797..5692c27d20 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -14,7 +14,8 @@ - +
{{ message | translate:model.validators }} From 509fd0d802d8ff7070e6ab3a4569c747c32c8bb0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 26 Jul 2019 19:43:15 +0200 Subject: [PATCH 041/111] Fixed an issue while editing repeatable fields --- .../sections/form/section-form.component.ts | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts index ef817a7568..2269ccd5f1 100644 --- a/src/app/submission/sections/form/section-form.component.ts +++ b/src/app/submission/sections/form/section-form.component.ts @@ -64,6 +64,12 @@ export class SubmissionSectionformComponent extends SectionModelComponent { */ public isLoading = true; + /** + * A map representing all field on their way to be removed + * @type {Map} + */ + protected fieldsOnTheirWayToBeRemoved: Map = new Map(); + /** * The form config * @type {SubmissionFormsModel} @@ -295,6 +301,7 @@ export class SubmissionSectionformComponent extends SectionModelComponent { }), distinctUntilChanged()) .subscribe((sectionState: SubmissionSectionObject) => { + this.fieldsOnTheirWayToBeRemoved = new Map(); this.updateForm(sectionState.data as WorkspaceitemSectionFormObject, sectionState.errors); }) ) @@ -348,11 +355,24 @@ export class SubmissionSectionformComponent extends SectionModelComponent { * the [[DynamicFormControlEvent]] emitted */ onRemove(event: DynamicFormControlEvent): void { + const fieldId = this.formBuilderService.getId(event.model); + const fieldIndex = this.formOperationsService.getArrayIndexFromEvent(event); + + // Keep track that this field will be removed + if (this.fieldsOnTheirWayToBeRemoved.has(fieldId)) { + const indexes = this.fieldsOnTheirWayToBeRemoved.get(fieldId); + indexes.push(fieldIndex); + this.fieldsOnTheirWayToBeRemoved.set(fieldId, indexes); + } else { + this.fieldsOnTheirWayToBeRemoved.set(fieldId, [fieldIndex]); + } + this.formOperationsService.dispatchOperationsFromEvent( this.pathCombiner, event, this.previousValue, - this.hasStoredValue(this.formBuilderService.getId(event.model), this.formOperationsService.getArrayIndexFromEvent(event))); + this.hasStoredValue(fieldId, fieldIndex)); + } /** @@ -365,9 +385,23 @@ export class SubmissionSectionformComponent extends SectionModelComponent { */ hasStoredValue(fieldId, index): boolean { if (isNotEmpty(this.sectionData.data)) { - return this.sectionData.data.hasOwnProperty(fieldId) && isNotEmpty(this.sectionData.data[fieldId][index]); + return this.sectionData.data.hasOwnProperty(fieldId) && + isNotEmpty(this.sectionData.data[fieldId][index]) && + !this.isFieldToRemove(fieldId, index); } else { return false; } } + + /** + * Check if the specified field is on the way to be removed + * + * @param fieldId + * the section data retrieved from the serverù + * @param index + * the section data retrieved from the server + */ + isFieldToRemove(fieldId, index) { + return this.fieldsOnTheirWayToBeRemoved.has(fieldId) && this.fieldsOnTheirWayToBeRemoved.get(fieldId).includes(index); + } } From 5c101d116a8db8ac40da236723744deb1e229508 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 8 Aug 2019 15:43:29 +0200 Subject: [PATCH 042/111] 63838: Refactor input suggestions to support DSpaceObjects as suggestions --- .../edit-item-page/edit-item-page.module.ts | 2 +- .../edit-item-page.routing.module.ts | 1 + .../edit-in-place-field.component.html | 4 +- .../edit-in-place-field.component.spec.ts | 2 +- .../edit-in-place-field.component.ts | 2 +- .../search-authority-filter.component.html | 4 +- .../search-facet-filter.component.ts | 2 +- .../search-hierarchy-filter.component.html | 4 +- .../search-text-filter.component.html | 4 +- .../dso-input-suggestions.component.html | 23 ++++++ .../dso-input-suggestions.component.spec.ts | 71 +++++++++++++++++++ .../dso-input-suggestions.component.ts | 47 ++++++++++++ .../filter-input-suggestions.component.html | 22 ++++++ ...filter-input-suggestions.component.spec.ts | 57 +++++++++++++++ .../filter-input-suggestions.component.ts | 44 ++++++++++++ .../input-suggestions.component.ts | 31 ++------ src/app/shared/shared.module.ts | 6 +- 17 files changed, 289 insertions(+), 37 deletions(-) create mode 100644 src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html create mode 100644 src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts create mode 100644 src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.ts create mode 100644 src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.html create mode 100644 src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.spec.ts create mode 100644 src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.ts 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 de672c9ea7..a82c1976c8 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 @@ -40,7 +40,7 @@ import { EditItemPageRoutingModule } from './edit-item-page.routing.module'; ItemMetadataComponent, ItemBitstreamsComponent, EditInPlaceFieldComponent, - ItemMoveComponent + ItemMoveComponent, ] }) export class EditItemPageModule { diff --git a/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/+item-page/edit-item-page/edit-item-page.routing.module.ts index c82025cf34..781b5ea933 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 @@ -111,6 +111,7 @@ export function getItemEditMovePath(id: string) { { path: ITEM_EDIT_MOVE_PATH, component: ItemMoveComponent, + data: {title: 'item.edit.move.title'}, resolve: { item: ItemPageResolver } diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html index e9c5de95ca..e8ffc28920 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html @@ -4,7 +4,7 @@ {{metadata?.key?.split('.').join('.​')}}
- + >
{{"item.edit.metadata.metadatafield.invalid" | translate}} diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts index 09363b9964..7182f90108 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts @@ -11,12 +11,12 @@ import { By } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { SharedModule } from '../../../../shared/shared.module'; import { getTestScheduler } from 'jasmine-marbles'; -import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model'; import { TestScheduler } from 'rxjs/testing'; import { MetadataSchema } from '../../../../core/metadata/metadataschema.model'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; import { TranslateModule } from '@ngx-translate/core'; import { MetadatumViewModel } from '../../../../core/shared/metadata.models'; +import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model'; let comp: EditInPlaceFieldComponent; let fixture: ComponentFixture; diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts index 0b9bc62c55..facfd25008 100644 --- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts +++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts @@ -5,12 +5,12 @@ import { cloneDeep } from 'lodash'; import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; import { map, take } from 'rxjs/operators'; import { MetadataField } from '../../../../core/metadata/metadatafield.model'; -import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model'; import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions'; import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer'; import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service'; import { NgModel } from '@angular/forms'; import { MetadatumViewModel } from '../../../../core/shared/metadata.models'; +import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model'; @Component({ // tslint:disable-next-line:component-selector diff --git a/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html index 76cdc6c8f5..f2b2aad7aa 100644 --- a/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-authority-filter/search-authority-filter.component.html @@ -15,7 +15,7 @@ | translate}}
- + ngDefaultControl>
diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index 772240eb0b..6402d07f9b 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -21,9 +21,9 @@ import { SearchService } from '../../../search-service/search.service'; import { FILTER_CONFIG, IN_PLACE_SEARCH, SearchFilterService } from '../search-filter.service'; import { SearchConfigurationService } from '../../../search-service/search-configuration.service'; import { getSucceededRemoteData } from '../../../../core/shared/operators'; -import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model'; import { SearchOptions } from '../../../search-options.model'; import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component'; +import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model'; @Component({ selector: 'ds-search-facet-filter', diff --git a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html index ac2a72f4b6..027f162a0a 100644 --- a/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.html @@ -15,7 +15,7 @@ | translate}}
- + >
diff --git a/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.html index a4f4fb5ee8..5241c6c0bd 100644 --- a/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.html +++ b/src/app/+search-page/search-filters/search-filter/search-text-filter/search-text-filter.component.html @@ -15,7 +15,7 @@ | translate}}
- + ngDefaultControl>
diff --git a/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html new file mode 100644 index 0000000000..016ff8c06c --- /dev/null +++ b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html @@ -0,0 +1,23 @@ + + + + + + diff --git a/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts new file mode 100644 index 0000000000..2e343a6834 --- /dev/null +++ b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts @@ -0,0 +1,71 @@ +import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; + +import { TranslateModule } from '@ngx-translate/core'; +import { By } from '@angular/platform-browser'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; +import { DsoInputSuggestionsComponent } from './dso-input-suggestions.component'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; + +describe('DsoInputSuggestionsComponent', () => { + + let comp: DsoInputSuggestionsComponent; + let fixture: ComponentFixture; + let de: DebugElement; + let el: HTMLElement; + + const dso1 = { + uuid: 'test-uuid-1', + name: 'test-name-1' + } as DSpaceObject; + + const dso2 = { + uuid: 'test-uuid-2', + name: 'test-name-2' + } as DSpaceObject; + + const dso3 = { + uuid: 'test-uuid-3', + name: 'test-name-3' + } as DSpaceObject; + + const suggestions = [dso1, dso2, dso3]; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NoopAnimationsModule, FormsModule], + declarations: [DsoInputSuggestionsComponent], + providers: [], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(DsoInputSuggestionsComponent, { + set: {changeDetection: ChangeDetectionStrategy.Default} + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DsoInputSuggestionsComponent); + + comp = fixture.componentInstance; // LoadingComponent test instance + comp.suggestions = suggestions; + // query for the message
@@ -40,11 +39,10 @@ [disabled]="firstInputValue.length === 0 || isInputDisabled()" [placeholder]="model.secondPlaceholder | translate" [readonly]="model.readOnly" - (change)="$event.preventDefault()" + (change)="onChange($event)" (blur)="onBlurEvent($event); $event.stopPropagation(); sdRef.close();" (focus)="onFocusEvent($event); $event.stopPropagation(); sdRef.close();" - (click)="$event.stopPropagation(); sdRef.close();" - (input)="onInput($event)"> + (click)="$event.stopPropagation(); sdRef.close();">
diff --git a/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts index 2e343a6834..4229060e86 100644 --- a/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts +++ b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.spec.ts @@ -60,7 +60,7 @@ describe('DsoInputSuggestionsComponent', () => { const clickedIndex = 0; beforeEach(() => { spyOn(comp, 'onClickSuggestion'); - const clickedLink = de.query(By.css('.dropdown-list > div:nth-child(' + (clickedIndex + 1) + ') a')); + const clickedLink = de.query(By.css('.dropdown-list > div:nth-child(' + (clickedIndex + 1) + ') button')); clickedLink.triggerEventHandler('click', {}); fixture.detectChanges(); }); diff --git a/src/app/shared/input-suggestions/input-suggestions.component.scss b/src/app/shared/input-suggestions/input-suggestions.component.scss index b04cef2adf..eb0db5ed42 100644 --- a/src/app/shared/input-suggestions/input-suggestions.component.scss +++ b/src/app/shared/input-suggestions/input-suggestions.component.scss @@ -1,12 +1,23 @@ .autocomplete { width: 100%; + .dropdown-item { white-space: normal; word-break: break-word; padding: $input-padding-y $input-padding-x; + position: relative; + &:focus { outline: none; } + + .click-blocker { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + } } } From b68c59b83506a9a2a8c1b4371a7bdba27362da81 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Sun, 1 Sep 2019 16:25:26 -0700 Subject: [PATCH 059/111] Updating comments per review --- .travis.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c161e85977..cff5a11c2c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,11 @@ sudo: required dist: trusty env: + # Install the latest docker-compose version for ci testing. + # The default installation in travis is not compatible with the latest docker-compose file version. COMPOSE_VERSION: 1.24.1 + # The ci step will test the dpsace-angular code against DSpace REST. + # Direct that step to utilize a DSpace REST service that has been started in docker. DSPACE_REST_HOST: localhost DSPACE_REST_PORT: 8080 DSPACE_REST_NAMESPACE: '/server/api' @@ -19,11 +23,10 @@ install: - docker-compose version - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose/d7.travis.yml up -d - travis_retry yarn install - # allow starup time - #- sleep 60 before_script: - # Startup the app + # The following line could be enabled to verify that the rest server is repsonding. + # Currently, "yarn run build" takes enough time to run to allow the service to be available #- curl http://localhost:8080/ after_script: From 9723d929e12ba0d9f1c52208053cd84848346435 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 5 Sep 2019 10:44:37 +0200 Subject: [PATCH 060/111] resolved issues after merge with master --- .../journal-issue/journal-issue-grid-element.component.html | 2 +- .../journal-volume/journal-volume-grid-element.component.html | 2 +- .../journal/journal-grid-element.component.html | 2 +- .../orgunit/orgunit-grid-element.component.html | 2 +- .../person/person-grid-element.component.html | 2 +- .../project/project-grid-element.component.html | 2 +- .../publication/publication-grid-element.component.html | 2 +- .../search-result-grid-element.component.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html index 4cb34a140b..3aa79fc70a 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html index d7c9b68a24..b2b251f550 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html index 467cdd1594..af0739004c 100644 --- a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html +++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html index 104d3a0a57..a4765c4e8f 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/orgunit/orgunit-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html index 86353377fa..331c2bd520 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html index a595791cc4..889276b29b 100644 --- a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html +++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html index e2477524ca..e735dbf107 100644 --- a/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html +++ b/src/app/shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts index af1768b0e5..63b2536043 100644 --- a/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts +++ b/src/app/shared/object-grid/search-result-grid-element/search-result-grid-element.component.ts @@ -22,7 +22,7 @@ export class SearchResultGridElementComponent, K exten super(listableObject); if (hasValue(this.object)) { this.dso = this.object.indexableObject; - this.isCollapsed$ = this.isCollapsed(); + this.isCollapsed$ = this.isCollapsed(); } } From 92f8a5d5224a5a6a5a17ce819f70d7683b1996c2 Mon Sep 17 00:00:00 2001 From: Julius Gruber Date: Fri, 6 Sep 2019 13:22:43 +0200 Subject: [PATCH 061/111] Typo in dropdown-field-parser.ts fixed --- src/app/shared/form/builder/parsers/dropdown-field-parser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/parsers/dropdown-field-parser.ts b/src/app/shared/form/builder/parsers/dropdown-field-parser.ts index 279f78f721..1623829b15 100644 --- a/src/app/shared/form/builder/parsers/dropdown-field-parser.ts +++ b/src/app/shared/form/builder/parsers/dropdown-field-parser.ts @@ -29,7 +29,7 @@ export class DropdownFieldParser extends FieldParser { const dropdownModel = new DynamicScrollableDropdownModel(dropdownModelConfig, layout); return dropdownModel; } else { - throw Error(`Authority name is not available. Please checks form configuration file.`); + throw Error(`Authority name is not available. Please check the form configuration file.`); } } } From 431d7e12b112f1b3b4ec96026a4017f12152523d Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 6 Sep 2019 14:09:21 +0200 Subject: [PATCH 062/111] 62589: Clean build folder pre-tests --- config/environment.test.js | 4 +--- package.json | 3 +++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/config/environment.test.js b/config/environment.test.js index 0652755bc7..f4d625303f 100644 --- a/config/environment.test.js +++ b/config/environment.test.js @@ -1,5 +1,3 @@ module.exports = { - theme: { - name: 'default', - } + }; diff --git a/package.json b/package.json index 7916379039..3f5bda522b 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,9 @@ "protractor": "node node_modules/protractor/bin/protractor", "pree2e": "yarn run webdriver:update", "e2e": "yarn run protractor", + "pretest": "yarn run clean:bld", + "pretest:headless": "yarn run pretest", + "pretest:watch": "yarn run pretest", "test": "karma start --single-run", "test:headless": "karma start --single-run --browsers ChromeHeadless", "test:watch": "karma start --no-single-run --auto-watch", From bf039978beac6b2ba3d17bcdcf0a918b8b94cec8 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 6 Sep 2019 14:30:57 +0200 Subject: [PATCH 063/111] 62589: test environment comment --- config/environment.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config/environment.test.js b/config/environment.test.js index f4d625303f..6897e29aa7 100644 --- a/config/environment.test.js +++ b/config/environment.test.js @@ -1,3 +1,4 @@ +// This configuration is currently only being used for unit tests, end-to-end tests use environment.dev.ts module.exports = { }; From ddd1bb42cf4580913e7db4847cf5e62257dc0629 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 6 Sep 2019 08:38:48 -0500 Subject: [PATCH 064/111] Spelling fixes --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cff5a11c2c..ee3604ffc3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ env: # Install the latest docker-compose version for ci testing. # The default installation in travis is not compatible with the latest docker-compose file version. COMPOSE_VERSION: 1.24.1 - # The ci step will test the dpsace-angular code against DSpace REST. + # The ci step will test the dspace-angular code against DSpace REST. # Direct that step to utilize a DSpace REST service that has been started in docker. DSPACE_REST_HOST: localhost DSPACE_REST_PORT: 8080 @@ -25,7 +25,7 @@ install: - travis_retry yarn install before_script: - # The following line could be enabled to verify that the rest server is repsonding. + # The following line could be enabled to verify that the rest server is responding. # Currently, "yarn run build" takes enough time to run to allow the service to be available #- curl http://localhost:8080/ From e51838002518067b4b04cff129ed5dc2a27e144a Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 6 Sep 2019 16:07:37 +0200 Subject: [PATCH 065/111] fixed import issues --- src/app/app.component.spec.ts | 2 +- src/app/app.component.ts | 3 +-- src/app/app.module.ts | 2 +- src/app/shared/lang-switch/lang-switch.component.spec.ts | 2 +- src/app/shared/lang-switch/lang-switch.component.ts | 3 +-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index d6315ce4f6..5b78e3462f 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -44,8 +44,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import { RouteService } from './core/services/route.service'; import { MockActivatedRoute } from './shared/mocks/mock-active-router'; import { MockRouter } from './shared/mocks/mock-router'; -import { CookieService } from './shared/services/cookie.service'; import { MockCookieService } from './shared/mocks/mock-cookie.service'; +import { CookieService } from './core/services/cookie.service'; let comp: AppComponent; let fixture: ComponentFixture; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 0c6fd538fa..50baaf6e57 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -32,9 +32,8 @@ import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs'; import { slideSidebarPadding } from './shared/animations/slide'; import { HostWindowService } from './shared/host-window.service'; import { Theme } from '../config/theme.inferface'; -import { ClientCookieService } from './shared/services/client-cookie.service'; import { isNotEmpty } from './shared/empty.util'; -import { CookieService } from './shared/services/cookie.service'; +import { CookieService } from './core/services/cookie.service'; export const LANG_COOKIE = 'language_cookie'; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3781edf532..916788df8c 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -39,7 +39,7 @@ import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/e import { NavbarModule } from './navbar/navbar.module'; import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module'; import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module'; -import { ClientCookieService } from './shared/services/client-cookie.service'; +import { ClientCookieService } from './core/services/client-cookie.service'; export function getConfig() { return ENV_CONFIG; diff --git a/src/app/shared/lang-switch/lang-switch.component.spec.ts b/src/app/shared/lang-switch/lang-switch.component.spec.ts index 0f84901fbc..5b10578f77 100644 --- a/src/app/shared/lang-switch/lang-switch.component.spec.ts +++ b/src/app/shared/lang-switch/lang-switch.component.spec.ts @@ -7,8 +7,8 @@ import { GLOBAL_CONFIG } from '../../../config'; import {LangConfig} from '../../../config/lang-config.interface'; import {Observable, of} from 'rxjs'; import { By } from '@angular/platform-browser'; -import { CookieService } from '../services/cookie.service'; import { MockCookieService } from '../mocks/mock-cookie.service'; +import { CookieService } from '../../core/services/cookie.service'; // This test is completely independent from any message catalogs or keys in the codebase // The translation module is instantiated with these bogus messages that we aren't using anyway. diff --git a/src/app/shared/lang-switch/lang-switch.component.ts b/src/app/shared/lang-switch/lang-switch.component.ts index 3b490b98d4..e91ed5c9a2 100644 --- a/src/app/shared/lang-switch/lang-switch.component.ts +++ b/src/app/shared/lang-switch/lang-switch.component.ts @@ -2,9 +2,8 @@ import {Component, Inject, OnInit} from '@angular/core'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; import {TranslateService} from '@ngx-translate/core'; import {LangConfig} from '../../../config/lang-config.interface'; -import { ClientCookieService } from '../services/client-cookie.service'; import { LANG_COOKIE } from '../../app.component'; -import { CookieService } from '../services/cookie.service'; +import { CookieService } from '../../core/services/cookie.service'; @Component({ selector: 'ds-lang-switch', From 0b1bc9fe65cd1ef7173c4e3bb9d6fb2f8f3e9750 Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Fri, 6 Sep 2019 20:27:44 -0700 Subject: [PATCH 066/111] Use v2 compose files --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index ee3604ffc3..3eeb583ca4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,8 @@ before_install: - git clone https://github.com/DSpace-Labs/DSpace-Docker-Images.git install: - - docker-compose version - - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose/d7.travis.yml up -d + - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose-v2/d7.travis.ci.yml up -d + - 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/cli.ingest.yml run --rm dspace-cli - travis_retry yarn install before_script: @@ -30,7 +30,7 @@ before_script: #- curl http://localhost:8080/ after_script: - - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose/d7.travis.yml down + - docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose-v2/d7.travis.ci.yml down addons: apt: From 754abdd2ecee0df51d003370717df3832b7a7f4e Mon Sep 17 00:00:00 2001 From: Terry Brady Date: Fri, 6 Sep 2019 20:32:11 -0700 Subject: [PATCH 067/111] correct data load script --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3eeb583ca4..0a5f0dd5e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,8 +20,10 @@ before_install: - 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 DSpace-Docker-Images/docker-compose-files/dspace-compose-v2/d7.cli.yml -f DSpace-Docker-Images/docker-compose-files/dspace-compose-v2/cli.ingest.yml run --rm dspace-cli + # 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 - travis_retry yarn install before_script: From 5553d046ce78fe9730176f6a5af90a811fd02864 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 9 Sep 2019 17:37:14 +0200 Subject: [PATCH 068/111] 64644: Remove empty line to fix lint error --- .../search-filters/search-filter/search-filter.service.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.spec.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.spec.ts index fe89d182d4..aefa5c145f 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.spec.ts @@ -260,8 +260,6 @@ describe('SearchFilterService', () => { }); }); - - describe('when the getCurrentView method is called', () => { beforeEach(() => { spyOn(routeServiceStub, 'getQueryParameterValue'); From 6360d5a88c9d38b128902eb1c7610c93b83760b9 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 28 Aug 2019 11:02:05 -0700 Subject: [PATCH 069/111] Modified the log-in component to set the redirectUrl in AuthState (so the user is returned to the same page after login). --- src/app/shared/log-in/log-in.component.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 1291e6aa4c..6bdf7ad987 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -20,6 +20,7 @@ import { CoreState } from '../../core/core.reducers'; import { isNotEmpty } from '../empty.util'; import { fadeOut } from '../animations/fade'; import { AuthService } from '../../core/auth/auth.service'; +import {Router} from '@angular/router'; /** * /users/sign-in @@ -90,6 +91,7 @@ export class LogInComponent implements OnDestroy, OnInit { constructor( private authService: AuthService, private formBuilder: FormBuilder, + private router: Router, private store: Store ) { } @@ -182,6 +184,9 @@ export class LogInComponent implements OnDestroy, OnInit { email.trim(); password.trim(); + // add the current url to store for later redirect. + this.authService.setRedirectUrl(this.router.url); + // dispatch AuthenticationAction this.store.dispatch(new AuthenticateAction(email, password)); From db326a706cb5a8402012f7f974b884f69b49c203 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 28 Aug 2019 12:56:29 -0700 Subject: [PATCH 070/111] Fixed the unit test (and added a typedoc) --- .../shared/log-in/log-in.component.spec.ts | 29 +++++++++++++++++-- src/app/shared/log-in/log-in.component.ts | 1 + 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/app/shared/log-in/log-in.component.spec.ts b/src/app/shared/log-in/log-in.component.spec.ts index dd2aea35d5..38173a6a8b 100644 --- a/src/app/shared/log-in/log-in.component.spec.ts +++ b/src/app/shared/log-in/log-in.component.spec.ts @@ -3,7 +3,7 @@ import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing' import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; -import { Store, StoreModule } from '@ngrx/store'; +import { Store, StoreModule, select } from '@ngrx/store'; import { LogInComponent } from './log-in.component'; import { authReducer } from '../../core/auth/auth.reducer'; @@ -13,6 +13,9 @@ import { TranslateModule } from '@ngx-translate/core'; import { AuthService } from '../../core/auth/auth.service'; import { AuthServiceStub } from '../testing/auth-service-stub'; import { AppState } from '../../app.reducer'; +import {AppRoutingModule} from '../../app-routing.module'; +import {PageNotFoundComponent} from '../../pagenotfound/pagenotfound.component'; +import {APP_BASE_HREF} from '@angular/common'; describe('LogInComponent', () => { @@ -38,13 +41,16 @@ describe('LogInComponent', () => { FormsModule, ReactiveFormsModule, StoreModule.forRoot(authReducer), + AppRoutingModule, TranslateModule.forRoot() ], declarations: [ - LogInComponent + LogInComponent, + PageNotFoundComponent ], providers: [ - {provide: AuthService, useClass: AuthServiceStub} + {provide: AuthService, useClass: AuthServiceStub}, + {provide: APP_BASE_HREF, useValue: '/'} ], schemas: [ CUSTOM_ELEMENTS_SCHEMA @@ -95,6 +101,23 @@ describe('LogInComponent', () => { // verify Store.dispatch() is invoked expect(page.navigateSpy.calls.any()).toBe(true, 'Store.dispatch not invoked'); }); + + it('should set the redirect url', () => { + fixture.detectChanges(); + + // set FormControl values + component.form.controls.email.setValue('user'); + component.form.controls.password.setValue('password'); + + const authService: AuthService = TestBed.get(AuthService); + spyOn(authService, 'setRedirectUrl'); + + component.submit(); + + // the redirect url should be set upon submit + expect(authService.setRedirectUrl).toHaveBeenCalled(); + }); + }); /** diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 6bdf7ad987..f6df9ee389 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -86,6 +86,7 @@ export class LogInComponent implements OnDestroy, OnInit { * @constructor * @param {AuthService} authService * @param {FormBuilder} formBuilder + * @param {Router} router * @param {Store} store */ constructor( From c6156c5cbe009f3af4ff370eac8c6dfb0cbf680d Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 29 Aug 2019 16:08:46 -0700 Subject: [PATCH 071/111] Modified log-in component to set the redirect url only if one has not been set already. --- .../shared/log-in/log-in.component.spec.ts | 17 +++++++++++++ src/app/shared/log-in/log-in.component.ts | 24 +++++++++++-------- src/app/shared/testing/auth-service-stub.ts | 7 +++++- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/app/shared/log-in/log-in.component.spec.ts b/src/app/shared/log-in/log-in.component.spec.ts index 38173a6a8b..6992afa5ec 100644 --- a/src/app/shared/log-in/log-in.component.spec.ts +++ b/src/app/shared/log-in/log-in.component.spec.ts @@ -118,6 +118,23 @@ describe('LogInComponent', () => { expect(authService.setRedirectUrl).toHaveBeenCalled(); }); + it('should not set the redirect url because one already exists', () => { + fixture.detectChanges(); + + const authService: AuthService = TestBed.get(AuthService); + authService.setRedirectUrl('/submit') + + // set FormControl values + component.form.controls.email.setValue('user'); + component.form.controls.password.setValue('password'); + + spyOn(authService, 'setRedirectUrl'); + + component.submit(); + + expect(authService.setRedirectUrl).not.toHaveBeenCalled(); + }); + }); /** diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index f6df9ee389..13fa73bc4e 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -1,4 +1,4 @@ -import { filter, map, takeWhile } from 'rxjs/operators'; +import {filter, map, take, takeWhile, tap} from 'rxjs/operators'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -17,7 +17,7 @@ import { } from '../../core/auth/selectors'; import { CoreState } from '../../core/core.reducers'; -import { isNotEmpty } from '../empty.util'; +import {isEmpty, isNotEmpty} from '../empty.util'; import { fadeOut } from '../animations/fade'; import { AuthService } from '../../core/auth/auth.service'; import {Router} from '@angular/router'; @@ -185,13 +185,17 @@ export class LogInComponent implements OnDestroy, OnInit { email.trim(); password.trim(); - // add the current url to store for later redirect. - this.authService.setRedirectUrl(this.router.url); - - // dispatch AuthenticationAction - this.store.dispatch(new AuthenticateAction(email, password)); - - // clear form - this.form.reset(); + this.authService.getRedirectUrl().pipe( + take(1)). + subscribe((r) => { + // Set the redirect url if none exists. + if (isEmpty(r)) { + this.authService.setRedirectUrl(this.router.url) + } + // dispatch AuthenticationAction + this.store.dispatch(new AuthenticateAction(email, password)); + // clear form + this.form.reset(); + }); } } diff --git a/src/app/shared/testing/auth-service-stub.ts b/src/app/shared/testing/auth-service-stub.ts index a65923dcab..a6d24d5c8b 100644 --- a/src/app/shared/testing/auth-service-stub.ts +++ b/src/app/shared/testing/auth-service-stub.ts @@ -10,6 +10,7 @@ export class AuthServiceStub { token: AuthTokenInfo = new AuthTokenInfo('token_test'); private _tokenExpired = false; + private redirectUrl; constructor() { this.token.expires = Date.now() + (1000 * 60 * 60); @@ -88,7 +89,11 @@ export class AuthServiceStub { } setRedirectUrl(url: string) { - return; + this.redirectUrl = url; + } + + getRedirectUrl() { + return observableOf(this.redirectUrl); } public storeToken(token: AuthTokenInfo) { From fcaf01807cf3c4993244518337b180d76054bb70 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Tue, 3 Sep 2019 17:49:08 -0700 Subject: [PATCH 072/111] Modifed log-in component to use LOGIN_ROUTE when setting the redirect url. Also added check for mobile layout that forces setting of the redirect url. --- .../shared/log-in/log-in.component.spec.ts | 111 +++++++++++++++++- src/app/shared/log-in/log-in.component.ts | 55 ++++++--- src/app/shared/testing/router-events-stub.ts | 24 ++++ 3 files changed, 170 insertions(+), 20 deletions(-) create mode 100644 src/app/shared/testing/router-events-stub.ts diff --git a/src/app/shared/log-in/log-in.component.spec.ts b/src/app/shared/log-in/log-in.component.spec.ts index 6992afa5ec..adb7187757 100644 --- a/src/app/shared/log-in/log-in.component.spec.ts +++ b/src/app/shared/log-in/log-in.component.spec.ts @@ -16,6 +16,12 @@ import { AppState } from '../../app.reducer'; import {AppRoutingModule} from '../../app-routing.module'; import {PageNotFoundComponent} from '../../pagenotfound/pagenotfound.component'; import {APP_BASE_HREF} from '@angular/common'; +import {HostWindowService} from '../host-window.service'; +import {HostWindowServiceStub} from '../testing/host-window-service-stub'; +import {RouterStub} from '../testing/router-stub'; +import {NavigationEnd, Router} from '@angular/router'; +import {Observable, of as observableOf} from 'rxjs'; +import {RouterEventsStub} from '../testing/router-events-stub'; describe('LogInComponent', () => { @@ -50,7 +56,9 @@ describe('LogInComponent', () => { ], providers: [ {provide: AuthService, useClass: AuthServiceStub}, - {provide: APP_BASE_HREF, useValue: '/'} + {provide: APP_BASE_HREF, useValue: '/'}, + {provide: Router, useClass: RouterStub}, + {provide: HostWindowService, useValue: new HostWindowServiceStub(900) } ], schemas: [ CUSTOM_ELEMENTS_SCHEMA @@ -118,11 +126,13 @@ describe('LogInComponent', () => { expect(authService.setRedirectUrl).toHaveBeenCalled(); }); - it('should not set the redirect url because one already exists', () => { + it('should not set the redirect url to /login', () => { fixture.detectChanges(); + const router: Router = TestBed.get(Router); + router.navigateByUrl('/login') + const authService: AuthService = TestBed.get(AuthService); - authService.setRedirectUrl('/submit') // set FormControl values component.form.controls.email.setValue('user'); @@ -135,6 +145,101 @@ describe('LogInComponent', () => { expect(authService.setRedirectUrl).not.toHaveBeenCalled(); }); + it('should not set the redirect url on init', () => { + + const authService: AuthService = TestBed.get(AuthService); + spyOn(authService, 'setRedirectUrl'); + + fixture.detectChanges(); + expect(authService.setRedirectUrl).not.toHaveBeenCalledWith(); + + }); + +}); + +describe('LogInComponent on small screen', () => { + + let component: LogInComponent; + let fixture: ComponentFixture; + let page: Page; + let user: EPerson; + + const navEvents = observableOf( + new NavigationEnd(0, 'http://localhost:3000/home', 'http://localhost:3000/home'), + new NavigationEnd(1, 'http://localhost:3000/login', 'http://localhost:3000/login') + ); + + const authState = { + authenticated: false, + loaded: false, + loading: false, + }; + + beforeEach(() => { + user = EPersonMock; + }); + + beforeEach(async(() => { + // refine the test module by declaring the test component + TestBed.configureTestingModule({ + imports: [ + FormsModule, + ReactiveFormsModule, + StoreModule.forRoot(authReducer), + AppRoutingModule, + TranslateModule.forRoot() + ], + declarations: [ + LogInComponent, + PageNotFoundComponent + ], + providers: [ + {provide: AuthService, useClass: AuthServiceStub}, + {provide: APP_BASE_HREF, useValue: '/'}, + {provide: Router, useValue: new RouterEventsStub(navEvents)}, + {provide: HostWindowService, useValue: new HostWindowServiceStub(300) } + ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA + ] + }) + .compileComponents(); + + })); + + beforeEach(inject([Store], (store: Store) => { + store + .subscribe((state) => { + (state as any).core = Object.create({}); + (state as any).core.auth = authState; + }); + + // create component and test fixture + fixture = TestBed.createComponent(LogInComponent); + + // get test component from the fixture + component = fixture.componentInstance; + + // create page + page = new Page(component, fixture); + + // verify the fixture is stable (no pending tasks) + fixture.whenStable().then(() => { + page.addPageElements(); + }); + + })); + + it('should set the redirect url on init', () => { + + const authService: AuthService = TestBed.get(AuthService); + spyOn(authService, 'setRedirectUrl'); + + fixture.detectChanges(); + expect(authService.setRedirectUrl).toHaveBeenCalledWith('http://localhost:3000/home'); + + }); + }); /** diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 13fa73bc4e..0821ff1678 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -1,4 +1,4 @@ -import {filter, map, take, takeWhile, tap} from 'rxjs/operators'; +import {filter, map, pairwise, take, takeWhile, tap} from 'rxjs/operators'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -19,8 +19,9 @@ import { CoreState } from '../../core/core.reducers'; import {isEmpty, isNotEmpty} from '../empty.util'; import { fadeOut } from '../animations/fade'; -import { AuthService } from '../../core/auth/auth.service'; -import {Router} from '@angular/router'; +import {AuthService, LOGIN_ROUTE} from '../../core/auth/auth.service'; +import {NavigationEnd, Router, RoutesRecognized} from '@angular/router'; +import {HostWindowService} from '../host-window.service'; /** * /users/sign-in @@ -82,17 +83,21 @@ export class LogInComponent implements OnDestroy, OnInit { */ private alive = true; + private redirectUrl = LOGIN_ROUTE; + /** * @constructor * @param {AuthService} authService * @param {FormBuilder} formBuilder * @param {Router} router + * @param {HostWindowService} windowService * @param {Store} store */ constructor( private authService: AuthService, private formBuilder: FormBuilder, private router: Router, + private windowService: HostWindowService, private store: Store ) { } @@ -101,10 +106,22 @@ export class LogInComponent implements OnDestroy, OnInit { * Lifecycle hook that is called after data-bound properties of a directive are initialized. * @method ngOnInit */ - public ngOnInit() { - // set isAuthenticated + public ngOnInit() { // set isAuthenticated this.isAuthenticated = this.store.pipe(select(isAuthenticated)); + // for mobile login, set the redirect url to the previous route + this.windowService.isXs().pipe(take(1)) + .subscribe((isMobile) => { + if (isMobile) { + this.router.events.pipe( + filter((e: any) => e instanceof NavigationEnd), + pairwise() + ).subscribe((e: any) => { + this.setRedirectUrl(e[0].urlAfterRedirects); + }); + } + }); + // set formGroup this.form = this.formBuilder.group({ email: ['', Validators.required], @@ -185,17 +202,21 @@ export class LogInComponent implements OnDestroy, OnInit { email.trim(); password.trim(); - this.authService.getRedirectUrl().pipe( - take(1)). - subscribe((r) => { - // Set the redirect url if none exists. - if (isEmpty(r)) { - this.authService.setRedirectUrl(this.router.url) - } - // dispatch AuthenticationAction - this.store.dispatch(new AuthenticateAction(email, password)); - // clear form - this.form.reset(); - }); + this.setRedirectUrl(this.router.url); + // dispatch AuthenticationAction + this.store.dispatch(new AuthenticateAction(email, password)); + // clear form + this.form.reset(); + + } + + /** + * Sets the redirect url if not LOGIN_ROUTE + * @param url + */ + private setRedirectUrl(url: string) { + if (url !== this.redirectUrl) { + this.authService.setRedirectUrl(url) + } } } diff --git a/src/app/shared/testing/router-events-stub.ts b/src/app/shared/testing/router-events-stub.ts new file mode 100644 index 0000000000..313f6a2b4e --- /dev/null +++ b/src/app/shared/testing/router-events-stub.ts @@ -0,0 +1,24 @@ +import {Observable, of as observableOf} from 'rxjs'; +export class RouterEventsStub { + url: string; + routeReuseStrategy = {shouldReuseRoute: {}}; + //noinspection TypeScriptUnresolvedFunction + navigate = jasmine.createSpy('navigate'); + parseUrl = jasmine.createSpy('parseUrl'); + events = new Observable((observer) => { + this.eventArr.forEach((e) => { + observer.next(e); + }); + observer.complete(); + }); + eventArr: any; + + // Stub constructor takes array of event objects. + constructor( events: any = observableOf({})) { + this.eventArr = events; + } + + navigateByUrl(url): void { + this.url = url; + } +} From 54fc57d1f33cec34f1dd165d87c534651b72ff32 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 4 Sep 2019 12:56:37 -0700 Subject: [PATCH 073/111] Modified mobile log-in to use history as provided by the store. Minor typedoc and import updates. --- .../shared/log-in/log-in.component.spec.ts | 22 +++++++--------- src/app/shared/log-in/log-in.component.ts | 26 +++++++++++-------- src/app/shared/testing/route-service-stub.ts | 3 +++ src/app/shared/testing/router-events-stub.ts | 24 ----------------- 4 files changed, 28 insertions(+), 47 deletions(-) delete mode 100644 src/app/shared/testing/router-events-stub.ts diff --git a/src/app/shared/log-in/log-in.component.spec.ts b/src/app/shared/log-in/log-in.component.spec.ts index adb7187757..43004b79c7 100644 --- a/src/app/shared/log-in/log-in.component.spec.ts +++ b/src/app/shared/log-in/log-in.component.spec.ts @@ -19,9 +19,9 @@ import {APP_BASE_HREF} from '@angular/common'; import {HostWindowService} from '../host-window.service'; import {HostWindowServiceStub} from '../testing/host-window-service-stub'; import {RouterStub} from '../testing/router-stub'; -import {NavigationEnd, Router} from '@angular/router'; -import {Observable, of as observableOf} from 'rxjs'; -import {RouterEventsStub} from '../testing/router-events-stub'; +import {Router} from '@angular/router'; +import {RouteService} from '../services/route.service'; +import {routeServiceStub} from '../testing/route-service-stub'; describe('LogInComponent', () => { @@ -58,6 +58,7 @@ describe('LogInComponent', () => { {provide: AuthService, useClass: AuthServiceStub}, {provide: APP_BASE_HREF, useValue: '/'}, {provide: Router, useClass: RouterStub}, + {provide: RouteService, useValue: routeServiceStub }, {provide: HostWindowService, useValue: new HostWindowServiceStub(900) } ], schemas: [ @@ -164,11 +165,6 @@ describe('LogInComponent on small screen', () => { let page: Page; let user: EPerson; - const navEvents = observableOf( - new NavigationEnd(0, 'http://localhost:3000/home', 'http://localhost:3000/home'), - new NavigationEnd(1, 'http://localhost:3000/login', 'http://localhost:3000/login') - ); - const authState = { authenticated: false, loaded: false, @@ -196,7 +192,8 @@ describe('LogInComponent on small screen', () => { providers: [ {provide: AuthService, useClass: AuthServiceStub}, {provide: APP_BASE_HREF, useValue: '/'}, - {provide: Router, useValue: new RouterEventsStub(navEvents)}, + {provide: Router, useClass: RouterStub}, + {provide: RouteService, useValue: routeServiceStub }, {provide: HostWindowService, useValue: new HostWindowServiceStub(300) } ], schemas: [ @@ -231,12 +228,13 @@ describe('LogInComponent on small screen', () => { })); it('should set the redirect url on init', () => { - const authService: AuthService = TestBed.get(AuthService); spyOn(authService, 'setRedirectUrl'); - fixture.detectChanges(); - expect(authService.setRedirectUrl).toHaveBeenCalledWith('http://localhost:3000/home'); + // set FormControl values + component.form.controls.email.setValue('user'); + component.form.controls.password.setValue('password'); + expect(authService.setRedirectUrl).toHaveBeenCalledWith('collection/123'); }); diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 0821ff1678..5097b2a0c7 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -1,9 +1,9 @@ -import {filter, map, pairwise, take, takeWhile, tap} from 'rxjs/operators'; +import {filter, map, pairwise, take, takeUntil, takeWhile, tap} from 'rxjs/operators'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { select, Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; +import {Observable, Subject} from 'rxjs'; import { AuthenticateAction, ResetAuthenticationMessagesAction @@ -17,11 +17,12 @@ import { } from '../../core/auth/selectors'; import { CoreState } from '../../core/core.reducers'; -import {isEmpty, isNotEmpty} from '../empty.util'; +import {isNotEmpty} from '../empty.util'; import { fadeOut } from '../animations/fade'; import {AuthService, LOGIN_ROUTE} from '../../core/auth/auth.service'; -import {NavigationEnd, Router, RoutesRecognized} from '@angular/router'; +import {Router} from '@angular/router'; import {HostWindowService} from '../host-window.service'; +import {RouteService} from '../services/route.service'; /** * /users/sign-in @@ -89,6 +90,7 @@ export class LogInComponent implements OnDestroy, OnInit { * @constructor * @param {AuthService} authService * @param {FormBuilder} formBuilder + * @param {RouteService} routeService * @param {Router} router * @param {HostWindowService} windowService * @param {Store} store @@ -96,6 +98,7 @@ export class LogInComponent implements OnDestroy, OnInit { constructor( private authService: AuthService, private formBuilder: FormBuilder, + private routeService: RouteService, private router: Router, private windowService: HostWindowService, private store: Store @@ -106,18 +109,19 @@ export class LogInComponent implements OnDestroy, OnInit { * Lifecycle hook that is called after data-bound properties of a directive are initialized. * @method ngOnInit */ - public ngOnInit() { // set isAuthenticated + public ngOnInit() { + // set isAuthenticated this.isAuthenticated = this.store.pipe(select(isAuthenticated)); // for mobile login, set the redirect url to the previous route this.windowService.isXs().pipe(take(1)) .subscribe((isMobile) => { if (isMobile) { - this.router.events.pipe( - filter((e: any) => e instanceof NavigationEnd), - pairwise() - ).subscribe((e: any) => { - this.setRedirectUrl(e[0].urlAfterRedirects); + this.routeService.getHistory().pipe( + take(1) + ).subscribe((history) => { + const previousIndex = history.length - 2; + this.setRedirectUrl(history[previousIndex]); }); } }); @@ -211,7 +215,7 @@ export class LogInComponent implements OnDestroy, OnInit { } /** - * Sets the redirect url if not LOGIN_ROUTE + * Sets the redirect url if not equal to LOGIN_ROUTE * @param url */ private setRedirectUrl(url: string) { diff --git a/src/app/shared/testing/route-service-stub.ts b/src/app/shared/testing/route-service-stub.ts index e3cdd9d8c6..22e039a815 100644 --- a/src/app/shared/testing/route-service-stub.ts +++ b/src/app/shared/testing/route-service-stub.ts @@ -27,6 +27,9 @@ export const routeServiceStub: any = { }, getRouteDataValue: (param) => { return observableOf({}) + }, + getHistory: () => { + return observableOf(['/home','collection/123','/login']) } /* tslint:enable:no-empty */ }; diff --git a/src/app/shared/testing/router-events-stub.ts b/src/app/shared/testing/router-events-stub.ts deleted file mode 100644 index 313f6a2b4e..0000000000 --- a/src/app/shared/testing/router-events-stub.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {Observable, of as observableOf} from 'rxjs'; -export class RouterEventsStub { - url: string; - routeReuseStrategy = {shouldReuseRoute: {}}; - //noinspection TypeScriptUnresolvedFunction - navigate = jasmine.createSpy('navigate'); - parseUrl = jasmine.createSpy('parseUrl'); - events = new Observable((observer) => { - this.eventArr.forEach((e) => { - observer.next(e); - }); - observer.complete(); - }); - eventArr: any; - - // Stub constructor takes array of event objects. - constructor( events: any = observableOf({})) { - this.eventArr = events; - } - - navigateByUrl(url): void { - this.url = url; - } -} From f0813fcbc18901ed3c33d1892c4e56815a8e4c6f Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Wed, 4 Sep 2019 14:02:07 -0700 Subject: [PATCH 074/111] Added @Input to the log-in component that indicates whether the component is being used in the standalone login page or the drop-down. --- src/app/+login-page/login-page.component.html | 3 ++- .../auth-nav-menu.component.html | 3 ++- .../shared/log-in/log-in.component.spec.ts | 9 ++++---- src/app/shared/log-in/log-in.component.ts | 23 ++++++++----------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/app/+login-page/login-page.component.html b/src/app/+login-page/login-page.component.html index 6dcb11fbb0..84059877f4 100644 --- a/src/app/+login-page/login-page.component.html +++ b/src/app/+login-page/login-page.component.html @@ -3,7 +3,8 @@

{{"login.form.header" | translate}}

- +
diff --git a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html index b560283ad5..4df07880d8 100644 --- a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html +++ b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html @@ -3,7 +3,8 @@ diff --git a/src/app/shared/log-in/log-in.component.spec.ts b/src/app/shared/log-in/log-in.component.spec.ts index 43004b79c7..8ad423629d 100644 --- a/src/app/shared/log-in/log-in.component.spec.ts +++ b/src/app/shared/log-in/log-in.component.spec.ts @@ -58,8 +58,7 @@ describe('LogInComponent', () => { {provide: AuthService, useClass: AuthServiceStub}, {provide: APP_BASE_HREF, useValue: '/'}, {provide: Router, useClass: RouterStub}, - {provide: RouteService, useValue: routeServiceStub }, - {provide: HostWindowService, useValue: new HostWindowServiceStub(900) } + {provide: RouteService, useValue: routeServiceStub } ], schemas: [ CUSTOM_ELEMENTS_SCHEMA @@ -151,6 +150,8 @@ describe('LogInComponent', () => { const authService: AuthService = TestBed.get(AuthService); spyOn(authService, 'setRedirectUrl'); + component.isStandalonePage = false; + fixture.detectChanges(); expect(authService.setRedirectUrl).not.toHaveBeenCalledWith(); @@ -193,8 +194,7 @@ describe('LogInComponent on small screen', () => { {provide: AuthService, useClass: AuthServiceStub}, {provide: APP_BASE_HREF, useValue: '/'}, {provide: Router, useClass: RouterStub}, - {provide: RouteService, useValue: routeServiceStub }, - {provide: HostWindowService, useValue: new HostWindowServiceStub(300) } + {provide: RouteService, useValue: routeServiceStub } ], schemas: [ CUSTOM_ELEMENTS_SCHEMA @@ -230,6 +230,7 @@ describe('LogInComponent on small screen', () => { it('should set the redirect url on init', () => { const authService: AuthService = TestBed.get(AuthService); spyOn(authService, 'setRedirectUrl'); + component.isStandalonePage = true; fixture.detectChanges(); // set FormControl values component.form.controls.email.setValue('user'); diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 5097b2a0c7..3a4ff151bf 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -1,5 +1,5 @@ import {filter, map, pairwise, take, takeUntil, takeWhile, tap} from 'rxjs/operators'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import {Component, Input, OnDestroy, OnInit} from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { select, Store } from '@ngrx/store'; @@ -86,13 +86,14 @@ export class LogInComponent implements OnDestroy, OnInit { private redirectUrl = LOGIN_ROUTE; + @Input() isStandalonePage: boolean; + /** * @constructor * @param {AuthService} authService * @param {FormBuilder} formBuilder * @param {RouteService} routeService * @param {Router} router - * @param {HostWindowService} windowService * @param {Store} store */ constructor( @@ -100,7 +101,6 @@ export class LogInComponent implements OnDestroy, OnInit { private formBuilder: FormBuilder, private routeService: RouteService, private router: Router, - private windowService: HostWindowService, private store: Store ) { } @@ -114,17 +114,14 @@ export class LogInComponent implements OnDestroy, OnInit { this.isAuthenticated = this.store.pipe(select(isAuthenticated)); // for mobile login, set the redirect url to the previous route - this.windowService.isXs().pipe(take(1)) - .subscribe((isMobile) => { - if (isMobile) { - this.routeService.getHistory().pipe( - take(1) - ).subscribe((history) => { - const previousIndex = history.length - 2; - this.setRedirectUrl(history[previousIndex]); - }); - } + if (this.isStandalonePage) { + this.routeService.getHistory().pipe( + take(1) + ).subscribe((history) => { + const previousIndex = history.length - 2; + this.setRedirectUrl(history[previousIndex]); }); + } // set formGroup this.form = this.formBuilder.group({ From 0935bd4afd6902cf0f364ff0e200dbd7f3b9a882 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Thu, 5 Sep 2019 17:49:56 -0700 Subject: [PATCH 075/111] Proposed authentication service method that sets redirect url and dispatches auth request. --- src/app/core/auth/auth.service.spec.ts | 43 +++++- src/app/core/auth/auth.service.ts | 40 ++++-- .../shared/log-in/log-in.component.spec.ts | 131 ------------------ src/app/shared/log-in/log-in.component.ts | 35 +---- src/app/shared/testing/auth-service-stub.ts | 3 + src/app/shared/testing/route-service-stub.ts | 2 +- 6 files changed, 79 insertions(+), 175 deletions(-) diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts index ab2e6fd86b..6044e9ecb6 100644 --- a/src/app/core/auth/auth.service.spec.ts +++ b/src/app/core/auth/auth.service.spec.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Store, StoreModule } from '@ngrx/store'; import { REQUEST } from '@nguniversal/express-engine/tokens'; -import { of as observableOf } from 'rxjs'; +import {of, of as observableOf} from 'rxjs'; import { authReducer, AuthState } from './auth.reducer'; import { NativeWindowRef, NativeWindowService } from '../services/window.service'; @@ -23,11 +23,14 @@ import { AppState } from '../../app.reducer'; import { ClientCookieService } from '../services/client-cookie.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; +import {routeServiceStub} from '../../shared/testing/route-service-stub'; +import {RouteService} from '../services/route.service'; describe('AuthService test', () => { let mockStore: Store; let authService: AuthService; + let routeServiceMock: RouteService; let authRequest; let window; let routerStub; @@ -74,6 +77,7 @@ describe('AuthService test', () => { { provide: NativeWindowService, useValue: window }, { provide: REQUEST, useValue: {} }, { provide: Router, useValue: routerStub }, + { provide: RouteService, useValue: routeServiceStub }, { provide: ActivatedRoute, useValue: routeStub }, { provide: Store, useValue: mockStore }, { provide: RemoteDataBuildService, useValue: rdbService }, @@ -82,6 +86,8 @@ describe('AuthService test', () => { ], }); authService = TestBed.get(AuthService); + routeServiceMock = TestBed.get(RouteService); + spyOn(authService, 'setRedirectUrl'); }); it('should return the authentication status object when user credentials are correct', () => { @@ -124,6 +130,31 @@ describe('AuthService test', () => { expect(authService.logout.bind(null)).toThrow(); }); + it ('should dispatch authentication', () => { + authService.doAuthentication(true, '', ''); + expect(mockStore.dispatch).toHaveBeenCalled(); + }); + + it ('should set redirect url to previous page', () => { + spyOn(routeServiceMock, 'getHistory').and.callThrough(); + authService.doAuthentication(true, '', ''); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(authService.setRedirectUrl).toHaveBeenCalledWith('/collection/123'); + }); + + it ('should set redirect url to current page', () => { + spyOn(routeServiceMock, 'getHistory').and.callThrough(); + authService.doAuthentication(false, '', ''); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(authService.setRedirectUrl).toHaveBeenCalledWith('/home'); + }); + + it ('should not set redirect url to /login', () => { + spyOn(routeServiceMock, 'getHistory').and.returnValue(of(['/login', '/login'])); + authService.doAuthentication(true, '', ''); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(authService.setRedirectUrl).not.toHaveBeenCalled(); + }); }); describe('', () => { @@ -138,6 +169,7 @@ describe('AuthService test', () => { { provide: AuthRequestService, useValue: authRequest }, { provide: REQUEST, useValue: {} }, { provide: Router, useValue: routerStub }, + { provide: RouteService, useValue: routeServiceStub }, { provide: RemoteDataBuildService, useValue: rdbService }, CookieService, AuthService @@ -145,13 +177,13 @@ describe('AuthService test', () => { }).compileComponents(); })); - beforeEach(inject([CookieService, AuthRequestService, Store, Router], (cookieService: CookieService, authReqService: AuthRequestService, store: Store, router: Router) => { + beforeEach(inject([CookieService, AuthRequestService, Store, Router, RouteService], (cookieService: CookieService, authReqService: AuthRequestService, store: Store, router: Router, routeService: RouteService) => { store .subscribe((state) => { (state as any).core = Object.create({}); (state as any).core.auth = authenticatedState; }); - authService = new AuthService({}, window, undefined, authReqService, router, cookieService, store, rdbService); + authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService); })); it('should return true when user is logged in', () => { @@ -189,6 +221,7 @@ describe('AuthService test', () => { { provide: AuthRequestService, useValue: authRequest }, { provide: REQUEST, useValue: {} }, { provide: Router, useValue: routerStub }, + { provide: RouteService, useValue: routeServiceStub }, { provide: RemoteDataBuildService, useValue: rdbService }, ClientCookieService, CookieService, @@ -197,7 +230,7 @@ describe('AuthService test', () => { }).compileComponents(); })); - beforeEach(inject([ClientCookieService, AuthRequestService, Store, Router], (cookieService: ClientCookieService, authReqService: AuthRequestService, store: Store, router: Router) => { + beforeEach(inject([ClientCookieService, AuthRequestService, Store, Router, RouteService], (cookieService: ClientCookieService, authReqService: AuthRequestService, store: Store, router: Router, routeService: RouteService) => { const expiredToken: AuthTokenInfo = new AuthTokenInfo('test_token'); expiredToken.expires = Date.now() - (1000 * 60 * 60); authenticatedState = { @@ -212,7 +245,7 @@ describe('AuthService test', () => { (state as any).core = Object.create({}); (state as any).core.auth = authenticatedState; }); - authService = new AuthService({}, window, undefined, authReqService, router, cookieService, store, rdbService); + authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService); storage = (authService as any).storage; spyOn(storage, 'get'); spyOn(storage, 'remove'); diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 08c94b02f2..615fee5d56 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -22,6 +22,7 @@ import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth. import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import {RouteService} from '../services/route.service'; export const LOGIN_ROUTE = '/login'; export const LOGOUT_ROUTE = '/logout'; @@ -45,6 +46,7 @@ export class AuthService { protected authRequestService: AuthRequestService, @Optional() @Inject(RESPONSE) private response: any, protected router: Router, + protected routeService: RouteService, protected storage: CookieService, protected store: Store, protected rdbService: RemoteDataBuildService @@ -337,7 +339,7 @@ export class AuthService { /** * Redirect to the route navigated before the login */ - public redirectToPreviousUrl() { + public redirectToPreviousUrl(isStandalonePage: boolean) { this.getRedirectUrl().pipe( take(1)) .subscribe((redirectUrl) => { @@ -346,18 +348,39 @@ export class AuthService { this.clearRedirectUrl(); this.router.onSameUrlNavigation = 'reload'; const url = decodeURIComponent(redirectUrl); - this.router.navigateByUrl(url); - /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */ - // this._window.nativeWindow.location.href = url; + this.navigateToRedirectUrl(url); } else { - this.router.navigate(['/']); - /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */ - // this._window.nativeWindow.location.href = '/'; + // If redirectUrl is empty use history. + this.routeService.getHistory().pipe( + take(1) + ).subscribe((history) => { + let redirUrl; + if (isStandalonePage) { + // For standalone login pages, use the previous route. + redirUrl = history[history.length - 2] || ''; + } else { + redirUrl = history[history.length - 1] || ''; + } + this.navigateToRedirectUrl(redirUrl); + }); } - }) + }); } + private navigateToRedirectUrl(url: string) { + // in case the user navigates directly to /login (via bookmark, etc), or the route history is not found. + if (isEmpty(url) || url.startsWith(LOGIN_ROUTE)) { + this.router.navigate(['/']); + /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */ + // this._window.nativeWindow.location.href = '/'; + } else { + /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */ + // this._window.nativeWindow.location.href = url; + this.router.navigate([url]); + } + } + /** * Refresh route navigated */ @@ -400,4 +423,5 @@ export class AuthService { this.store.dispatch(new SetRedirectUrlAction('')); this.storage.remove(REDIRECT_COOKIE); } + } diff --git a/src/app/shared/log-in/log-in.component.spec.ts b/src/app/shared/log-in/log-in.component.spec.ts index 8ad423629d..6df992fff7 100644 --- a/src/app/shared/log-in/log-in.component.spec.ts +++ b/src/app/shared/log-in/log-in.component.spec.ts @@ -16,8 +16,6 @@ import { AppState } from '../../app.reducer'; import {AppRoutingModule} from '../../app-routing.module'; import {PageNotFoundComponent} from '../../pagenotfound/pagenotfound.component'; import {APP_BASE_HREF} from '@angular/common'; -import {HostWindowService} from '../host-window.service'; -import {HostWindowServiceStub} from '../testing/host-window-service-stub'; import {RouterStub} from '../testing/router-stub'; import {Router} from '@angular/router'; import {RouteService} from '../services/route.service'; @@ -110,135 +108,6 @@ describe('LogInComponent', () => { expect(page.navigateSpy.calls.any()).toBe(true, 'Store.dispatch not invoked'); }); - it('should set the redirect url', () => { - fixture.detectChanges(); - - // set FormControl values - component.form.controls.email.setValue('user'); - component.form.controls.password.setValue('password'); - - const authService: AuthService = TestBed.get(AuthService); - spyOn(authService, 'setRedirectUrl'); - - component.submit(); - - // the redirect url should be set upon submit - expect(authService.setRedirectUrl).toHaveBeenCalled(); - }); - - it('should not set the redirect url to /login', () => { - fixture.detectChanges(); - - const router: Router = TestBed.get(Router); - router.navigateByUrl('/login') - - const authService: AuthService = TestBed.get(AuthService); - - // set FormControl values - component.form.controls.email.setValue('user'); - component.form.controls.password.setValue('password'); - - spyOn(authService, 'setRedirectUrl'); - - component.submit(); - - expect(authService.setRedirectUrl).not.toHaveBeenCalled(); - }); - - it('should not set the redirect url on init', () => { - - const authService: AuthService = TestBed.get(AuthService); - spyOn(authService, 'setRedirectUrl'); - - component.isStandalonePage = false; - - fixture.detectChanges(); - expect(authService.setRedirectUrl).not.toHaveBeenCalledWith(); - - }); - -}); - -describe('LogInComponent on small screen', () => { - - let component: LogInComponent; - let fixture: ComponentFixture; - let page: Page; - let user: EPerson; - - const authState = { - authenticated: false, - loaded: false, - loading: false, - }; - - beforeEach(() => { - user = EPersonMock; - }); - - beforeEach(async(() => { - // refine the test module by declaring the test component - TestBed.configureTestingModule({ - imports: [ - FormsModule, - ReactiveFormsModule, - StoreModule.forRoot(authReducer), - AppRoutingModule, - TranslateModule.forRoot() - ], - declarations: [ - LogInComponent, - PageNotFoundComponent - ], - providers: [ - {provide: AuthService, useClass: AuthServiceStub}, - {provide: APP_BASE_HREF, useValue: '/'}, - {provide: Router, useClass: RouterStub}, - {provide: RouteService, useValue: routeServiceStub } - ], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ] - }) - .compileComponents(); - - })); - - beforeEach(inject([Store], (store: Store) => { - store - .subscribe((state) => { - (state as any).core = Object.create({}); - (state as any).core.auth = authState; - }); - - // create component and test fixture - fixture = TestBed.createComponent(LogInComponent); - - // get test component from the fixture - component = fixture.componentInstance; - - // create page - page = new Page(component, fixture); - - // verify the fixture is stable (no pending tasks) - fixture.whenStable().then(() => { - page.addPageElements(); - }); - - })); - - it('should set the redirect url on init', () => { - const authService: AuthService = TestBed.get(AuthService); - spyOn(authService, 'setRedirectUrl'); - component.isStandalonePage = true; - fixture.detectChanges(); - // set FormControl values - component.form.controls.email.setValue('user'); - component.form.controls.password.setValue('password'); - expect(authService.setRedirectUrl).toHaveBeenCalledWith('collection/123'); - - }); - }); /** diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index 3a4ff151bf..aa798548f4 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -1,9 +1,9 @@ -import {filter, map, pairwise, take, takeUntil, takeWhile, tap} from 'rxjs/operators'; +import {filter, map, takeWhile } from 'rxjs/operators'; import {Component, Input, OnDestroy, OnInit} from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { select, Store } from '@ngrx/store'; -import {Observable, Subject} from 'rxjs'; +import {Observable} from 'rxjs'; import { AuthenticateAction, ResetAuthenticationMessagesAction @@ -19,10 +19,8 @@ import { CoreState } from '../../core/core.reducers'; import {isNotEmpty} from '../empty.util'; import { fadeOut } from '../animations/fade'; -import {AuthService, LOGIN_ROUTE} from '../../core/auth/auth.service'; +import {AuthService} from '../../core/auth/auth.service'; import {Router} from '@angular/router'; -import {HostWindowService} from '../host-window.service'; -import {RouteService} from '../services/route.service'; /** * /users/sign-in @@ -84,8 +82,6 @@ export class LogInComponent implements OnDestroy, OnInit { */ private alive = true; - private redirectUrl = LOGIN_ROUTE; - @Input() isStandalonePage: boolean; /** @@ -99,7 +95,6 @@ export class LogInComponent implements OnDestroy, OnInit { constructor( private authService: AuthService, private formBuilder: FormBuilder, - private routeService: RouteService, private router: Router, private store: Store ) { @@ -113,16 +108,6 @@ export class LogInComponent implements OnDestroy, OnInit { // set isAuthenticated this.isAuthenticated = this.store.pipe(select(isAuthenticated)); - // for mobile login, set the redirect url to the previous route - if (this.isStandalonePage) { - this.routeService.getHistory().pipe( - take(1) - ).subscribe((history) => { - const previousIndex = history.length - 2; - this.setRedirectUrl(history[previousIndex]); - }); - } - // set formGroup this.form = this.formBuilder.group({ email: ['', Validators.required], @@ -156,7 +141,7 @@ export class LogInComponent implements OnDestroy, OnInit { takeWhile(() => this.alive), filter((authenticated) => authenticated)) .subscribe(() => { - this.authService.redirectToPreviousUrl(); + this.authService.redirectToPreviousUrl(this.isStandalonePage); } ); } @@ -203,21 +188,11 @@ export class LogInComponent implements OnDestroy, OnInit { email.trim(); password.trim(); - this.setRedirectUrl(this.router.url); // dispatch AuthenticationAction this.store.dispatch(new AuthenticateAction(email, password)); + // clear form this.form.reset(); - } - /** - * Sets the redirect url if not equal to LOGIN_ROUTE - * @param url - */ - private setRedirectUrl(url: string) { - if (url !== this.redirectUrl) { - this.authService.setRedirectUrl(url) - } - } } diff --git a/src/app/shared/testing/auth-service-stub.ts b/src/app/shared/testing/auth-service-stub.ts index a6d24d5c8b..c5f9d92a53 100644 --- a/src/app/shared/testing/auth-service-stub.ts +++ b/src/app/shared/testing/auth-service-stub.ts @@ -96,6 +96,9 @@ export class AuthServiceStub { return observableOf(this.redirectUrl); } + public doAuthentication(isStandalonePage, email, password) { + return; + } public storeToken(token: AuthTokenInfo) { return; } diff --git a/src/app/shared/testing/route-service-stub.ts b/src/app/shared/testing/route-service-stub.ts index 22e039a815..a493f10a13 100644 --- a/src/app/shared/testing/route-service-stub.ts +++ b/src/app/shared/testing/route-service-stub.ts @@ -29,7 +29,7 @@ export const routeServiceStub: any = { return observableOf({}) }, getHistory: () => { - return observableOf(['/home','collection/123','/login']) + return observableOf(['/home','/collection/123','/home']) } /* tslint:enable:no-empty */ }; From d9fb68dce97405c890c2bc15ba2164eff98b22bf Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Fri, 6 Sep 2019 12:18:28 -0700 Subject: [PATCH 076/111] Modified redirectToPreviousUrl to use the standalone page parameter if no redirect url is found in the store. Removed unused import that was causing merge conflict. Once again try to fix merge conflict. Added routeService to server module providers. Changed order of providers. Minor change to ServerAuthService to make method signature consistent with AuthService. Try adding RouteService to browser-app module to see if that fixes travis build. One more try at getting the CI build to work. Removed change to browser module. --- src/app/core/auth/auth.service.spec.ts | 57 ++++++++++--------- src/app/core/auth/server-auth.service.ts | 2 +- .../shared/log-in/log-in.component.spec.ts | 16 +----- src/app/shared/log-in/log-in.component.ts | 1 - src/modules/app/server-app.module.ts | 2 +- 5 files changed, 34 insertions(+), 44 deletions(-) diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts index 6044e9ecb6..380c50d64b 100644 --- a/src/app/core/auth/auth.service.spec.ts +++ b/src/app/core/auth/auth.service.spec.ts @@ -86,8 +86,6 @@ describe('AuthService test', () => { ], }); authService = TestBed.get(AuthService); - routeServiceMock = TestBed.get(RouteService); - spyOn(authService, 'setRedirectUrl'); }); it('should return the authentication status object when user credentials are correct', () => { @@ -130,31 +128,6 @@ describe('AuthService test', () => { expect(authService.logout.bind(null)).toThrow(); }); - it ('should dispatch authentication', () => { - authService.doAuthentication(true, '', ''); - expect(mockStore.dispatch).toHaveBeenCalled(); - }); - - it ('should set redirect url to previous page', () => { - spyOn(routeServiceMock, 'getHistory').and.callThrough(); - authService.doAuthentication(true, '', ''); - expect(routeServiceMock.getHistory).toHaveBeenCalled(); - expect(authService.setRedirectUrl).toHaveBeenCalledWith('/collection/123'); - }); - - it ('should set redirect url to current page', () => { - spyOn(routeServiceMock, 'getHistory').and.callThrough(); - authService.doAuthentication(false, '', ''); - expect(routeServiceMock.getHistory).toHaveBeenCalled(); - expect(authService.setRedirectUrl).toHaveBeenCalledWith('/home'); - }); - - it ('should not set redirect url to /login', () => { - spyOn(routeServiceMock, 'getHistory').and.returnValue(of(['/login', '/login'])); - authService.doAuthentication(true, '', ''); - expect(routeServiceMock.getHistory).toHaveBeenCalled(); - expect(authService.setRedirectUrl).not.toHaveBeenCalled(); - }); }); describe('', () => { @@ -247,9 +220,12 @@ describe('AuthService test', () => { }); authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService); storage = (authService as any).storage; + routeServiceMock = TestBed.get(RouteService); + routerStub = TestBed.get(Router); spyOn(storage, 'get'); spyOn(storage, 'remove'); spyOn(storage, 'set'); + })); it('should throw false when token is not valid', () => { @@ -271,5 +247,32 @@ describe('AuthService test', () => { expect(storage.remove).toHaveBeenCalled(); }); + it ('should set redirect url to previous page', () => { + spyOn(routeServiceMock, 'getHistory').and.callThrough(); + authService.redirectToPreviousUrl(true); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(routerStub.navigate).toHaveBeenCalledWith(['/collection/123']); + }); + + it ('should set redirect url to current page', () => { + spyOn(routeServiceMock, 'getHistory').and.callThrough(); + authService.redirectToPreviousUrl(false); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(routerStub.navigate).toHaveBeenCalledWith(['/home']); + }); + + it ('should redirect to / and not to /login', () => { + spyOn(routeServiceMock, 'getHistory').and.returnValue(of(['/login', '/login'])); + authService.redirectToPreviousUrl(true); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(routerStub.navigate).toHaveBeenCalledWith(['/']); + }); + + it ('should redirect to / when no redirect url is found', () => { + spyOn(routeServiceMock, 'getHistory').and.returnValue(of([''])); + authService.redirectToPreviousUrl(true); + expect(routeServiceMock.getHistory).toHaveBeenCalled(); + expect(routerStub.navigate).toHaveBeenCalledWith(['/']); + }); }); }); diff --git a/src/app/core/auth/server-auth.service.ts b/src/app/core/auth/server-auth.service.ts index c344683e38..50e3bc53e1 100644 --- a/src/app/core/auth/server-auth.service.ts +++ b/src/app/core/auth/server-auth.service.ts @@ -54,7 +54,7 @@ export class ServerAuthService extends AuthService { /** * Redirect to the route navigated before the login */ - public redirectToPreviousUrl() { + public redirectToPreviousUrl(isStandalonePage: boolean) { this.getRedirectUrl().pipe( take(1)) .subscribe((redirectUrl) => { diff --git a/src/app/shared/log-in/log-in.component.spec.ts b/src/app/shared/log-in/log-in.component.spec.ts index 6df992fff7..c3bee04d76 100644 --- a/src/app/shared/log-in/log-in.component.spec.ts +++ b/src/app/shared/log-in/log-in.component.spec.ts @@ -13,13 +13,6 @@ import { TranslateModule } from '@ngx-translate/core'; import { AuthService } from '../../core/auth/auth.service'; import { AuthServiceStub } from '../testing/auth-service-stub'; import { AppState } from '../../app.reducer'; -import {AppRoutingModule} from '../../app-routing.module'; -import {PageNotFoundComponent} from '../../pagenotfound/pagenotfound.component'; -import {APP_BASE_HREF} from '@angular/common'; -import {RouterStub} from '../testing/router-stub'; -import {Router} from '@angular/router'; -import {RouteService} from '../services/route.service'; -import {routeServiceStub} from '../testing/route-service-stub'; describe('LogInComponent', () => { @@ -45,18 +38,13 @@ describe('LogInComponent', () => { FormsModule, ReactiveFormsModule, StoreModule.forRoot(authReducer), - AppRoutingModule, TranslateModule.forRoot() ], declarations: [ - LogInComponent, - PageNotFoundComponent + LogInComponent ], providers: [ - {provide: AuthService, useClass: AuthServiceStub}, - {provide: APP_BASE_HREF, useValue: '/'}, - {provide: Router, useClass: RouterStub}, - {provide: RouteService, useValue: routeServiceStub } + {provide: AuthService, useClass: AuthServiceStub} ], schemas: [ CUSTOM_ELEMENTS_SCHEMA diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index aa798548f4..c7f67ea28b 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -88,7 +88,6 @@ export class LogInComponent implements OnDestroy, OnInit { * @constructor * @param {AuthService} authService * @param {FormBuilder} formBuilder - * @param {RouteService} routeService * @param {Router} router * @param {Store} store */ diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index bd3379c8de..dfe936df25 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -64,7 +64,7 @@ export function createTranslateLoader() { { provide: SubmissionService, useClass: ServerSubmissionService - }, + } ] }) export class ServerAppModule { From c3102b9d437f8d4aa2e0798d53fc21f2a2c85911 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Mon, 9 Sep 2019 12:15:09 -0700 Subject: [PATCH 077/111] A little cleanup in unit tests. --- src/app/shared/log-in/log-in.component.spec.ts | 2 +- src/app/shared/testing/auth-service-stub.ts | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/app/shared/log-in/log-in.component.spec.ts b/src/app/shared/log-in/log-in.component.spec.ts index c3bee04d76..13f9e5369a 100644 --- a/src/app/shared/log-in/log-in.component.spec.ts +++ b/src/app/shared/log-in/log-in.component.spec.ts @@ -3,7 +3,7 @@ import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing' import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { By } from '@angular/platform-browser'; -import { Store, StoreModule, select } from '@ngrx/store'; +import { Store, StoreModule } from '@ngrx/store'; import { LogInComponent } from './log-in.component'; import { authReducer } from '../../core/auth/auth.reducer'; diff --git a/src/app/shared/testing/auth-service-stub.ts b/src/app/shared/testing/auth-service-stub.ts index c5f9d92a53..a6d24d5c8b 100644 --- a/src/app/shared/testing/auth-service-stub.ts +++ b/src/app/shared/testing/auth-service-stub.ts @@ -96,9 +96,6 @@ export class AuthServiceStub { return observableOf(this.redirectUrl); } - public doAuthentication(isStandalonePage, email, password) { - return; - } public storeToken(token: AuthTokenInfo) { return; } From 6425f6e7d1542c08c3ab0d9d187e1158d29eb0e4 Mon Sep 17 00:00:00 2001 From: Michael W Spalti Date: Mon, 9 Sep 2019 13:33:51 -0700 Subject: [PATCH 078/111] Removed the router injection that slipped into auth service during rebase, test now passes. --- src/app/shared/log-in/log-in.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/shared/log-in/log-in.component.ts b/src/app/shared/log-in/log-in.component.ts index c7f67ea28b..a6c26381e4 100644 --- a/src/app/shared/log-in/log-in.component.ts +++ b/src/app/shared/log-in/log-in.component.ts @@ -94,7 +94,6 @@ export class LogInComponent implements OnDestroy, OnInit { constructor( private authService: AuthService, private formBuilder: FormBuilder, - private router: Router, private store: Store ) { } From 154fc83930d0f64d198641e5565e262741807579 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 12 Sep 2019 17:14:19 +0200 Subject: [PATCH 079/111] fix typo --- resources/i18n/en.json5 | 2 +- src/app/shared/uploader/uploader.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 8f8ad1c272..d36932dcd9 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -775,5 +775,5 @@ "uploader.drag-message": "Drag & Drop your files here", "uploader.or": ", or", "uploader.processing": "Processing", - "uploader.queue-lenght": "Queue length", + "uploader.queue-length": "Queue length", } diff --git a/src/app/shared/uploader/uploader.component.html b/src/app/shared/uploader/uploader.component.html index 5168404c83..9d994313c6 100644 --- a/src/app/shared/uploader/uploader.component.html +++ b/src/app/shared/uploader/uploader.component.html @@ -29,7 +29,7 @@
- {{'uploader.queue-lenght' | translate}}: {{ uploader?.queue?.length }} | {{ uploader?.queue[0]?.file.name }} + {{'uploader.queue-length' | translate}}: {{ uploader?.queue?.length }} | {{ uploader?.queue[0]?.file.name }}