From 819610476ef91fcee5748d3f49788ce79e2c86c6 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 20 Jul 2018 16:04:02 +0200 Subject: [PATCH 01/57] 54472: Intermediate commit --- .../create-community-page.component.html | 0 .../create-community-page.component.ts | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 src/app/+community-page/create-community-page/create-community-page.component.html create mode 100644 src/app/+community-page/create-community-page/create-community-page.component.ts diff --git a/src/app/+community-page/create-community-page/create-community-page.component.html b/src/app/+community-page/create-community-page/create-community-page.component.html new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..ffc6642cdf --- /dev/null +++ b/src/app/+community-page/create-community-page/create-community-page.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-create-community', + templateUrl: './create-community-page.component.html' +}) +export class CreateCommunityPageComponent { + +} From 0da645931cf5eea983abd805c5547189d10f0312 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 23 Jul 2018 14:29:32 +0200 Subject: [PATCH 02/57] 54472: Create Community page --- resources/i18n/en.json | 15 ++++++++++ .../community-form.component.html | 27 +++++++++++++++++ .../community-form.component.scss | 7 +++++ .../community-form.component.ts | 30 +++++++++++++++++++ .../community-page-routing.module.ts | 2 ++ .../+community-page/community-page.module.ts | 4 +++ .../create-community-page.component.html | 8 +++++ .../create-community-page.component.scss | 1 + .../create-community-page.component.ts | 5 ++++ src/app/core/comcol/comcol.service.ts | 0 10 files changed, 99 insertions(+) create mode 100644 src/app/+community-page/community-form/community-form.component.html create mode 100644 src/app/+community-page/community-form/community-form.component.scss create mode 100644 src/app/+community-page/community-form/community-form.component.ts create mode 100644 src/app/+community-page/create-community-page/create-community-page.component.scss create mode 100644 src/app/core/comcol/comcol.service.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index ba70b87e12..c0a213fe1b 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -22,6 +22,21 @@ }, "sub-collection-list": { "head": "Collections of this Community" + }, + "edit": { + "name": "Name", + "description": "Short Description", + "introductory": "Introductory text (HTML)", + "copyright": "Copyright text (HTML)", + "news": "News (HTML)", + "submit": "Submit", + "cancel": "Cancel", + "required": { + "name": "Please enter a community name" + } + }, + "create": { + "head": "Create a Community" } }, "item": { diff --git a/src/app/+community-page/community-form/community-form.component.html b/src/app/+community-page/community-form/community-form.component.html new file mode 100644 index 0000000000..bc45b582ac --- /dev/null +++ b/src/app/+community-page/community-form/community-form.component.html @@ -0,0 +1,27 @@ +
+
+ + +
{{ 'community.edit.required.name' | translate }}
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/src/app/+community-page/community-form/community-form.component.scss b/src/app/+community-page/community-form/community-form.component.scss new file mode 100644 index 0000000000..d5811186e7 --- /dev/null +++ b/src/app/+community-page/community-form/community-form.component.scss @@ -0,0 +1,7 @@ +@import '../../../styles/variables.scss'; + +// temporary fix for bootstrap 4 beta btn color issue +.btn-secondary { + background-color: $input-bg; + color: $input-color; +} diff --git a/src/app/+community-page/community-form/community-form.component.ts b/src/app/+community-page/community-form/community-form.component.ts new file mode 100644 index 0000000000..64d08ab862 --- /dev/null +++ b/src/app/+community-page/community-form/community-form.component.ts @@ -0,0 +1,30 @@ +import { Component, EventEmitter, Output } from '@angular/core'; +import { isNotEmpty } from '../../shared/empty.util'; + +@Component({ + selector: 'ds-community-form', + styleUrls: ['./community-form.component.scss'], + templateUrl: './community-form.component.html' +}) +export class CommunityFormComponent { + + name: string; + description: string; + introductory: string; + copyright: string; + news: string; + + nameRequiredError = false; + + @Output() submitted: EventEmitter = new EventEmitter(); + + onSubmit(data: any) { + if (isNotEmpty(data.name)) { + this.submitted.emit(data); + this.nameRequiredError = false; + } else { + this.nameRequiredError = true; + } + } + +} diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index 6fd5cc8cb5..249b01ea18 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -2,10 +2,12 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommunityPageComponent } from './community-page.component'; +import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; @NgModule({ imports: [ RouterModule.forChild([ + { path: 'create', component: CreateCommunityPageComponent }, { path: ':id', component: CommunityPageComponent, pathMatch: 'full' } ]) ] diff --git a/src/app/+community-page/community-page.module.ts b/src/app/+community-page/community-page.module.ts index e00c3910c5..292e6aaf9c 100644 --- a/src/app/+community-page/community-page.module.ts +++ b/src/app/+community-page/community-page.module.ts @@ -6,6 +6,8 @@ import { SharedModule } from '../shared/shared.module'; import { CommunityPageComponent } from './community-page.component'; import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component'; import { CommunityPageRoutingModule } from './community-page-routing.module'; +import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; +import { CommunityFormComponent } from './community-form/community-form.component'; @NgModule({ imports: [ @@ -16,6 +18,8 @@ import { CommunityPageRoutingModule } from './community-page-routing.module'; declarations: [ CommunityPageComponent, CommunityPageSubCollectionListComponent, + CreateCommunityPageComponent, + CommunityFormComponent ] }) export class CommunityPageModule { diff --git a/src/app/+community-page/create-community-page/create-community-page.component.html b/src/app/+community-page/create-community-page/create-community-page.component.html index e69de29bb2..ea270db92b 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.html +++ b/src/app/+community-page/create-community-page/create-community-page.component.html @@ -0,0 +1,8 @@ +
+
+
+ +
+
+ +
diff --git a/src/app/+community-page/create-community-page/create-community-page.component.scss b/src/app/+community-page/create-community-page/create-community-page.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/+community-page/create-community-page/create-community-page.component.scss @@ -0,0 +1 @@ + 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 ffc6642cdf..db2e4f25fa 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 @@ -2,8 +2,13 @@ import { Component } from '@angular/core'; @Component({ selector: 'ds-create-community', + styleUrls: ['./create-community-page.component.scss'], templateUrl: './create-community-page.component.html' }) export class CreateCommunityPageComponent { + onSubmit(data: any) { + console.log('yay, made it with name: ' + data.name); + } + } diff --git a/src/app/core/comcol/comcol.service.ts b/src/app/core/comcol/comcol.service.ts new file mode 100644 index 0000000000..e69de29bb2 From aa73e874a57ec590718ef137c8aa15360960410b Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 8 Aug 2018 13:49:34 +0200 Subject: [PATCH 03/57] 54472: Create method in comcol-data-service intermediate commit --- .../create-community-page.component.ts | 5 ++++- src/app/core/comcol/comcol.service.ts | 0 src/app/core/data/comcol-data.service.ts | 19 +++++++++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) delete mode 100644 src/app/core/comcol/comcol.service.ts 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 db2e4f25fa..b49ea1bbfa 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,4 +1,5 @@ import { Component } from '@angular/core'; +import { Community } from '../../core/shared/community.model'; @Component({ selector: 'ds-create-community', @@ -8,7 +9,9 @@ import { Component } from '@angular/core'; export class CreateCommunityPageComponent { onSubmit(data: any) { - console.log('yay, made it with name: ' + data.name); + Object.assign(new Community(), { + // TODO: Create community object to add to rest + }); } } diff --git a/src/app/core/comcol/comcol.service.ts b/src/app/core/comcol/comcol.service.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 112afa0bc8..5b9f10b957 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -1,5 +1,5 @@ import { Observable } from 'rxjs/Observable'; -import { isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { isEmpty, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { NormalizedCommunity } from '../cache/models/normalized-community.model'; import { CacheableObject } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -8,9 +8,14 @@ import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { CommunityDataService } from './community-data.service'; import { DataService } from './data.service'; -import { FindByIDRequest } from './request.models'; +import { FindByIDRequest, PutRequest } from './request.models'; import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { DSpaceObject } from '../shared/dspace-object.model'; +import { Community } from '../shared/community.model'; +import { Collection } from '../shared/collection.model'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { configureRequest } from '../shared/operators'; export abstract class ComColDataService extends DataService { protected abstract cds: CommunityDataService; @@ -56,4 +61,14 @@ export abstract class ComColDataService new PutRequest(this.requestService.generateRequestId(), endpointURL, comcol)), + configureRequest(this.requestService) + ); + } + } From 79a3788b3bbe03fd656909ee5612c57189d006ca Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 10 Aug 2018 15:05:22 +0200 Subject: [PATCH 04/57] 54472: Intermediate commit --- .../create-community-page.component.ts | 12 ++++++++++-- src/app/core/data/collection-data.service.ts | 9 ++++++++- src/app/core/data/comcol-data.service.ts | 17 +++++++++++++++-- src/app/core/data/community-data.service.ts | 8 +++++++- 4 files changed, 40 insertions(+), 6 deletions(-) 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 b49ea1bbfa..255d88c21b 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,5 +1,8 @@ import { Component } from '@angular/core'; import { Community } from '../../core/shared/community.model'; +import { ComColDataService } from '../../core/data/comcol-data.service'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { CommunityDataService } from '../../core/data/community-data.service'; @Component({ selector: 'ds-create-community', @@ -8,10 +11,15 @@ import { Community } from '../../core/shared/community.model'; }) export class CreateCommunityPageComponent { + public constructor(private communityDataService: CommunityDataService) { + + } + onSubmit(data: any) { - Object.assign(new Community(), { - // TODO: Create community object to add to rest + const community = Object.assign(new Community(), { + name: data.name }); + this.communityDataService.create(community); } } diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 7d1e463dbe..6e06a600cf 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -11,6 +11,8 @@ import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { AuthService } from '../auth/auth.service'; +import { Community } from '../shared/community.model'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -23,8 +25,13 @@ export class CollectionDataService extends ComColDataService, protected cds: CommunityDataService, protected objectCache: ObjectCacheService, - protected halService: HALEndpointService + protected halService: HALEndpointService, + protected authService: AuthService ) { super(); } + + getName(collection: Collection) { + return collection.name; + } } diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 5b9f10b957..8c912b4353 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -8,7 +8,7 @@ import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { CommunityDataService } from './community-data.service'; import { DataService } from './data.service'; -import { FindByIDRequest, PutRequest } from './request.models'; +import { FindByIDRequest, PostRequest, PutRequest } from './request.models'; import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { DSpaceObject } from '../shared/dspace-object.model'; @@ -16,11 +16,15 @@ import { Community } from '../shared/community.model'; import { Collection } from '../shared/collection.model'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { configureRequest } from '../shared/operators'; +import { AuthService } from '../auth/auth.service'; +import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; +import { HttpHeaders } from '@angular/common/http'; export abstract class ComColDataService extends DataService { protected abstract cds: CommunityDataService; protected abstract objectCache: ObjectCacheService; protected abstract halService: HALEndpointService; + protected abstract authService: AuthService; /** * Get the scoped endpoint URL by fetching the object with @@ -66,9 +70,18 @@ export abstract class ComColDataService new PutRequest(this.requestService.generateRequestId(), endpointURL, comcol)), + map((endpointURL: string) => { + const options: HttpOptions = Object.create({}); + const headers = new HttpHeaders(); + headers.append('Authentication', this.authService.buildAuthHeader()); + options.headers = headers; + console.log(options); + return new PostRequest(this.requestService.generateRequestId(), endpointURL + '?name=' + this.getName(comcol), options); + }), configureRequest(this.requestService) ); } + abstract getName(comcol: TDomain): string; + } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 88ad3a5287..e02c9bc033 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -11,6 +11,7 @@ import { Community } from '../shared/community.model'; import { ComColDataService } from './comcol-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { AuthService } from '../auth/auth.service'; @Injectable() export class CommunityDataService extends ComColDataService { @@ -23,7 +24,8 @@ export class CommunityDataService extends ComColDataService, protected objectCache: ObjectCacheService, - protected halService: HALEndpointService + protected halService: HALEndpointService, + protected authService: AuthService ) { super(); } @@ -31,4 +33,8 @@ export class CommunityDataService extends ComColDataService Date: Fri, 10 Aug 2018 16:52:13 +0200 Subject: [PATCH 05/57] 54472: Post request on submit and collection create page --- resources/i18n/en.json | 17 +++++++++ .../collection-form.component.html | 35 +++++++++++++++++++ .../collection-form.component.scss | 7 ++++ .../collection-form.component.ts | 32 +++++++++++++++++ .../collection-page-routing.module.ts | 2 ++ .../collection-page.module.ts | 4 +++ .../create-collection-page.component.html | 8 +++++ .../create-collection-page.component.scss | 1 + .../create-collection-page.component.ts | 27 ++++++++++++++ src/app/core/data/collection-data.service.ts | 5 +-- src/app/core/data/comcol-data.service.ts | 7 ++-- src/app/core/data/community-data.service.ts | 5 +-- 12 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 src/app/+collection-page/collection-form/collection-form.component.html create mode 100644 src/app/+collection-page/collection-form/collection-form.component.scss create mode 100644 src/app/+collection-page/collection-form/collection-form.component.ts create mode 100644 src/app/+collection-page/create-collection-page/create-collection-page.component.html create mode 100644 src/app/+collection-page/create-collection-page/create-collection-page.component.scss create mode 100644 src/app/+collection-page/create-collection-page/create-collection-page.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index c0a213fe1b..963dfae80f 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -13,6 +13,23 @@ "head": "Recent Submissions" } } + }, + "edit": { + "name": "Name", + "description": "Short Description", + "introductory": "Introductory text (HTML)", + "copyright": "Copyright text (HTML)", + "news": "News (HTML)", + "license": "License", + "provenance": "Provenance", + "submit": "Submit", + "cancel": "Cancel", + "required": { + "name": "Please enter a collection name" + } + }, + "create": { + "head": "Create a Collection" } }, "community": { diff --git a/src/app/+collection-page/collection-form/collection-form.component.html b/src/app/+collection-page/collection-form/collection-form.component.html new file mode 100644 index 0000000000..307d7406ed --- /dev/null +++ b/src/app/+collection-page/collection-form/collection-form.component.html @@ -0,0 +1,35 @@ +
+
+ + +
{{ 'collection.edit.required.name' | translate }}
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
diff --git a/src/app/+collection-page/collection-form/collection-form.component.scss b/src/app/+collection-page/collection-form/collection-form.component.scss new file mode 100644 index 0000000000..d5811186e7 --- /dev/null +++ b/src/app/+collection-page/collection-form/collection-form.component.scss @@ -0,0 +1,7 @@ +@import '../../../styles/variables.scss'; + +// temporary fix for bootstrap 4 beta btn color issue +.btn-secondary { + background-color: $input-bg; + color: $input-color; +} diff --git a/src/app/+collection-page/collection-form/collection-form.component.ts b/src/app/+collection-page/collection-form/collection-form.component.ts new file mode 100644 index 0000000000..c51633dfb6 --- /dev/null +++ b/src/app/+collection-page/collection-form/collection-form.component.ts @@ -0,0 +1,32 @@ +import { Component, EventEmitter, Output } from '@angular/core'; +import { isNotEmpty } from '../../shared/empty.util'; + +@Component({ + selector: 'ds-collection-form', + styleUrls: ['./collection-form.component.scss'], + templateUrl: './collection-form.component.html' +}) +export class CollectionFormComponent { + + name: string; + description: string; + introductory: string; + copyright: string; + news: string; + license: string; + provenance: string; + + nameRequiredError = false; + + @Output() submitted: EventEmitter = new EventEmitter(); + + onSubmit(data: any) { + if (isNotEmpty(data.name)) { + this.submitted.emit(data); + this.nameRequiredError = false; + } else { + this.nameRequiredError = true; + } + } + +} diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index c886aa655c..1bd53dd2b3 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -2,10 +2,12 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CollectionPageComponent } from './collection-page.component'; +import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component'; @NgModule({ imports: [ RouterModule.forChild([ + { path: 'create', component: CreateCollectionPageComponent }, { path: ':id', component: CollectionPageComponent, pathMatch: 'full' } ]) ] diff --git a/src/app/+collection-page/collection-page.module.ts b/src/app/+collection-page/collection-page.module.ts index d0bc918b22..3ce3b66d05 100644 --- a/src/app/+collection-page/collection-page.module.ts +++ b/src/app/+collection-page/collection-page.module.ts @@ -5,6 +5,8 @@ import { SharedModule } from '../shared/shared.module'; import { CollectionPageComponent } from './collection-page.component'; import { CollectionPageRoutingModule } from './collection-page-routing.module'; +import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component'; +import { CollectionFormComponent } from './collection-form/collection-form.component'; @NgModule({ imports: [ @@ -14,6 +16,8 @@ import { CollectionPageRoutingModule } from './collection-page-routing.module'; ], declarations: [ CollectionPageComponent, + CreateCollectionPageComponent, + CollectionFormComponent ] }) export class CollectionPageModule { diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.html b/src/app/+collection-page/create-collection-page/create-collection-page.component.html new file mode 100644 index 0000000000..53a55571f6 --- /dev/null +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.html @@ -0,0 +1,8 @@ +
+
+
+ +
+
+ +
diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.scss b/src/app/+collection-page/create-collection-page/create-collection-page.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.scss @@ -0,0 +1 @@ + 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 new file mode 100644 index 0000000000..689bd30dfc --- /dev/null +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.ts @@ -0,0 +1,27 @@ +import { Component } from '@angular/core'; +import { Community } from '../../core/shared/community.model'; +import { ComColDataService } from '../../core/data/comcol-data.service'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { Collection } from '../../core/shared/collection.model'; + +@Component({ + selector: 'ds-create-collection', + styleUrls: ['./create-collection-page.component.scss'], + templateUrl: './create-collection-page.component.html' +}) +export class CreateCollectionPageComponent { + + public constructor(private collectionDataService: CollectionDataService) { + + } + + onSubmit(data: any) { + const collection = Object.assign(new Collection(), { + name: data.name + }); + this.collectionDataService.create(collection); + } + +} diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 6e06a600cf..765cba0a8b 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -31,7 +31,8 @@ export class CollectionDataService extends ComColDataService + diff --git a/src/app/+collection-page/collection-form/collection-form.component.ts b/src/app/+collection-page/collection-form/collection-form.component.ts index c51633dfb6..4238eb3811 100644 --- a/src/app/+collection-page/collection-form/collection-form.component.ts +++ b/src/app/+collection-page/collection-form/collection-form.component.ts @@ -1,5 +1,6 @@ import { Component, EventEmitter, Output } from '@angular/core'; import { isNotEmpty } from '../../shared/empty.util'; +import { Location } from '@angular/common'; @Component({ selector: 'ds-collection-form', @@ -20,6 +21,10 @@ export class CollectionFormComponent { @Output() submitted: EventEmitter = new EventEmitter(); + public constructor(private location: Location) { + + } + onSubmit(data: any) { if (isNotEmpty(data.name)) { this.submitted.emit(data); @@ -29,4 +34,8 @@ export class CollectionFormComponent { } } + cancel() { + this.location.back(); + } + } diff --git a/src/app/+community-page/community-form/community-form.component.html b/src/app/+community-page/community-form/community-form.component.html index bc45b582ac..1bb5e97ec1 100644 --- a/src/app/+community-page/community-form/community-form.component.html +++ b/src/app/+community-page/community-form/community-form.component.html @@ -21,7 +21,7 @@
- +
diff --git a/src/app/+community-page/community-form/community-form.component.ts b/src/app/+community-page/community-form/community-form.component.ts index 64d08ab862..d7797028b7 100644 --- a/src/app/+community-page/community-form/community-form.component.ts +++ b/src/app/+community-page/community-form/community-form.component.ts @@ -1,5 +1,6 @@ import { Component, EventEmitter, Output } from '@angular/core'; import { isNotEmpty } from '../../shared/empty.util'; +import { Location } from '@angular/common'; @Component({ selector: 'ds-community-form', @@ -18,6 +19,10 @@ export class CommunityFormComponent { @Output() submitted: EventEmitter = new EventEmitter(); + public constructor(private location: Location) { + + } + onSubmit(data: any) { if (isNotEmpty(data.name)) { this.submitted.emit(data); @@ -27,4 +32,8 @@ export class CommunityFormComponent { } } + cancel() { + this.location.back(); + } + } From dfa42acab5f691904c71bde7c41470a72510cea8 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 20 Aug 2018 16:09:42 +0200 Subject: [PATCH 07/57] 54472: Create community/collection with parent community + error messages --- resources/i18n/en.json | 6 ++-- .../create-collection-page.component.html | 9 ++++- .../create-collection-page.component.ts | 34 +++++++++++++++++-- .../create-community-page.component.html | 9 ++++- .../create-community-page.component.ts | 34 +++++++++++++++++-- src/app/core/data/collection-data.service.ts | 4 --- src/app/core/data/comcol-data.service.ts | 28 ++++++++++----- src/app/core/data/community-data.service.ts | 4 --- 8 files changed, 103 insertions(+), 25 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 963dfae80f..af9d1a08c8 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -29,7 +29,8 @@ } }, "create": { - "head": "Create a Collection" + "head": "Create a Collection", + "sub-head": "Create a Collection for Community {{ parent }}" } }, "community": { @@ -53,7 +54,8 @@ } }, "create": { - "head": "Create a Community" + "head": "Create a Community", + "sub-head": "Create a Sub-Community for Community {{ parent }}" } }, "item": { diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.html b/src/app/+collection-page/create-collection-page/create-collection-page.component.html index 53a55571f6..9cf01f7c88 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.html +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.html @@ -1,8 +1,15 @@
- + + +

{{ 'collection.create.sub-head' | translate:{ parent: community.name } }}

+
+
+ {{error.statusCode}} +

{{error.errorMessage}}

+
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 689bd30dfc..2e8d9b557d 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 @@ -5,6 +5,13 @@ import { NormalizedCommunity } from '../../core/cache/models/normalized-communit import { CommunityDataService } from '../../core/data/community-data.service'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { Collection } from '../../core/shared/collection.model'; +import { RouteService } from '../../shared/services/route.service'; +import { Router } from '@angular/router'; +import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; +import { Observable } from 'rxjs/Observable'; +import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; +import { map } from 'rxjs/operators'; +import { RemoteData } from '../../core/data/remote-data'; @Component({ selector: 'ds-create-collection', @@ -13,15 +20,38 @@ import { Collection } from '../../core/shared/collection.model'; }) export class CreateCollectionPageComponent { - public constructor(private collectionDataService: CollectionDataService) { + private error$: Observable; + private parentUUID$: Observable; + private communityRDObs: Observable>; + public constructor(private collectionDataService: CollectionDataService, private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router) { + this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); + this.parentUUID$.subscribe((uuid: string) => { + this.communityRDObs = this.communityDataService.findById(uuid); + }); } onSubmit(data: any) { const collection = Object.assign(new Collection(), { name: data.name }); - this.collectionDataService.create(collection); + this.parentUUID$.subscribe((uuid: string) => { + let response$: Observable; + if (uuid) { + response$ = this.collectionDataService.create(collection, uuid); + } else { + response$ = this.collectionDataService.create(collection); + } + this.error$ = response$.pipe( + map((response: ResponseCacheEntry) => { + if (!response.response.isSuccessful && response.response instanceof ErrorResponse) { + return response.response; + } else if (response.response instanceof DSOSuccessResponse) { + this.router.navigateByUrl(''); + } + }) + ); + }); } } diff --git a/src/app/+community-page/create-community-page/create-community-page.component.html b/src/app/+community-page/create-community-page/create-community-page.component.html index ea270db92b..53ca4f5020 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.html +++ b/src/app/+community-page/create-community-page/create-community-page.component.html @@ -1,8 +1,15 @@
- + + +

{{ 'community.create.sub-head' | translate:{ parent: community.name } }}

+
+
+ {{error.statusCode}} +

{{error.errorMessage}}

+
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 255d88c21b..1da0299e66 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 @@ -3,6 +3,13 @@ import { Community } from '../../core/shared/community.model'; import { ComColDataService } from '../../core/data/comcol-data.service'; import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; import { CommunityDataService } from '../../core/data/community-data.service'; +import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; +import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../../core/cache/response-cache.models'; +import { Observable } from 'rxjs/Observable'; +import { map } from 'rxjs/operators'; +import { RouteService } from '../../shared/services/route.service'; +import { Router } from '@angular/router'; +import { RemoteData } from '../../core/data/remote-data'; @Component({ selector: 'ds-create-community', @@ -11,15 +18,38 @@ import { CommunityDataService } from '../../core/data/community-data.service'; }) export class CreateCommunityPageComponent { - public constructor(private communityDataService: CommunityDataService) { + private error$: Observable; + private parentUUID$: Observable; + private communityRDObs: Observable>; + public constructor(private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router) { + this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); + this.parentUUID$.subscribe((uuid: string) => { + this.communityRDObs = this.communityDataService.findById(uuid); + }); } onSubmit(data: any) { const community = Object.assign(new Community(), { name: data.name }); - this.communityDataService.create(community); + this.parentUUID$.subscribe((uuid: string) => { + let response$: Observable; + if (uuid) { + response$ = this.communityDataService.create(community, uuid); + } else { + response$ = this.communityDataService.create(community); + } + this.error$ = response$.pipe( + map((response: ResponseCacheEntry) => { + if (!response.response.isSuccessful && response.response instanceof ErrorResponse) { + return response.response; + } else if (response.response instanceof DSOSuccessResponse) { + this.router.navigateByUrl(''); + } + }) + ); + }); } } diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 765cba0a8b..70c11ca5fc 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -31,8 +31,4 @@ export class CollectionDataService extends ComColDataService { + return this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), distinctUntilChanged(), map((endpointURL: string) => { @@ -75,12 +75,22 @@ export abstract class ComColDataService request.href), + getResponseFromSelflink(this.responseCache) + ); } - abstract buildCreateParams(comcol: TDomain): string; + public buildCreateParams(comcol: TDomain, parentUUID?: string): string { + if (comcol instanceof Community || comcol instanceof Collection) { + let urlParams = '?name=' + comcol.name; + if (parentUUID) { + urlParams += '&parent=' + parentUUID; + } + return urlParams; + } + } } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index acc915c093..9d6af5ee6f 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -34,8 +34,4 @@ export class CommunityDataService extends ComColDataService Date: Wed, 22 Aug 2018 16:10:55 +0200 Subject: [PATCH 08/57] 54472: Authorization + tests (intermediate) --- .../collection-page-routing.module.ts | 3 +- .../community-page-routing.module.ts | 3 +- .../create-community-page.component.spec.ts | 69 +++++++++++ src/app/core/data/comcol-data.service.spec.ts | 110 +++++++++++++++--- src/app/core/data/comcol-data.service.ts | 2 +- 5 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 src/app/+community-page/create-community-page/create-community-page.component.spec.ts diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index 1bd53dd2b3..35faae7b02 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -3,11 +3,12 @@ import { RouterModule } from '@angular/router'; import { CollectionPageComponent } from './collection-page.component'; import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component'; +import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; @NgModule({ imports: [ RouterModule.forChild([ - { path: 'create', component: CreateCollectionPageComponent }, + { path: 'create', component: CreateCollectionPageComponent, canActivate: [AuthenticatedGuard] }, { path: ':id', component: CollectionPageComponent, pathMatch: 'full' } ]) ] diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index 249b01ea18..13d75628b9 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -3,11 +3,12 @@ import { RouterModule } from '@angular/router'; import { CommunityPageComponent } from './community-page.component'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; +import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; @NgModule({ imports: [ RouterModule.forChild([ - { path: 'create', component: CreateCommunityPageComponent }, + { path: 'create', component: CreateCommunityPageComponent, canActivate: [AuthenticatedGuard] }, { path: ':id', component: CommunityPageComponent, pathMatch: 'full' } ]) ] 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 new file mode 100644 index 0000000000..990a9895b9 --- /dev/null +++ b/src/app/+community-page/create-community-page/create-community-page.component.spec.ts @@ -0,0 +1,69 @@ +import { CreateCommunityPageComponent } from './create-community-page.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { RouteService } from '../../shared/services/route.service'; +import { Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../../core/data/remote-data'; +import { Community } from '../../core/shared/community.model'; +import { DSOSuccessResponse } from '../../core/cache/response-cache.models'; +import { BrowserModule } from '@angular/platform-browser'; +import { SharedModule } from '../../shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { CommunityFormComponent } from '../community-form/community-form.component'; + +describe('CreateCommunityPageComponent', () => { + let comp: CreateCommunityPageComponent; + let fixture: ComponentFixture; + let communityDataService: CommunityDataService; + let routeService: RouteService; + let router: any = {}; + + const community = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66b9300d347', + name: 'test community' + }); + + const communityDataServiceStub = { + findById: (uuid) => Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { + uuid: uuid, + name: community.name + }))), + create: (com, uuid?) => Observable.of({ + response: new DSOSuccessResponse(null,'200',null) + }) + }; + const routeServiceStub = { + getQueryParameterValue: (param) => Observable.of(community.uuid) + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule], + declarations: [CreateCommunityPageComponent, CommunityFormComponent], + providers: [ + { provide: CommunityDataService, useValue: communityDataServiceStub }, + { provide: RouteService, useValue: routeServiceStub }, + { provide: Router, useValue: router } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateCommunityPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + communityDataService = (comp as any).communityDataService; + routeService = (comp as any).routeService; + router = (comp as any).router; + }); + + it('should navigate on successful submit', () => { + spyOn(router, 'navigateByUrl'); + comp.onSubmit({ + name: 'test' + }); + expect(router.navigateByUrl).toHaveBeenCalled(); + }); +}); diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index b5727fb22f..5f440dd442 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -1,6 +1,6 @@ import { Store } from '@ngrx/store'; import { cold, getTestScheduler, hot } from 'jasmine-marbles'; -import { TestScheduler } from 'rxjs/Rx'; +import { Observable, TestScheduler } from 'rxjs/Rx'; import { GlobalConfig } from '../../../config'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; @@ -14,6 +14,8 @@ import { FindByIDRequest } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { Community } from '../shared/community.model'; +import { AuthService } from '../auth/auth.service'; const LINK_NAME = 'test'; @@ -32,6 +34,7 @@ class TestService extends ComColDataService { protected cds: CommunityDataService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, + protected authService: AuthService, protected linkPath: string ) { super(); @@ -46,7 +49,8 @@ describe('ComColDataService', () => { let requestService: RequestService; let cds: CommunityDataService; let objectCache: ObjectCacheService; - const halService: any = {}; + let authService: AuthService; + let halService: any = {}; const rdbService = {} as RemoteDataBuildService; const store = {} as Store; @@ -57,6 +61,11 @@ describe('ComColDataService', () => { const communityEndpoint = `${communitiesEndpoint}/${scopeID}`; const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`; const serviceEndpoint = `https://rest.api/core/${LINK_NAME}`; + const authHeader = 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJlaWQiOiJhNjA4NmIzNC0zOTE4LTQ1YjctOGRkZC05MzI5YTcwMmEyNmEiLCJzZyI6W10sImV4cCI6MTUzNDk0MDcyNX0.RV5GAtiX6cpwBN77P_v16iG9ipeyiO7faNYSNMzq_sQ'; + + const mockHalService = { + getEndpoint: (linkPath) => Observable.of(communitiesEndpoint) + }; function initMockCommunityDataService(): CommunityDataService { return jasmine.createSpyObj('responseCache', { @@ -85,6 +94,14 @@ describe('ComColDataService', () => { }); } + function initMockAuthService(): AuthService { + return jasmine.createSpyObj('authService', { + buildAuthHeader: cold('c-', { + c: authHeader + }) + }); + } + function initTestService(): TestService { return new TestService( responseCache, @@ -95,22 +112,27 @@ describe('ComColDataService', () => { cds, objectCache, halService, + authService, LINK_NAME ); } + beforeEach(() => { + cds = initMockCommunityDataService(); + requestService = getMockRequestService(); + objectCache = initMockObjectCacheService(); + responseCache = initMockResponseCacheService(true); + halService = mockHalService; + authService = initMockAuthService(); + service = initTestService(); + }); + describe('getScopedEndpoint', () => { beforeEach(() => { scheduler = getTestScheduler(); }); it('should configure a new FindByIDRequest for the scope Community', () => { - cds = initMockCommunityDataService(); - requestService = getMockRequestService(); - objectCache = initMockObjectCacheService(); - responseCache = initMockResponseCacheService(true); - service = initTestService(); - const expected = new FindByIDRequest(requestService.generateRequestId(), communityEndpoint, scopeID); scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe()); @@ -120,14 +142,6 @@ describe('ComColDataService', () => { }); describe('if the scope Community can be found', () => { - beforeEach(() => { - cds = initMockCommunityDataService(); - requestService = getMockRequestService(); - objectCache = initMockObjectCacheService(); - responseCache = initMockResponseCacheService(true); - service = initTestService(); - }); - it('should fetch the scope Community from the cache', () => { scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe()); scheduler.flush(); @@ -160,4 +174,68 @@ describe('ComColDataService', () => { }); }); + + describe('create', () => { + let community: Community; + const name = 'test community'; + + beforeEach(() => { + community = Object.assign(new Community(), { + name: name + }); + spyOn(service, 'buildCreateParams'); + }); + + describe('when creating a top-level community', () => { + it('should build params without parent UUID', () => { + scheduler.schedule(() => service.create(community).subscribe()); + scheduler.flush(); + expect(service.buildCreateParams).toHaveBeenCalledWith(community); + }); + }); + + describe('when creating a community part of another community', () => { + let parentCommunity: Community; + const parentName = 'test parent community'; + const parentUUID = 'a20da287-e174-466a-9926-f66b9300d347'; + + beforeEach(() => { + parentCommunity = Object.assign(new Community(), { + id: parentUUID, + uuid: parentUUID, + name: parentName + }); + }); + + it('should build params with parent UUID', () => { + scheduler.schedule(() => service.create(community, parentUUID).subscribe()); + scheduler.flush(); + expect(service.buildCreateParams).toHaveBeenCalledWith(community, parentUUID); + }); + }); + }); + + describe('buildCreateParams', () => { + let community: Community; + const name = 'test community'; + let parentCommunity: Community; + const parentName = 'test parent community'; + const parentUUID = 'a20da287-e174-466a-9926-f66b9300d347'; + + beforeEach(() => { + community = Object.assign(new Community(), { + name: name + }); + parentCommunity = Object.assign(new Community(), { + id: parentUUID, + uuid: parentUUID, + name: parentName + }); + }); + + it('should return the correct url parameters', () => { + expect(service.buildCreateParams(community, parentUUID)).toEqual('?name=' + name + '&parent=' + parentUUID); + }); + }); + }); diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index b7a87913de..a57f5fa910 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -75,7 +75,7 @@ export abstract class ComColDataService request.href), From 3a0de2865a753eabddc29a6427639ab4812de73e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 23 Aug 2018 11:03:06 +0200 Subject: [PATCH 09/57] 54472: CreateCommunityPageComponent and CreateCollectionPageComponent tests --- .../create-collection-page.component.spec.ts | 99 +++++++++++++++++++ .../create-community-page.component.spec.ts | 38 +++++-- 2 files changed, 129 insertions(+), 8 deletions(-) create mode 100644 src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts 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 new file mode 100644 index 0000000000..efe0e3da97 --- /dev/null +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts @@ -0,0 +1,99 @@ +import { SharedModule } from '../../shared/shared.module'; +import { Community } from '../../core/shared/community.model'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; +import { CommonModule } from '@angular/common'; +import { CreateCommunityPageComponent } from '../../+community-page/create-community-page/create-community-page.component'; +import { Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommunityFormComponent } from '../../+community-page/community-form/community-form.component'; +import { Observable } from 'rxjs/Observable'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { RequestError } from '../../core/data/request.models'; +import { RouteService } from '../../shared/services/route.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { CreateCollectionPageComponent } from './create-collection-page.component'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { Collection } from '../../core/shared/collection.model'; +import { RouterTestingModule } from '@angular/router/testing'; +import { CollectionFormComponent } from '../collection-form/collection-form.component'; + +describe('CreateCollectionPageComponent', () => { + let comp: CreateCollectionPageComponent; + let fixture: ComponentFixture; + let collectionDataService: CollectionDataService; + let communityDataService: CommunityDataService; + let routeService: RouteService; + let router: Router; + + const community = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66b9300d347', + name: 'test community' + }); + + const collectionDataServiceStub = { + create: (com, uuid?) => Observable.of({ + response: new DSOSuccessResponse(null,'200',null) + }) + }; + const communityDataServiceStub = { + findById: (uuid) => Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { + uuid: uuid, + name: community.name + }))) + }; + const routeServiceStub = { + getQueryParameterValue: (param) => Observable.of(community.uuid) + }; + const routerStub = { + navigateByUrl: (url) => url + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [CreateCollectionPageComponent, CollectionFormComponent], + providers: [ + { provide: CollectionDataService, useValue: collectionDataServiceStub }, + { provide: CommunityDataService, useValue: communityDataServiceStub }, + { provide: RouteService, useValue: routeServiceStub }, + { provide: Router, useValue: routerStub } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateCollectionPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + collectionDataService = (comp as any).collectionDataService; + communityDataService = (comp as any).communityDataService; + routeService = (comp as any).routeService; + router = (comp as any).router; + }); + + describe('onSubmit', () => { + const data = { + name: 'test' + }; + + it('should navigate when successful', () => { + spyOn(router, 'navigateByUrl'); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigateByUrl).toHaveBeenCalled(); + }); + + it('should not navigate on failure', () => { + spyOn(router, 'navigateByUrl'); + spyOn(collectionDataService, 'create').and.returnValue(Observable.of({ + response: Object.assign(new ErrorResponse(new RequestError()), { + isSuccessful: false + }) + })); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigateByUrl).not.toHaveBeenCalled(); + }); + }); +}); 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 990a9895b9..3c4c7648da 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 @@ -7,18 +7,20 @@ import { TranslateModule } from '@ngx-translate/core'; import { Observable } from 'rxjs/Observable'; import { RemoteData } from '../../core/data/remote-data'; import { Community } from '../../core/shared/community.model'; -import { DSOSuccessResponse } from '../../core/cache/response-cache.models'; +import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; import { BrowserModule } from '@angular/platform-browser'; import { SharedModule } from '../../shared/shared.module'; import { CommonModule } from '@angular/common'; import { CommunityFormComponent } from '../community-form/community-form.component'; +import { RouterTestingModule } from '@angular/router/testing'; +import { RequestError } from '../../core/data/request.models'; describe('CreateCommunityPageComponent', () => { let comp: CreateCommunityPageComponent; let fixture: ComponentFixture; let communityDataService: CommunityDataService; let routeService: RouteService; - let router: any = {}; + let router: Router; const community = Object.assign(new Community(), { uuid: 'a20da287-e174-466a-9926-f66b9300d347', @@ -37,15 +39,18 @@ describe('CreateCommunityPageComponent', () => { const routeServiceStub = { getQueryParameterValue: (param) => Observable.of(community.uuid) }; + const routerStub = { + navigateByUrl: (url) => url + }; beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), SharedModule, CommonModule], + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], declarations: [CreateCommunityPageComponent, CommunityFormComponent], providers: [ { provide: CommunityDataService, useValue: communityDataServiceStub }, { provide: RouteService, useValue: routeServiceStub }, - { provide: Router, useValue: router } + { provide: Router, useValue: routerStub } ] }).compileComponents(); })); @@ -59,11 +64,28 @@ describe('CreateCommunityPageComponent', () => { router = (comp as any).router; }); - it('should navigate on successful submit', () => { - spyOn(router, 'navigateByUrl'); - comp.onSubmit({ + describe('onSubmit', () => { + const data = { name: 'test' + }; + + it('should navigate when successful', () => { + spyOn(router, 'navigateByUrl'); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigateByUrl).toHaveBeenCalled(); + }); + + it('should not navigate on failure', () => { + spyOn(router, 'navigateByUrl'); + spyOn(communityDataService, 'create').and.returnValue(Observable.of({ + response: Object.assign(new ErrorResponse(new RequestError()), { + isSuccessful: false + }) + })); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigateByUrl).not.toHaveBeenCalled(); }); - expect(router.navigateByUrl).toHaveBeenCalled(); }); }); From 7bb264b8e6d3cebfa74aee2e63f1533866897e04 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 23 Aug 2018 11:42:36 +0200 Subject: [PATCH 10/57] 54472: CommunityFormComponent tests --- .../community-form.component.html | 4 +- .../community-form.component.spec.ts | 70 +++++++++++++++++++ 2 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/app/+community-page/community-form/community-form.component.spec.ts diff --git a/src/app/+community-page/community-form/community-form.component.html b/src/app/+community-page/community-form/community-form.component.html index 1bb5e97ec1..578e2a0d6a 100644 --- a/src/app/+community-page/community-form/community-form.component.html +++ b/src/app/+community-page/community-form/community-form.component.html @@ -21,7 +21,7 @@
- - + +
diff --git a/src/app/+community-page/community-form/community-form.component.spec.ts b/src/app/+community-page/community-form/community-form.component.spec.ts new file mode 100644 index 0000000000..b7b18dc70b --- /dev/null +++ b/src/app/+community-page/community-form/community-form.component.spec.ts @@ -0,0 +1,70 @@ +import { CommunityFormComponent } from './community-form.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SharedModule } from '../../shared/shared.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; + +fdescribe('CommunityFormComponent', () => { + let comp: CommunityFormComponent; + let fixture: ComponentFixture + let location: Location; + + /* tslint:disable:no-empty */ + const locationStub = { + back: () => {} + }; + /* tslint:enable:no-empty */ + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [CommunityFormComponent], + providers: [ + { provide: Location, useValue: locationStub } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommunityFormComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + location = (comp as any).location; + }); + + describe('when submitting', () => { + let input: DebugElement; + let submit: DebugElement; + let cancel: DebugElement; + let error: DebugElement; + + beforeEach(() => { + input = fixture.debugElement.query(By.css('input#community-name')); + submit = fixture.debugElement.query(By.css('button#community-submit')); + cancel = fixture.debugElement.query(By.css('button#community-cancel')); + error = fixture.debugElement.query(By.css('div.invalid-feedback')); + }); + + it('should display an error when leaving name empty', () => { + const el = input.nativeElement; + + el.value = ''; + el.dispatchEvent(new Event('input')); + submit.nativeElement.click(); + fixture.detectChanges(); + + expect(error.nativeElement.style.display).not.toEqual('none'); + }); + + it('should navigate back when pressing cancel', () => { + spyOn(location, 'back'); + cancel.nativeElement.click(); + fixture.detectChanges(); + + expect(location.back).toHaveBeenCalled(); + }); + }) +}); From def2cfab3a05a4bd1ab2de69a01e5da6c3bdcc4b Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 23 Aug 2018 12:59:33 +0200 Subject: [PATCH 11/57] 54472: CollectionFormComponent tests --- .../collection-form.component.html | 4 +- .../collection-form.component.spec.ts | 71 +++++++++++++++++++ .../community-form.component.spec.ts | 4 +- 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 src/app/+collection-page/collection-form/collection-form.component.spec.ts diff --git a/src/app/+collection-page/collection-form/collection-form.component.html b/src/app/+collection-page/collection-form/collection-form.component.html index 808a2793c4..ea99c58c6a 100644 --- a/src/app/+collection-page/collection-form/collection-form.component.html +++ b/src/app/+collection-page/collection-form/collection-form.component.html @@ -29,7 +29,7 @@
- - + +
diff --git a/src/app/+collection-page/collection-form/collection-form.component.spec.ts b/src/app/+collection-page/collection-form/collection-form.component.spec.ts new file mode 100644 index 0000000000..9c65b7c305 --- /dev/null +++ b/src/app/+collection-page/collection-form/collection-form.component.spec.ts @@ -0,0 +1,71 @@ +import { CommunityFormComponent } from './community-form.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SharedModule } from '../../shared/shared.module'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; +import { CollectionFormComponent } from './collection-form.component'; + +describe('CommunityFormComponent', () => { + let comp: CollectionFormComponent; + let fixture: ComponentFixture + let location: Location; + + /* tslint:disable:no-empty */ + const locationStub = { + back: () => {} + }; + /* tslint:enable:no-empty */ + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [CollectionFormComponent], + providers: [ + { provide: Location, useValue: locationStub } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectionFormComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + location = (comp as any).location; + }); + + describe('when submitting', () => { + let input: DebugElement; + let submit: DebugElement; + let cancel: DebugElement; + let error: DebugElement; + + beforeEach(() => { + input = fixture.debugElement.query(By.css('input#collection-name')); + submit = fixture.debugElement.query(By.css('button#collection-submit')); + cancel = fixture.debugElement.query(By.css('button#collection-cancel')); + error = fixture.debugElement.query(By.css('div.invalid-feedback')); + }); + + it('should display an error when leaving name empty', () => { + const el = input.nativeElement; + + el.value = ''; + el.dispatchEvent(new Event('input')); + submit.nativeElement.click(); + fixture.detectChanges(); + + expect(error.nativeElement.style.display).not.toEqual('none'); + }); + + it('should navigate back when pressing cancel', () => { + spyOn(location, 'back'); + cancel.nativeElement.click(); + fixture.detectChanges(); + + expect(location.back).toHaveBeenCalled(); + }); + }) +}); diff --git a/src/app/+community-page/community-form/community-form.component.spec.ts b/src/app/+community-page/community-form/community-form.component.spec.ts index b7b18dc70b..d8eab13f20 100644 --- a/src/app/+community-page/community-form/community-form.component.spec.ts +++ b/src/app/+community-page/community-form/community-form.component.spec.ts @@ -7,9 +7,9 @@ import { RouterTestingModule } from '@angular/router/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; -fdescribe('CommunityFormComponent', () => { +describe('CommunityFormComponent', () => { let comp: CommunityFormComponent; - let fixture: ComponentFixture + let fixture: ComponentFixture; let location: Location; /* tslint:disable:no-empty */ From 870c545357f07cf7eb9f704ade62b516452f0c0d Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 23 Aug 2018 13:56:16 +0200 Subject: [PATCH 12/57] 54472: Added metadata to community/collection creation --- .../collection-form/collection-form.component.html | 4 ++-- .../create-collection-page.component.ts | 9 ++++++++- .../create-community-page.component.ts | 8 +++++++- src/app/core/data/comcol-data.service.ts | 5 +++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/app/+collection-page/collection-form/collection-form.component.html b/src/app/+collection-page/collection-form/collection-form.component.html index ea99c58c6a..85135af15d 100644 --- a/src/app/+collection-page/collection-form/collection-form.component.html +++ b/src/app/+collection-page/collection-form/collection-form.component.html @@ -22,11 +22,11 @@
- +
- +
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 2e8d9b557d..92314a53c7 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 @@ -33,7 +33,14 @@ export class CreateCollectionPageComponent { onSubmit(data: any) { const collection = Object.assign(new Collection(), { - name: data.name + name: data.name, + metadata: [ + { key: 'dc.description', value: data.introductory }, + { key: 'dc.description.abstract', value: data.description }, + { key: 'dc.rights', value: data.copyright }, + { key: 'dc.rights.license', value: data.license } + // TODO: metadata for news and provenance + ] }); this.parentUUID$.subscribe((uuid: string) => { let response$: Observable; 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 1da0299e66..edb332d807 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 @@ -31,7 +31,13 @@ export class CreateCommunityPageComponent { onSubmit(data: any) { const community = Object.assign(new Community(), { - name: data.name + name: data.name, + metadata: [ + { key: 'dc.description', value: data.introductory }, + { key: 'dc.description.abstract', value: data.description }, + { key: 'dc.rights', value: data.copyright } + // TODO: metadata for news + ] }); this.parentUUID$.subscribe((uuid: string) => { let response$: Observable; diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index a57f5fa910..745879be1f 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -89,6 +89,11 @@ export abstract class ComColDataService { let comp: CollectionFormComponent; 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 92314a53c7..4f57482cbd 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 @@ -20,9 +20,9 @@ import { RemoteData } from '../../core/data/remote-data'; }) export class CreateCollectionPageComponent { - private error$: Observable; - private parentUUID$: Observable; - private communityRDObs: Observable>; + public error$: Observable; + public parentUUID$: Observable; + public communityRDObs: Observable>; public constructor(private collectionDataService: CollectionDataService, private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router) { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); diff --git a/src/app/+community-page/community-form/community-form.component.spec.ts b/src/app/+community-page/community-form/community-form.component.spec.ts index d8eab13f20..ea17d15942 100644 --- a/src/app/+community-page/community-form/community-form.component.spec.ts +++ b/src/app/+community-page/community-form/community-form.component.spec.ts @@ -6,6 +6,7 @@ import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; +import { Location } from '@angular/common'; describe('CommunityFormComponent', () => { let comp: CommunityFormComponent; 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 edb332d807..8f2f61ab80 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 @@ -18,9 +18,9 @@ import { RemoteData } from '../../core/data/remote-data'; }) export class CreateCommunityPageComponent { - private error$: Observable; - private parentUUID$: Observable; - private communityRDObs: Observable>; + public error$: Observable; + public parentUUID$: Observable; + public communityRDObs: Observable>; public constructor(private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router) { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); From e7c5a2a29ced4dc9d414879fe4ec588e34f44445 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 5 Sep 2018 16:31:06 +0200 Subject: [PATCH 14/57] 54472: refactoring feedback pt1 --- .../create-collection-page.component.html | 2 +- .../create-collection-page.component.ts | 50 ++++++------ .../create-community-page.component.html | 2 +- .../create-community-page.component.ts | 48 ++++++------ src/app/core/data/collection-data.service.ts | 1 + src/app/core/data/comcol-data.service.spec.ts | 1 + src/app/core/data/comcol-data.service.ts | 45 ++++------- src/app/core/data/community-data.service.ts | 1 + src/app/core/data/data.service.ts | 76 ++++++++++++++----- src/app/core/data/item-data.service.ts | 10 ++- 10 files changed, 133 insertions(+), 103 deletions(-) diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.html b/src/app/+collection-page/create-collection-page/create-collection-page.component.html index 9cf01f7c88..11f4f53ecd 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.html +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.html @@ -1,7 +1,7 @@
- +

{{ 'collection.create.sub-head' | translate:{ parent: community.name } }}

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 4f57482cbd..8f31d30441 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 @@ -12,6 +12,7 @@ import { Observable } from 'rxjs/Observable'; import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; import { map } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; +import { isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-create-collection', @@ -27,37 +28,34 @@ export class CreateCollectionPageComponent { public constructor(private collectionDataService: CollectionDataService, private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router) { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); this.parentUUID$.subscribe((uuid: string) => { - this.communityRDObs = this.communityDataService.findById(uuid); + if (isNotEmpty(uuid)) { + this.communityRDObs = this.communityDataService.findById(uuid); + } }); } onSubmit(data: any) { - const collection = Object.assign(new Collection(), { - name: data.name, - metadata: [ - { key: 'dc.description', value: data.introductory }, - { key: 'dc.description.abstract', value: data.description }, - { key: 'dc.rights', value: data.copyright }, - { key: 'dc.rights.license', value: data.license } - // TODO: metadata for news and provenance - ] - }); this.parentUUID$.subscribe((uuid: string) => { - let response$: Observable; - if (uuid) { - response$ = this.collectionDataService.create(collection, uuid); - } else { - response$ = this.collectionDataService.create(collection); - } - this.error$ = response$.pipe( - map((response: ResponseCacheEntry) => { - if (!response.response.isSuccessful && response.response instanceof ErrorResponse) { - return response.response; - } else if (response.response instanceof DSOSuccessResponse) { - this.router.navigateByUrl(''); - } - }) - ); + const collection = Object.assign(new Collection(), { + name: data.name, + metadata: [ + { key: 'dc.description', value: data.introductory }, + { key: 'dc.description.abstract', value: data.description }, + { key: 'dc.rights', value: data.copyright }, + { key: 'dc.rights.license', value: data.license } + // TODO: metadata for news and provenance + ], + owner: Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { + id: uuid, + uuid: uuid + }))) + }); + this.collectionDataService.create(collection).subscribe((rd: RemoteData) => { + console.log(rd); + if (rd.hasSucceeded) { + this.router.navigateByUrl(''); + } + }); }); } diff --git a/src/app/+community-page/create-community-page/create-community-page.component.html b/src/app/+community-page/create-community-page/create-community-page.component.html index 53ca4f5020..06fd643530 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.html +++ b/src/app/+community-page/create-community-page/create-community-page.component.html @@ -1,7 +1,7 @@
- +

{{ 'community.create.sub-head' | translate:{ parent: community.name } }}

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 8f2f61ab80..04d5b8fd39 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 @@ -10,6 +10,7 @@ import { map } from 'rxjs/operators'; import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; +import { isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-create-community', @@ -25,36 +26,33 @@ export class CreateCommunityPageComponent { public constructor(private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router) { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); this.parentUUID$.subscribe((uuid: string) => { - this.communityRDObs = this.communityDataService.findById(uuid); + if (isNotEmpty(uuid)) { + this.communityRDObs = this.communityDataService.findById(uuid); + } }); } onSubmit(data: any) { - const community = Object.assign(new Community(), { - name: data.name, - metadata: [ - { key: 'dc.description', value: data.introductory }, - { key: 'dc.description.abstract', value: data.description }, - { key: 'dc.rights', value: data.copyright } - // TODO: metadata for news - ] - }); this.parentUUID$.subscribe((uuid: string) => { - let response$: Observable; - if (uuid) { - response$ = this.communityDataService.create(community, uuid); - } else { - response$ = this.communityDataService.create(community); - } - this.error$ = response$.pipe( - map((response: ResponseCacheEntry) => { - if (!response.response.isSuccessful && response.response instanceof ErrorResponse) { - return response.response; - } else if (response.response instanceof DSOSuccessResponse) { - this.router.navigateByUrl(''); - } - }) - ); + const community = Object.assign(new Community(), { + name: data.name, + metadata: [ + { key: 'dc.description', value: data.introductory }, + { key: 'dc.description.abstract', value: data.description }, + { key: 'dc.rights', value: data.copyright } + // TODO: metadata for news + ], + owner: Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { + id: uuid, + uuid: uuid + }))) + }); + this.communityDataService.create(community).subscribe((rd: RemoteData) => { + console.log(rd); + if (rd.hasSucceeded) { + this.router.navigateByUrl(''); + } + }); }); } diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 70c11ca5fc..dc3635595a 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -13,6 +13,7 @@ import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { AuthService } from '../auth/auth.service'; import { Community } from '../shared/community.model'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; @Injectable() export class CollectionDataService extends ComColDataService { diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index 5f440dd442..9cc44e65db 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -16,6 +16,7 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Community } from '../shared/community.model'; import { AuthService } from '../auth/auth.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; const LINK_NAME = 'test'; diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 745879be1f..c47d48dc07 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -19,12 +19,12 @@ import { configureRequest, getResponseFromSelflink } from '../shared/operators'; import { AuthService } from '../auth/auth.service'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpHeaders } from '@angular/common/http'; +import { RemoteData } from './remote-data'; export abstract class ComColDataService extends DataService { protected abstract cds: CommunityDataService; protected abstract objectCache: ObjectCacheService; protected abstract halService: HALEndpointService; - protected abstract authService: AuthService; /** * Get the scoped endpoint URL by fetching the object with @@ -66,36 +66,21 @@ export abstract class ComColDataService { - return this.halService.getEndpoint(this.linkPath).pipe( - isNotEmptyOperator(), - distinctUntilChanged(), - map((endpointURL: string) => { - const options: HttpOptions = Object.create({}); - const headers = new HttpHeaders(); - headers.append('Authentication', this.authService.buildAuthHeader()); - options.headers = headers; - return new PostRequest(this.requestService.generateRequestId(), endpointURL + ((parentUUID) ? this.buildCreateParams(comcol, parentUUID) : this.buildCreateParams(comcol)), options); - }), - configureRequest(this.requestService), - map((request: RestRequest) => request.href), - getResponseFromSelflink(this.responseCache) + public buildCreateParams(comcol): Observable { + return comcol.owner.pipe( + map((rd: RemoteData) => { + let urlParams = '?name=' + comcol.name; + if (rd.payload.id) { + urlParams += '&parent=' + rd.payload.id; + } + if (comcol.metadata) { + for (const i of Object.keys(comcol.metadata)) { + urlParams += '&' + comcol.metadata[i].key + '=' + comcol.metadata[i].value; + } + } + return urlParams; + }) ); } - public buildCreateParams(comcol: TDomain, parentUUID?: string): string { - if (comcol instanceof Community || comcol instanceof Collection) { - let urlParams = '?name=' + comcol.name; - if (parentUUID) { - urlParams += '&parent=' + parentUUID; - } - if (comcol.metadata) { - for (const i of Object.keys(comcol.metadata)) { - urlParams += '&' + comcol.metadata[i].key + '=' + comcol.metadata[i].value; - } - } - return urlParams; - } - } - } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 9d6af5ee6f..d367b15f6b 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -12,6 +12,7 @@ import { ComColDataService } from './comcol-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { AuthService } from '../auth/auth.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; @Injectable() export class CommunityDataService extends ComColDataService { diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index f532ff05ba..b3b69fdd54 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,6 +1,6 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; -import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ResponseCacheService } from '../cache/response-cache.service'; import { CoreState } from '../core.reducers'; @@ -8,9 +8,30 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { URLCombiner } from '../url-combiner/url-combiner'; import { PaginatedList } from './paginated-list'; import { RemoteData } from './remote-data'; -import { FindAllOptions, FindAllRequest, FindByIDRequest, GetRequest } from './request.models'; +import { + FindAllOptions, + FindAllRequest, + FindByIDRequest, + GetRequest, + PostRequest, + RestRequest +} from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; +import { distinctUntilChanged, map, share, withLatestFrom } from 'rxjs/operators'; +import { + configureRequest, + filterSuccessfulResponses, + getRequestFromSelflink, + getResponseFromSelflink +} from '../shared/operators'; +import { ResponseCacheEntry } from '../cache/response-cache.reducer'; +import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; +import { HttpHeaders } from '@angular/common/http'; +import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models'; +import { BrowseEntry } from '../shared/browse-entry.model'; +import { AuthService } from '../auth/auth.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; export abstract class DataService { protected abstract responseCache: ResponseCacheService; @@ -19,6 +40,7 @@ export abstract class DataService protected abstract store: Store; protected abstract linkPath: string; protected abstract halService: HALEndpointService; + protected abstract authService: AuthService; public abstract getScopedEndpoint(scope: string): Observable @@ -91,21 +113,37 @@ export abstract class DataService return this.rdbService.buildSingle(href); } - // TODO implement, after the structure of the REST server's POST response is finalized - // create(dso: DSpaceObject): Observable> { - // const postHrefObs = this.getEndpoint(); - // - // // TODO ID is unknown at this point - // const idHrefObs = postHrefObs.map((href: string) => this.getFindByIDHref(href, dso.id)); - // - // postHrefObs - // .filter((href: string) => hasValue(href)) - // .take(1) - // .subscribe((href: string) => { - // const request = new RestRequest(this.requestService.generateRequestId(), href, RestRequestMethod.Post, dso); - // this.requestService.configure(request); - // }); - // - // return this.rdbService.buildSingle(idHrefObs, this.normalizedResourceType); - // } + public create(dso: TDomain): Observable> { + const request$ = this.halService.getEndpoint(this.linkPath).pipe( + isNotEmptyOperator(), + distinctUntilChanged(), + withLatestFrom(this.buildCreateParams(dso)), + map(([endpointURL, params]) => { + const options: HttpOptions = Object.create({}); + const headers = new HttpHeaders(); + headers.append('Authentication', this.authService.buildAuthHeader()); + options.headers = headers; + return new PostRequest(this.requestService.generateRequestId(), endpointURL + params, options); + }), + configureRequest(this.requestService), + share() + ); + + const href$ = request$.pipe(map((request: RestRequest) => request.href)); + + const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); + const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); + + const payload$ = responseCache$.pipe( + filterSuccessfulResponses(), + map((entry: ResponseCacheEntry) => entry.response), + map((response: GenericSuccessResponse) => response.payload), + distinctUntilChanged() + ); + + return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + } + + public abstract buildCreateParams(dso: TDomain): Observable; + } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 6b0937d8e4..d1db71b78d 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -15,6 +15,8 @@ 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 { NotificationsService } from '../../shared/notifications/notifications.service'; +import { AuthService } from '../auth/auth.service'; @Injectable() export class ItemDataService extends DataService { @@ -26,7 +28,8 @@ export class ItemDataService extends DataService { protected rdbService: RemoteDataBuildService, protected store: Store, private bs: BrowseService, - protected halService: HALEndpointService) { + protected halService: HALEndpointService, + protected authService: AuthService) { super(); } @@ -41,4 +44,9 @@ export class ItemDataService extends DataService { } } + buildCreateParams(dso: Item): Observable { + // TODO: Build parameters for creating an Item on the REST service + return undefined; + } + } From 260ddd979a84e6a71ef4037f83c722927d7a9a6e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 7 Sep 2018 11:07:04 +0200 Subject: [PATCH 15/57] 54472: request created dso --- .../create-community-page.component.ts | 6 +----- src/app/core/data/data.service.ts | 15 +++++---------- src/app/core/data/dso-response-parsing.service.ts | 8 ++++++-- 3 files changed, 12 insertions(+), 17 deletions(-) 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 04d5b8fd39..19e777a454 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,12 +1,8 @@ import { Component } from '@angular/core'; import { Community } from '../../core/shared/community.model'; -import { ComColDataService } from '../../core/data/comcol-data.service'; -import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; import { CommunityDataService } from '../../core/data/community-data.service'; -import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; -import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../../core/cache/response-cache.models'; +import { ErrorResponse } from '../../core/cache/response-cache.models'; import { Observable } from 'rxjs/Observable'; -import { map } from 'rxjs/operators'; import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index b3b69fdd54..d03891065b 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -18,20 +18,17 @@ import { } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; -import { distinctUntilChanged, map, share, withLatestFrom } from 'rxjs/operators'; +import { distinctUntilChanged, map, share, switchMap, withLatestFrom } from 'rxjs/operators'; import { configureRequest, filterSuccessfulResponses, - getRequestFromSelflink, getResponseFromSelflink } from '../shared/operators'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpHeaders } from '@angular/common/http'; -import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models'; -import { BrowseEntry } from '../shared/browse-entry.model'; +import { DSOSuccessResponse } from '../cache/response-cache.models'; import { AuthService } from '../auth/auth.service'; -import { NotificationsService } from '../../shared/notifications/notifications.service'; export abstract class DataService { protected abstract responseCache: ResponseCacheService; @@ -130,18 +127,16 @@ export abstract class DataService ); const href$ = request$.pipe(map((request: RestRequest) => request.href)); - - const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); - const payload$ = responseCache$.pipe( + const dsoHref$ = responseCache$.pipe( filterSuccessfulResponses(), map((entry: ResponseCacheEntry) => entry.response), - map((response: GenericSuccessResponse) => response.payload), + map((response: DSOSuccessResponse) => response.resourceSelfLinks[0]), distinctUntilChanged() ); - return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + return this.rdbService.buildSingle(dsoHref$); } public abstract buildCreateParams(dso: TDomain): Observable; diff --git a/src/app/core/data/dso-response-parsing.service.ts b/src/app/core/data/dso-response-parsing.service.ts index 9651eb3157..e2d930d88c 100644 --- a/src/app/core/data/dso-response-parsing.service.ts +++ b/src/app/core/data/dso-response-parsing.service.ts @@ -12,6 +12,7 @@ import { RestRequest } from './request.models'; import { ResponseParsingService } from './parsing.service'; import { BaseResponseParsingService } from './base-response-parsing.service'; +import { isNotEmpty } from '../../shared/empty.util'; @Injectable() export class DSOResponseParsingService extends BaseResponseParsingService implements ResponseParsingService { @@ -27,8 +28,11 @@ export class DSOResponseParsingService extends BaseResponseParsingService implem parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { const processRequestDTO = this.process(data.payload, request.href); - const selfLinks = this.flattenSingleKeyObject(processRequestDTO).map((no) => no.self); - return new DSOSuccessResponse(selfLinks, data.statusCode, this.processPageInfo(data.payload)) + let selfLinks = []; + if (processRequestDTO !== undefined) { + selfLinks = this.flattenSingleKeyObject(processRequestDTO).map((no) => no.self); + } + return new DSOSuccessResponse(selfLinks, data.statusCode, (isNotEmpty(data.payload) ? this.processPageInfo(data.payload) : null)) } } From 7a2e0ae851a41902e9b2d1b8abb2720bd46e1217 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 7 Sep 2018 14:32:43 +0200 Subject: [PATCH 16/57] 54472: Post-merge error fixes --- src/app/core/data/dspace-object-data.service.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 39feea4c30..bf2da345b8 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -10,6 +10,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { DataService } from './data.service'; import { RemoteData } from './remote-data'; import { RequestService } from './request.service'; +import { AuthService } from '../auth/auth.service'; /* tslint:disable:max-classes-per-file */ class DataServiceImpl extends DataService { @@ -20,7 +21,8 @@ class DataServiceImpl extends DataService protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected store: Store, - protected halService: HALEndpointService) { + protected halService: HALEndpointService, + protected authService: AuthService) { super(); } @@ -31,6 +33,10 @@ class DataServiceImpl extends DataService getFindByIDHref(endpoint, resourceID): string { return endpoint.replace(/\{\?uuid\}/,`?uuid=${resourceID}`); } + + buildCreateParams(dso: DSpaceObject): Observable { + return undefined; + } } @Injectable() @@ -41,8 +47,9 @@ export class DSpaceObjectDataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, - protected halService: HALEndpointService) { - this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService); + protected halService: HALEndpointService, + protected authService: AuthService) { + this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService, authService); } findById(uuid: string): Observable> { From ea075fbd8f286b11f3760b5eaec153b3ed0230f9 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 10 Sep 2018 08:44:56 +0200 Subject: [PATCH 17/57] Correct parsing of AuthStatus and small changes --- src/app/core/auth/auth-response-parsing.service.ts | 5 +++-- src/app/core/auth/auth.service.ts | 2 +- src/app/core/auth/models/auth-status.model.ts | 2 +- src/app/core/auth/models/normalized-auth-status.model.ts | 2 +- src/app/core/auth/server-auth.service.ts | 2 +- src/app/shared/testing/auth-request-service-stub.ts | 4 ++-- src/app/shared/testing/auth-service-stub.ts | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/app/core/auth/auth-response-parsing.service.ts b/src/app/core/auth/auth-response-parsing.service.ts index 80c1b2eeca..f024035c06 100644 --- a/src/app/core/auth/auth-response-parsing.service.ts +++ b/src/app/core/auth/auth-response-parsing.service.ts @@ -26,8 +26,9 @@ export class AuthResponseParsingService extends BaseResponseParsingService imple parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && (data.statusCode === '200' || data.statusCode === 'OK')) { - const response = this.process(data.payload, request.href); - return new AuthStatusResponse(response[Object.keys(response)[0]][0], data.statusCode); + const response: AuthStatus = this.process(data.payload, request.href); + response.eperson = data.payload._embedded.eperson; + return new AuthStatusResponse(response, data.statusCode); } else { return new AuthStatusResponse(data.payload as AuthStatus, data.statusCode); } diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 2848b54b50..2eb6736d89 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -126,7 +126,7 @@ export class AuthService { return this.authRequestService.getRequest('status', options) .map((status: AuthStatus) => { if (status.authenticated) { - return status.eperson[0]; + return status.eperson; } else { throw(new Error('Not authenticated')); } diff --git a/src/app/core/auth/models/auth-status.model.ts b/src/app/core/auth/models/auth-status.model.ts index 22c9d14718..9d69c18388 100644 --- a/src/app/core/auth/models/auth-status.model.ts +++ b/src/app/core/auth/models/auth-status.model.ts @@ -13,7 +13,7 @@ export class AuthStatus { error?: AuthError; - eperson: Eperson[]; + eperson: Eperson; token?: AuthTokenInfo; diff --git a/src/app/core/auth/models/normalized-auth-status.model.ts b/src/app/core/auth/models/normalized-auth-status.model.ts index 19952f7c70..c63c611a75 100644 --- a/src/app/core/auth/models/normalized-auth-status.model.ts +++ b/src/app/core/auth/models/normalized-auth-status.model.ts @@ -21,6 +21,6 @@ export class NormalizedAuthStatus extends NormalizedDSpaceObject { authenticated: boolean; @autoserializeAs(Eperson) - eperson: Eperson[]; + eperson: Eperson; } diff --git a/src/app/core/auth/server-auth.service.ts b/src/app/core/auth/server-auth.service.ts index 96ee2e355a..00dfbc5615 100644 --- a/src/app/core/auth/server-auth.service.ts +++ b/src/app/core/auth/server-auth.service.ts @@ -35,7 +35,7 @@ export class ServerAuthService extends AuthService { return this.authRequestService.getRequest('status', options) .map((status: AuthStatus) => { if (status.authenticated) { - return status.eperson[0]; + return status.eperson; } else { throw(new Error('Not authenticated')); } diff --git a/src/app/shared/testing/auth-request-service-stub.ts b/src/app/shared/testing/auth-request-service-stub.ts index 2c47068af4..9703f49967 100644 --- a/src/app/shared/testing/auth-request-service-stub.ts +++ b/src/app/shared/testing/auth-request-service-stub.ts @@ -26,7 +26,7 @@ export class AuthRequestServiceStub { if (this.validateToken(token)) { authStatusStub.authenticated = true; authStatusStub.token = this.mockTokenInfo; - authStatusStub.eperson = [this.mockUser]; + authStatusStub.eperson = this.mockUser; } else { authStatusStub.authenticated = false; } @@ -45,7 +45,7 @@ export class AuthRequestServiceStub { if (this.validateToken(token)) { authStatusStub.authenticated = true; authStatusStub.token = this.mockTokenInfo; - authStatusStub.eperson = [this.mockUser]; + authStatusStub.eperson = this.mockUser; } else { authStatusStub.authenticated = false; } diff --git a/src/app/shared/testing/auth-service-stub.ts b/src/app/shared/testing/auth-service-stub.ts index c7d5556910..9e830930c1 100644 --- a/src/app/shared/testing/auth-service-stub.ts +++ b/src/app/shared/testing/auth-service-stub.ts @@ -19,7 +19,7 @@ export class AuthServiceStub { authStatus.okay = true; authStatus.authenticated = true; authStatus.token = this.token; - authStatus.eperson = [EpersonMock]; + authStatus.eperson = EpersonMock; return Observable.of(authStatus); } else { console.log('error'); From 2f139c5c9410a09d6ca8b4f97da969e390a68623 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 10 Sep 2018 12:03:00 +0200 Subject: [PATCH 18/57] 54472: Create request, response, responseparsingservice and data-service changes --- src/app/core/cache/response-cache.models.ts | 11 +++++ src/app/core/core.module.ts | 2 + src/app/core/data/data.service.ts | 17 +++++--- src/app/core/data/request.models.ts | 12 ++++++ .../single-dso-response-parsing.service.ts | 43 +++++++++++++++++++ 5 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 src/app/core/data/single-dso-response-parsing.service.ts diff --git a/src/app/core/cache/response-cache.models.ts b/src/app/core/cache/response-cache.models.ts index 9b1b5b89eb..03c48b62fe 100644 --- a/src/app/core/cache/response-cache.models.ts +++ b/src/app/core/cache/response-cache.models.ts @@ -10,6 +10,7 @@ import { MetadataSchema } from '../metadata/metadataschema.model'; import { RegistryMetadatafieldsResponse } from '../registry/registry-metadatafields-response.model'; import { RegistryBitstreamformatsResponse } from '../registry/registry-bitstreamformats-response.model'; import { AuthStatus } from '../auth/models/auth-status.model'; +import { DSpaceObject } from '../shared/dspace-object.model'; /* tslint:disable:max-classes-per-file */ export class RestResponse { @@ -32,6 +33,16 @@ export class DSOSuccessResponse extends RestResponse { } } +export class SingleDSOSuccessResponse extends DSOSuccessResponse { + constructor( + public resourceSelfLinks: string[], + public statusCode: string, + public dso: DSpaceObject + ) { + super(resourceSelfLinks, statusCode); + } +} + export class RegistryMetadataschemasSuccessResponse extends RestResponse { constructor( public metadataschemasResponse: RegistryMetadataschemasResponse, diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index dabdfba0ab..61568b99a6 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -63,6 +63,7 @@ import { RegistryBitstreamformatsResponseParsingService } from './data/registry- import { NotificationsService } from '../shared/notifications/notifications.service'; import { UploaderService } from '../shared/uploader/uploader.service'; import { DSpaceObjectDataService } from './data/dspace-object-data.service'; +import { SingleDsoResponseParsingService } from './data/single-dso-response-parsing.service'; const IMPORTS = [ CommonModule, @@ -126,6 +127,7 @@ const PROVIDERS = [ UploaderService, UUIDService, DSpaceObjectDataService, + SingleDsoResponseParsingService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 6a652dbf99..a07adc0552 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -9,6 +9,7 @@ import { URLCombiner } from '../url-combiner/url-combiner'; import { PaginatedList } from './paginated-list'; import { RemoteData } from './remote-data'; import { + CreateRequest, FindAllOptions, FindAllRequest, FindByIDRequest, @@ -21,14 +22,15 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { distinctUntilChanged, map, share, switchMap, withLatestFrom } from 'rxjs/operators'; import { configureRequest, - filterSuccessfulResponses, + filterSuccessfulResponses, getRequestFromSelflink, getResponseFromSelflink } from '../shared/operators'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpHeaders } from '@angular/common/http'; -import { DSOSuccessResponse } from '../cache/response-cache.models'; +import { DSOSuccessResponse, SingleDSOSuccessResponse } from '../cache/response-cache.models'; import { AuthService } from '../auth/auth.service'; +import { DSpaceObject } from '../shared/dspace-object.model'; export abstract class DataService { protected abstract responseCache: ResponseCacheService; @@ -113,7 +115,7 @@ export abstract class DataService return this.rdbService.buildSingle(href); } - public create(dso: TDomain): Observable> { + public create(dso: TDomain): Observable> { const request$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), distinctUntilChanged(), @@ -123,23 +125,24 @@ export abstract class DataService const headers = new HttpHeaders(); headers.append('Authentication', this.authService.buildAuthHeader()); options.headers = headers; - return new PostRequest(this.requestService.generateRequestId(), endpointURL + params, options); + return new CreateRequest(this.requestService.generateRequestId(), endpointURL + params, options); }), configureRequest(this.requestService), share() ); const href$ = request$.pipe(map((request: RestRequest) => request.href)); + const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); - const dsoHref$ = responseCache$.pipe( + const payload$ = responseCache$.pipe( filterSuccessfulResponses(), map((entry: ResponseCacheEntry) => entry.response), - map((response: DSOSuccessResponse) => response.resourceSelfLinks[0]), + map((response: SingleDSOSuccessResponse) => response.dso), distinctUntilChanged() ); - return this.rdbService.buildSingle(dsoHref$); + return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); } public abstract buildCreateParams(dso: TDomain): Observable; diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index ce45d5b41c..f5b9127a6b 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -12,6 +12,7 @@ import { AuthResponseParsingService } from '../auth/auth-response-parsing.servic import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpHeaders } from '@angular/common/http'; import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service'; +import { SingleDsoResponseParsingService } from './single-dso-response-parsing.service'; /* tslint:disable:max-classes-per-file */ @@ -223,6 +224,17 @@ export class IntegrationRequest extends GetRequest { return IntegrationResponseParsingService; } } + +export class CreateRequest extends PostRequest { + constructor(uuid: string, href: string, public body?: any, public options?: HttpOptions) { + super(uuid, href, body, options); + } + + getResponseParser(): GenericConstructor { + return SingleDsoResponseParsingService; + } +} + export class RequestError extends Error { statusText: string; } diff --git a/src/app/core/data/single-dso-response-parsing.service.ts b/src/app/core/data/single-dso-response-parsing.service.ts new file mode 100644 index 0000000000..6b9817375c --- /dev/null +++ b/src/app/core/data/single-dso-response-parsing.service.ts @@ -0,0 +1,43 @@ +import { Inject, Injectable } from '@angular/core'; +import { ResponseParsingService } from './parsing.service'; +import { BaseResponseParsingService } from './base-response-parsing.service'; +import { GlobalConfig } from '../../../config/global-config.interface'; +import { GLOBAL_CONFIG } from '../../../config'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { RestRequest } from './request.models'; +import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; +import { DSOSuccessResponse, RestResponse, SingleDSOSuccessResponse } from '../cache/response-cache.models'; +import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; +import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; +import { DSpaceObject } from '../shared/dspace-object.model'; +import { NormalizedObject } from '../cache/models/normalized-object.model'; +import { ResourceType } from '../shared/resource-type'; +import { hasNoValue, hasValue } from '../../shared/empty.util'; + +@Injectable() +export class SingleDsoResponseParsingService extends BaseResponseParsingService implements ResponseParsingService { + protected objectFactory = NormalizedObjectFactory; + protected toCache = true; + + constructor( + @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, + protected objectCache: ObjectCacheService, + ) { super(); + } + + parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { + const processRequestDTO = this.process(data.payload, request.href); + if (hasNoValue(processRequestDTO)) { + return new SingleDSOSuccessResponse([], data.statusCode, undefined) + } + let objectList = processRequestDTO; + if (hasValue(processRequestDTO.page)) { + objectList = processRequestDTO.page; + } else if (!Array.isArray(processRequestDTO)) { + objectList = [processRequestDTO]; + } + const selfLinks = objectList.map((no) => no.self); + return new SingleDSOSuccessResponse(selfLinks, data.statusCode, processRequestDTO); + } + +} From 7b3e82c5afc140a94b125f5b577531078f2173c2 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 10 Sep 2018 13:48:34 +0200 Subject: [PATCH 19/57] 54472: Removed SingleDsoSuccessResponse and replaced by GenericSuccessResponse --- .../create-collection-page.component.ts | 3 ++- .../create-community-page.component.ts | 3 ++- src/app/core/cache/response-cache.models.ts | 10 ---------- src/app/core/data/data.service.ts | 8 +++++--- .../data/single-dso-response-parsing.service.ts | 17 +++++++---------- 5 files changed, 16 insertions(+), 25 deletions(-) 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 8f31d30441..f94547c8fb 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 @@ -13,6 +13,7 @@ import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; import { map } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; @Component({ selector: 'ds-create-collection', @@ -50,7 +51,7 @@ export class CreateCollectionPageComponent { uuid: uuid }))) }); - this.collectionDataService.create(collection).subscribe((rd: RemoteData) => { + this.collectionDataService.create(collection).subscribe((rd: RemoteData) => { console.log(rd); if (rd.hasSucceeded) { this.router.navigateByUrl(''); 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 19e777a454..b6c6fb3c13 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 @@ -7,6 +7,7 @@ import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; @Component({ selector: 'ds-create-community', @@ -43,7 +44,7 @@ export class CreateCommunityPageComponent { uuid: uuid }))) }); - this.communityDataService.create(community).subscribe((rd: RemoteData) => { + this.communityDataService.create(community).subscribe((rd: RemoteData) => { console.log(rd); if (rd.hasSucceeded) { this.router.navigateByUrl(''); diff --git a/src/app/core/cache/response-cache.models.ts b/src/app/core/cache/response-cache.models.ts index 03c48b62fe..94ddd48f83 100644 --- a/src/app/core/cache/response-cache.models.ts +++ b/src/app/core/cache/response-cache.models.ts @@ -33,16 +33,6 @@ export class DSOSuccessResponse extends RestResponse { } } -export class SingleDSOSuccessResponse extends DSOSuccessResponse { - constructor( - public resourceSelfLinks: string[], - public statusCode: string, - public dso: DSpaceObject - ) { - super(resourceSelfLinks, statusCode); - } -} - export class RegistryMetadataschemasSuccessResponse extends RestResponse { constructor( public metadataschemasResponse: RegistryMetadataschemasResponse, diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index a07adc0552..e245760d81 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -28,7 +28,7 @@ import { import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpHeaders } from '@angular/common/http'; -import { DSOSuccessResponse, SingleDSOSuccessResponse } from '../cache/response-cache.models'; +import { DSOSuccessResponse, GenericSuccessResponse } from '../cache/response-cache.models'; import { AuthService } from '../auth/auth.service'; import { DSpaceObject } from '../shared/dspace-object.model'; @@ -115,7 +115,7 @@ export abstract class DataService return this.rdbService.buildSingle(href); } - public create(dso: TDomain): Observable> { + public create(dso: TDomain): Observable> { const request$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), distinctUntilChanged(), @@ -138,10 +138,12 @@ export abstract class DataService const payload$ = responseCache$.pipe( filterSuccessfulResponses(), map((entry: ResponseCacheEntry) => entry.response), - map((response: SingleDSOSuccessResponse) => response.dso), + map((response: GenericSuccessResponse) => response.payload), distinctUntilChanged() ); + payload$.subscribe((value) => console.log(value)); + return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); } diff --git a/src/app/core/data/single-dso-response-parsing.service.ts b/src/app/core/data/single-dso-response-parsing.service.ts index 6b9817375c..03b9f5bea2 100644 --- a/src/app/core/data/single-dso-response-parsing.service.ts +++ b/src/app/core/data/single-dso-response-parsing.service.ts @@ -6,7 +6,11 @@ import { GLOBAL_CONFIG } from '../../../config'; import { ObjectCacheService } from '../cache/object-cache.service'; import { RestRequest } from './request.models'; import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; -import { DSOSuccessResponse, RestResponse, SingleDSOSuccessResponse } from '../cache/response-cache.models'; +import { + DSOSuccessResponse, + GenericSuccessResponse, + RestResponse +} from '../cache/response-cache.models'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { DSpaceObject } from '../shared/dspace-object.model'; @@ -28,16 +32,9 @@ export class SingleDsoResponseParsingService extends BaseResponseParsingService parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { const processRequestDTO = this.process(data.payload, request.href); if (hasNoValue(processRequestDTO)) { - return new SingleDSOSuccessResponse([], data.statusCode, undefined) + return new GenericSuccessResponse(undefined, data.statusCode) } - let objectList = processRequestDTO; - if (hasValue(processRequestDTO.page)) { - objectList = processRequestDTO.page; - } else if (!Array.isArray(processRequestDTO)) { - objectList = [processRequestDTO]; - } - const selfLinks = objectList.map((no) => no.self); - return new SingleDSOSuccessResponse(selfLinks, data.statusCode, processRequestDTO); + return new GenericSuccessResponse(processRequestDTO, data.statusCode); } } From 7f8fa9c1b7a8e072931416e6d3f2bae15b2f1f0b Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 10 Sep 2018 15:20:16 +0200 Subject: [PATCH 20/57] 54472: GetRequestByUUID --- src/app/core/data/data.service.ts | 9 +++++---- src/app/core/shared/operators.ts | 7 +++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index e245760d81..045f98a14f 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -22,7 +22,7 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { distinctUntilChanged, map, share, switchMap, withLatestFrom } from 'rxjs/operators'; import { configureRequest, - filterSuccessfulResponses, getRequestFromSelflink, + filterSuccessfulResponses, getRequestFromSelflink, getRequestFromUUID, getResponseFromSelflink } from '../shared/operators'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; @@ -31,6 +31,8 @@ import { HttpHeaders } from '@angular/common/http'; import { DSOSuccessResponse, GenericSuccessResponse } from '../cache/response-cache.models'; import { AuthService } from '../auth/auth.service'; import { DSpaceObject } from '../shared/dspace-object.model'; +import { first } from 'rxjs/operator/first'; +import { take } from 'rxjs/operator/take'; export abstract class DataService { protected abstract responseCache: ResponseCacheService; @@ -132,7 +134,8 @@ export abstract class DataService ); const href$ = request$.pipe(map((request: RestRequest) => request.href)); - const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); + const uuid$ = request$.pipe(map((request: RestRequest) => request.uuid)); + const requestEntry$ = uuid$.pipe(getRequestFromUUID(this.requestService)); const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); const payload$ = responseCache$.pipe( @@ -142,8 +145,6 @@ export abstract class DataService distinctUntilChanged() ); - payload$.subscribe((value) => console.log(value)); - return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); } diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index fcb4c2c256..350a664e9d 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -23,6 +23,13 @@ export const getRequestFromSelflink = (requestService: RequestService) => hasValueOperator() ); +export const getRequestFromUUID = (requestService: RequestService) => + (source: Observable): Observable => + source.pipe( + flatMap((uuid: string) => requestService.getByUUID(uuid)), + hasValueOperator() + ); + export const getResponseFromSelflink = (responseCache: ResponseCacheService) => (source: Observable): Observable => source.pipe( From 79afa38a67e136ffdf74067304cb4b343f18d9a8 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 11 Sep 2018 08:12:16 +0200 Subject: [PATCH 21/57] 54472: Fixed duplicate requests --- .../create-community-page.component.ts | 5 +-- src/app/core/data/data.service.ts | 34 +++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) 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 b6c6fb3c13..69012ca2a4 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 @@ -8,6 +8,7 @@ import { Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { take } from 'rxjs/operators'; @Component({ selector: 'ds-create-community', @@ -21,7 +22,7 @@ export class CreateCommunityPageComponent { public communityRDObs: Observable>; public constructor(private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router) { - this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); + this.parentUUID$ = this.routeService.getQueryParameterValue('parent').pipe(take(1)); this.parentUUID$.subscribe((uuid: string) => { if (isNotEmpty(uuid)) { this.communityRDObs = this.communityDataService.findById(uuid); @@ -44,7 +45,7 @@ export class CreateCommunityPageComponent { uuid: uuid }))) }); - this.communityDataService.create(community).subscribe((rd: RemoteData) => { + this.communityDataService.create(community).pipe(take(1)).subscribe((rd: RemoteData) => { console.log(rd); if (rd.hasSucceeded) { this.router.navigateByUrl(''); diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 045f98a14f..a29cb7877f 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -19,7 +19,7 @@ import { } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; -import { distinctUntilChanged, map, share, switchMap, withLatestFrom } from 'rxjs/operators'; +import { distinctUntilChanged, map, share, switchMap, take, withLatestFrom } from 'rxjs/operators'; import { configureRequest, filterSuccessfulResponses, getRequestFromSelflink, getRequestFromUUID, @@ -30,9 +30,8 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpHeaders } from '@angular/common/http'; import { DSOSuccessResponse, GenericSuccessResponse } from '../cache/response-cache.models'; import { AuthService } from '../auth/auth.service'; -import { DSpaceObject } from '../shared/dspace-object.model'; -import { first } from 'rxjs/operator/first'; -import { take } from 'rxjs/operator/take'; +import { Collection } from '../shared/collection.model'; +import { Community } from '../shared/community.model'; export abstract class DataService { protected abstract responseCache: ResponseCacheService; @@ -118,33 +117,38 @@ export abstract class DataService } public create(dso: TDomain): Observable> { - const request$ = this.halService.getEndpoint(this.linkPath).pipe( + const requestId = this.requestService.generateRequestId(); + const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), distinctUntilChanged(), withLatestFrom(this.buildCreateParams(dso)), - map(([endpointURL, params]) => { + map(([endpointURL, params]) => endpointURL + params) + ); + + const request$ = endpoint$.pipe( + take(1), + map((endpoint) => { const options: HttpOptions = Object.create({}); const headers = new HttpHeaders(); headers.append('Authentication', this.authService.buildAuthHeader()); options.headers = headers; - return new CreateRequest(this.requestService.generateRequestId(), endpointURL + params, options); + return new CreateRequest(requestId, endpoint, options); }), - configureRequest(this.requestService), - share() + configureRequest(this.requestService) ); - const href$ = request$.pipe(map((request: RestRequest) => request.href)); - const uuid$ = request$.pipe(map((request: RestRequest) => request.uuid)); - const requestEntry$ = uuid$.pipe(getRequestFromUUID(this.requestService)); - const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); - - const payload$ = responseCache$.pipe( + const payload$ = request$.pipe( + map((request: RestRequest) => request.href), + getResponseFromSelflink(this.responseCache), filterSuccessfulResponses(), map((entry: ResponseCacheEntry) => entry.response), map((response: GenericSuccessResponse) => response.payload), distinctUntilChanged() ); + const requestEntry$ = this.requestService.getByUUID(requestId); + const responseCache$ = endpoint$.pipe(getResponseFromSelflink(this.responseCache)); + return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); } From 12058cb4dbcb2ec8135dfa8f57b20b7448f17307 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 11 Sep 2018 08:31:41 +0200 Subject: [PATCH 22/57] 54472: Remove console logs --- .../create-collection-page/create-collection-page.component.ts | 1 - .../create-community-page/create-community-page.component.ts | 1 - 2 files changed, 2 deletions(-) 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 f94547c8fb..1c2f78bb06 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 @@ -52,7 +52,6 @@ export class CreateCollectionPageComponent { }))) }); this.collectionDataService.create(collection).subscribe((rd: RemoteData) => { - console.log(rd); if (rd.hasSucceeded) { this.router.navigateByUrl(''); } 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 69012ca2a4..c79b6fa9cd 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 @@ -46,7 +46,6 @@ export class CreateCommunityPageComponent { }))) }); this.communityDataService.create(community).pipe(take(1)).subscribe((rd: RemoteData) => { - console.log(rd); if (rd.hasSucceeded) { this.router.navigateByUrl(''); } From dfb4a3bd9c28889f7599a86d03f03477256440c1 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 11 Sep 2018 09:21:35 +0200 Subject: [PATCH 23/57] 54472: Notification errors --- src/app/core/data/collection-data.service.ts | 3 ++- src/app/core/data/comcol-data.service.spec.ts | 1 + src/app/core/data/community-data.service.ts | 4 +++- src/app/core/data/data.service.spec.ts | 8 ++++++-- src/app/core/data/data.service.ts | 12 +++++++++++- src/app/core/data/dspace-object-data.service.ts | 9 ++++++--- src/app/core/data/item-data.service.ts | 3 ++- 7 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index dc3635595a..fd2404650f 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -27,7 +27,8 @@ export class CollectionDataService extends ComColDataService { protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, + protected notificationsService: NotificationsService, protected linkPath: string ) { super(); diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 4570e749d8..4689fac854 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -16,6 +16,7 @@ import { RemoteData } from './remote-data'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { Observable } from 'rxjs/Observable'; import { PaginatedList } from './paginated-list'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; @Injectable() export class CommunityDataService extends ComColDataService { @@ -30,7 +31,8 @@ export class CommunityDataService extends ComColDataService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, - protected authService: AuthService + protected authService: AuthService, + protected notificationsService: NotificationsService ) { super(); } diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index 8377afe92e..8abf4e51ca 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -9,6 +9,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Observable } from 'rxjs/Observable'; import { FindAllOptions } from './request.models'; import { SortOptions, SortDirection } from '../cache/models/sort-options.model'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; const LINK_NAME = 'test' @@ -23,7 +24,8 @@ class TestService extends DataService { protected rdbService: RemoteDataBuildService, protected store: Store, protected linkPath: string, - protected halService: HALEndpointService + protected halService: HALEndpointService, + protected notificationsService: NotificationsService ) { super(); } @@ -41,6 +43,7 @@ describe('DataService', () => { const requestService = {} as RequestService; const halService = {} as HALEndpointService; const rdbService = {} as RemoteDataBuildService; + const notificationsService = {} as NotificationsService; const store = {} as Store; const endpoint = 'https://rest.api/core'; @@ -51,7 +54,8 @@ describe('DataService', () => { rdbService, store, LINK_NAME, - halService + halService, + notificationsService ); } diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index a29cb7877f..9d7422cbe1 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -28,10 +28,12 @@ import { import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpHeaders } from '@angular/common/http'; -import { DSOSuccessResponse, GenericSuccessResponse } from '../cache/response-cache.models'; +import { DSOSuccessResponse, ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models'; import { AuthService } from '../auth/auth.service'; import { Collection } from '../shared/collection.model'; import { Community } from '../shared/community.model'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; export abstract class DataService { protected abstract responseCache: ResponseCacheService; @@ -41,6 +43,7 @@ export abstract class DataService protected abstract linkPath: string; protected abstract halService: HALEndpointService; protected abstract authService: AuthService; + protected abstract notificationsService: NotificationsService; public abstract getScopedEndpoint(scope: string): Observable @@ -140,6 +143,13 @@ export abstract class DataService const payload$ = request$.pipe( map((request: RestRequest) => request.href), getResponseFromSelflink(this.responseCache), + map((response: ResponseCacheEntry) => { + if (!response.response.isSuccessful && response.response instanceof ErrorResponse) { + const errorResponse: ErrorResponse = response.response; + this.notificationsService.error('Server Error:', errorResponse.errorMessage, new NotificationOptions(-1)); + } + return response; + }), filterSuccessfulResponses(), map((entry: ResponseCacheEntry) => entry.response), map((response: GenericSuccessResponse) => response.payload), diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index bf2da345b8..eb657e1737 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -11,6 +11,7 @@ import { DataService } from './data.service'; import { RemoteData } from './remote-data'; import { RequestService } from './request.service'; import { AuthService } from '../auth/auth.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; /* tslint:disable:max-classes-per-file */ class DataServiceImpl extends DataService { @@ -22,7 +23,8 @@ class DataServiceImpl extends DataService protected rdbService: RemoteDataBuildService, protected store: Store, protected halService: HALEndpointService, - protected authService: AuthService) { + protected authService: AuthService, + protected notificationsService: NotificationsService) { super(); } @@ -48,8 +50,9 @@ export class DSpaceObjectDataService { protected requestService: RequestService, protected rdbService: RemoteDataBuildService, protected halService: HALEndpointService, - protected authService: AuthService) { - this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService, authService); + protected authService: AuthService, + protected notificationsService: NotificationsService) { + this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService, authService, notificationsService); } findById(uuid: string): Observable> { diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index d1db71b78d..448d3fc84b 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -29,7 +29,8 @@ export class ItemDataService extends DataService { protected store: Store, private bs: BrowseService, protected halService: HALEndpointService, - protected authService: AuthService) { + protected authService: AuthService, + protected notificationsService: NotificationsService) { super(); } From 2740f690250a092e2be093b687d3e5090e113bff Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 11 Sep 2018 09:22:45 +0200 Subject: [PATCH 24/57] 54472: Removed old error messages --- .../create-collection-page.component.html | 4 ---- .../create-collection-page.component.ts | 1 - .../create-community-page.component.html | 4 ---- .../create-community-page/create-community-page.component.ts | 1 - 4 files changed, 10 deletions(-) diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.html b/src/app/+collection-page/create-collection-page/create-collection-page.component.html index 11f4f53ecd..1d1f8deb6c 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.html +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.html @@ -7,9 +7,5 @@
-
- {{error.statusCode}} -

{{error.errorMessage}}

-
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 1c2f78bb06..0f79558a44 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 @@ -22,7 +22,6 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model'; }) export class CreateCollectionPageComponent { - public error$: Observable; public parentUUID$: Observable; public communityRDObs: Observable>; diff --git a/src/app/+community-page/create-community-page/create-community-page.component.html b/src/app/+community-page/create-community-page/create-community-page.component.html index 06fd643530..35f1deaa73 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.html +++ b/src/app/+community-page/create-community-page/create-community-page.component.html @@ -7,9 +7,5 @@
-
- {{error.statusCode}} -

{{error.errorMessage}}

-
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 c79b6fa9cd..9a4730f74f 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 @@ -17,7 +17,6 @@ import { take } from 'rxjs/operators'; }) export class CreateCommunityPageComponent { - public error$: Observable; public parentUUID$: Observable; public communityRDObs: Observable>; From ad9852f1941609f9364affc04ab6cc32f13b5364 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 11 Sep 2018 14:50:23 +0200 Subject: [PATCH 25/57] 54472: form-data intermediate commit --- src/app/core/data/comcol-data.service.ts | 14 +++++++---- src/app/core/data/data.service.ts | 24 ++++++++----------- .../core/data/dspace-object-data.service.ts | 2 +- src/app/core/data/item-data.service.ts | 4 ++-- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index c47d48dc07..c9dff8fbe1 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -66,19 +66,23 @@ export abstract class ComColDataService { + public buildCreateBody(comcol): Observable { return comcol.owner.pipe( map((rd: RemoteData) => { - let urlParams = '?name=' + comcol.name; + const form: any = { + name: comcol.name + }; if (rd.payload.id) { - urlParams += '&parent=' + rd.payload.id; + form.parent = rd.payload.id; } if (comcol.metadata) { for (const i of Object.keys(comcol.metadata)) { - urlParams += '&' + comcol.metadata[i].key + '=' + comcol.metadata[i].value; + if (isNotEmpty(comcol.metadata[i].value)) { + form[comcol.metadata[i].key] = comcol.metadata[i].value; + } } } - return urlParams; + return form; }) ); } diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 9d7422cbe1..3dc32e9e69 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -14,24 +14,21 @@ import { FindAllRequest, FindByIDRequest, GetRequest, - PostRequest, RestRequest } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; -import { distinctUntilChanged, map, share, switchMap, take, withLatestFrom } from 'rxjs/operators'; +import { distinctUntilChanged, map, take, withLatestFrom } from 'rxjs/operators'; import { configureRequest, - filterSuccessfulResponses, getRequestFromSelflink, getRequestFromUUID, + filterSuccessfulResponses, getResponseFromSelflink } from '../shared/operators'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpHeaders } from '@angular/common/http'; -import { DSOSuccessResponse, ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models'; +import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models'; import { AuthService } from '../auth/auth.service'; -import { Collection } from '../shared/collection.model'; -import { Community } from '../shared/community.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; @@ -123,19 +120,18 @@ export abstract class DataService const requestId = this.requestService.generateRequestId(); const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), - distinctUntilChanged(), - withLatestFrom(this.buildCreateParams(dso)), - map(([endpointURL, params]) => endpointURL + params) + distinctUntilChanged() ); const request$ = endpoint$.pipe( take(1), - map((endpoint) => { + withLatestFrom(this.buildCreateBody(dso)), + map(([endpoint, formdata]) => { const options: HttpOptions = Object.create({}); - const headers = new HttpHeaders(); - headers.append('Authentication', this.authService.buildAuthHeader()); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type','multipart/form-data'); options.headers = headers; - return new CreateRequest(requestId, endpoint, options); + return new CreateRequest(requestId, endpoint, formdata, options); }), configureRequest(this.requestService) ); @@ -162,6 +158,6 @@ export abstract class DataService return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); } - public abstract buildCreateParams(dso: TDomain): Observable; + public abstract buildCreateBody(dso: TDomain): Observable; } diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index eb657e1737..8bae4e56ed 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -36,7 +36,7 @@ class DataServiceImpl extends DataService return endpoint.replace(/\{\?uuid\}/,`?uuid=${resourceID}`); } - buildCreateParams(dso: DSpaceObject): Observable { + buildCreateBody(dso: DSpaceObject): Observable { return undefined; } } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 448d3fc84b..343d4efede 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -45,8 +45,8 @@ export class ItemDataService extends DataService { } } - buildCreateParams(dso: Item): Observable { - // TODO: Build parameters for creating an Item on the REST service + buildCreateBody(dso: Item): Observable { + // TODO: Build http body for creating an Item on the REST service return undefined; } From 758127e40981752adfb0d425e2723f98fdaf76c9 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 11 Sep 2018 16:23:57 +0200 Subject: [PATCH 26/57] 54472: Working post request with form-data (contains test code) --- .../create-collection-page.component.ts | 4 ++ src/app/core/data/collection-data.service.ts | 4 +- src/app/core/data/community-data.service.ts | 4 +- src/app/core/data/data.service.ts | 39 ++++++++++++++----- .../core/data/dspace-object-data.service.ts | 9 +++-- src/app/core/data/item-data.service.ts | 4 +- src/app/core/data/request.effects.ts | 2 +- 7 files changed, 50 insertions(+), 16 deletions(-) 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 0f79558a44..93c3c16646 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 @@ -14,6 +14,7 @@ import { map } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { HttpEvent } from '@angular/common/http'; @Component({ selector: 'ds-create-collection', @@ -55,6 +56,9 @@ export class CreateCollectionPageComponent { this.router.navigateByUrl(''); } }); + // this.collectionDataService.createSimple(collection).subscribe((httpEvent: HttpEvent<{}>) => { + // console.log(httpEvent); + // }); }); } diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index fd2404650f..4157d96a9e 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -14,6 +14,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { AuthService } from '../auth/auth.service'; import { Community } from '../shared/community.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -28,7 +29,8 @@ export class CollectionDataService extends ComColDataService { @@ -32,7 +33,8 @@ export class CommunityDataService extends ComColDataService protected abstract halService: HALEndpointService; protected abstract authService: AuthService; protected abstract notificationsService: NotificationsService; + protected abstract http: HttpClient; public abstract getScopedEndpoint(scope: string): Observable @@ -126,17 +127,12 @@ export abstract class DataService const request$ = endpoint$.pipe( take(1), withLatestFrom(this.buildCreateBody(dso)), - map(([endpoint, formdata]) => { - const options: HttpOptions = Object.create({}); - let headers = new HttpHeaders(); - headers = headers.append('Content-Type','multipart/form-data'); - options.headers = headers; - return new CreateRequest(requestId, endpoint, formdata, options); - }), + map(([endpoint, formdata]) => new CreateRequest(requestId, endpoint, this.buildFormData(formdata))), configureRequest(this.requestService) ); const payload$ = request$.pipe( + take(1), map((request: RestRequest) => request.href), getResponseFromSelflink(this.responseCache), map((response: ResponseCacheEntry) => { @@ -158,6 +154,31 @@ export abstract class DataService return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); } + public createSimple(dso: TDomain): Observable> { + const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( + isNotEmptyOperator(), + distinctUntilChanged() + ); + + return endpoint$.pipe( + withLatestFrom(this.buildCreateBody(dso)), + switchMap(([endpoint, form]) => { + const req = new HttpRequest('POST', endpoint, this.buildFormData(form)); + return this.http.request(req); + }) + ); + } + public abstract buildCreateBody(dso: TDomain): Observable; + protected buildFormData(form: any): FormData { + const formdata = new FormData(); + for (const param in form) { + if (form.hasOwnProperty(param)) { + formdata.append(param, form[param]); + } + } + return formdata; + } + } diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 8bae4e56ed..ecf52098ee 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -12,6 +12,7 @@ import { RemoteData } from './remote-data'; import { RequestService } from './request.service'; import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; /* tslint:disable:max-classes-per-file */ class DataServiceImpl extends DataService { @@ -24,7 +25,8 @@ class DataServiceImpl extends DataService protected store: Store, protected halService: HALEndpointService, protected authService: AuthService, - protected notificationsService: NotificationsService) { + protected notificationsService: NotificationsService, + protected http: HttpClient) { super(); } @@ -51,8 +53,9 @@ export class DSpaceObjectDataService { protected rdbService: RemoteDataBuildService, protected halService: HALEndpointService, protected authService: AuthService, - protected notificationsService: NotificationsService) { - this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService, authService, notificationsService); + protected notificationsService: NotificationsService, + protected http: HttpClient) { + this.dataService = new DataServiceImpl(null, requestService, rdbService, null, halService, authService, notificationsService, http); } findById(uuid: string): Observable> { diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 343d4efede..4c003e15c9 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -17,6 +17,7 @@ import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { AuthService } from '../auth/auth.service'; +import { HttpClient } from '@angular/common/http'; @Injectable() export class ItemDataService extends DataService { @@ -30,7 +31,8 @@ export class ItemDataService extends DataService { private bs: BrowseService, protected halService: HALEndpointService, protected authService: AuthService, - protected notificationsService: NotificationsService) { + protected notificationsService: NotificationsService, + protected http: HttpClient) { super(); } diff --git a/src/app/core/data/request.effects.ts b/src/app/core/data/request.effects.ts index 5fadd316f4..9d27717dc6 100644 --- a/src/app/core/data/request.effects.ts +++ b/src/app/core/data/request.effects.ts @@ -38,7 +38,7 @@ export class RequestEffects { }), map((entry: RequestEntry) => entry.request), flatMap((request: RestRequest) => { - let body; + let body = request.body; if (isNotEmpty(request.body)) { const serializer = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(request.body.type)); body = serializer.serialize(request.body); From 0197b4a9dc8ec90032d185528c87906d51fc0eaf Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 12 Sep 2018 08:11:33 +0200 Subject: [PATCH 27/57] 54472: Passing parent as parameter and fixed duplicate collection created --- .../create-collection-page.component.ts | 15 +++------ .../create-community-page.component.ts | 10 ++---- src/app/core/data/comcol-data.service.ts | 31 ++++++++---------- src/app/core/data/data.service.ts | 32 ++----------------- .../core/data/dspace-object-data.service.ts | 2 +- src/app/core/data/item-data.service.ts | 4 +-- 6 files changed, 26 insertions(+), 68 deletions(-) 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 93c3c16646..6cca8f1d09 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 @@ -10,7 +10,7 @@ import { Router } from '@angular/router'; import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; import { Observable } from 'rxjs/Observable'; import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; -import { map } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; @@ -36,7 +36,7 @@ export class CreateCollectionPageComponent { } onSubmit(data: any) { - this.parentUUID$.subscribe((uuid: string) => { + this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { const collection = Object.assign(new Collection(), { name: data.name, metadata: [ @@ -45,20 +45,13 @@ export class CreateCollectionPageComponent { { key: 'dc.rights', value: data.copyright }, { key: 'dc.rights.license', value: data.license } // TODO: metadata for news and provenance - ], - owner: Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { - id: uuid, - uuid: uuid - }))) + ] }); - this.collectionDataService.create(collection).subscribe((rd: RemoteData) => { + this.collectionDataService.create(collection, uuid).pipe(take(1)).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { this.router.navigateByUrl(''); } }); - // this.collectionDataService.createSimple(collection).subscribe((httpEvent: HttpEvent<{}>) => { - // console.log(httpEvent); - // }); }); } 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 9a4730f74f..afb90141c9 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 @@ -30,7 +30,7 @@ export class CreateCommunityPageComponent { } onSubmit(data: any) { - this.parentUUID$.subscribe((uuid: string) => { + this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { const community = Object.assign(new Community(), { name: data.name, metadata: [ @@ -38,13 +38,9 @@ export class CreateCommunityPageComponent { { key: 'dc.description.abstract', value: data.description }, { key: 'dc.rights', value: data.copyright } // TODO: metadata for news - ], - owner: Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { - id: uuid, - uuid: uuid - }))) + ] }); - this.communityDataService.create(community).pipe(take(1)).subscribe((rd: RemoteData) => { + this.communityDataService.create(community, uuid).pipe(take(1)).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { this.router.navigateByUrl(''); } diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index c9dff8fbe1..8519bb2b25 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -66,25 +66,20 @@ export abstract class ComColDataService { - return comcol.owner.pipe( - map((rd: RemoteData) => { - const form: any = { - name: comcol.name - }; - if (rd.payload.id) { - form.parent = rd.payload.id; + public buildFormData(comcol, parentUUID): FormData { + const form: FormData = new FormData(); + form.append('name', comcol.name); + if (isNotEmpty(parentUUID)) { + form.append('parent', parentUUID); + } + if (comcol.metadata) { + for (const i of Object.keys(comcol.metadata)) { + if (isNotEmpty(comcol.metadata[i].value)) { + form.append(comcol.metadata[i].key, comcol.metadata[i].value); } - if (comcol.metadata) { - for (const i of Object.keys(comcol.metadata)) { - if (isNotEmpty(comcol.metadata[i].value)) { - form[comcol.metadata[i].key] = comcol.metadata[i].value; - } - } - } - return form; - }) - ); + } + } + return form; } } diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index fb3002b5d7..b2946b2164 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -117,7 +117,7 @@ export abstract class DataService return this.rdbService.buildSingle(href); } - public create(dso: TDomain): Observable> { + public create(dso: TDomain, parentUUID: string): Observable> { const requestId = this.requestService.generateRequestId(); const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), @@ -126,8 +126,7 @@ export abstract class DataService const request$ = endpoint$.pipe( take(1), - withLatestFrom(this.buildCreateBody(dso)), - map(([endpoint, formdata]) => new CreateRequest(requestId, endpoint, this.buildFormData(formdata))), + map((endpoint: string) => new CreateRequest(requestId, endpoint, this.buildFormData(dso, parentUUID))), configureRequest(this.requestService) ); @@ -154,31 +153,6 @@ export abstract class DataService return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); } - public createSimple(dso: TDomain): Observable> { - const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( - isNotEmptyOperator(), - distinctUntilChanged() - ); - - return endpoint$.pipe( - withLatestFrom(this.buildCreateBody(dso)), - switchMap(([endpoint, form]) => { - const req = new HttpRequest('POST', endpoint, this.buildFormData(form)); - return this.http.request(req); - }) - ); - } - - public abstract buildCreateBody(dso: TDomain): Observable; - - protected buildFormData(form: any): FormData { - const formdata = new FormData(); - for (const param in form) { - if (form.hasOwnProperty(param)) { - formdata.append(param, form[param]); - } - } - return formdata; - } + public abstract buildFormData(dso: TDomain, parentUUID: string): FormData; } diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index ecf52098ee..14540d0782 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -38,7 +38,7 @@ class DataServiceImpl extends DataService return endpoint.replace(/\{\?uuid\}/,`?uuid=${resourceID}`); } - buildCreateBody(dso: DSpaceObject): Observable { + buildFormData(dso: DSpaceObject, parentUUID: string): FormData { return undefined; } } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 4c003e15c9..c3c32691ce 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -47,8 +47,8 @@ export class ItemDataService extends DataService { } } - buildCreateBody(dso: Item): Observable { - // TODO: Build http body for creating an Item on the REST service + buildFormData(dso: Item, parentUUID: string): FormData { + // TODO: build FormData for creating an Item return undefined; } From 6973c66d797563fd722db900ccf43aede7aa3e8b Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 12 Sep 2018 10:49:31 +0200 Subject: [PATCH 28/57] 54472: Intermediate commit --- src/app/core/data/comcol-data.service.ts | 16 ---------------- src/app/core/data/data.service.ts | 7 +++---- .../core/data/dspace-object-data.service.ts | 4 ---- src/app/core/data/item-data.service.ts | 5 ----- src/app/core/data/request.effects.ts | 2 +- .../dspace-rest-v2/dspace-rest-v2.service.ts | 18 ++++++++++++++++++ 6 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 8519bb2b25..0859d075ca 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -66,20 +66,4 @@ export abstract class ComColDataService const requestId = this.requestService.generateRequestId(); const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), - distinctUntilChanged() + distinctUntilChanged(), + map((endpoint: string) => parentUUID ? `${endpoint}?parent=${parentUUID}` : endpoint) ); const request$ = endpoint$.pipe( take(1), - map((endpoint: string) => new CreateRequest(requestId, endpoint, this.buildFormData(dso, parentUUID))), + map((endpoint: string) => new CreateRequest(requestId, endpoint, dso)), configureRequest(this.requestService) ); @@ -153,6 +154,4 @@ export abstract class DataService return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); } - public abstract buildFormData(dso: TDomain, parentUUID: string): FormData; - } diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 14540d0782..d14d7f8a0b 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -37,10 +37,6 @@ class DataServiceImpl extends DataService getFindByIDHref(endpoint, resourceID): string { return endpoint.replace(/\{\?uuid\}/,`?uuid=${resourceID}`); } - - buildFormData(dso: DSpaceObject, parentUUID: string): FormData { - return undefined; - } } @Injectable() diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index c3c32691ce..6b62fe6688 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -47,9 +47,4 @@ export class ItemDataService extends DataService { } } - buildFormData(dso: Item, parentUUID: string): FormData { - // TODO: build FormData for creating an Item - return undefined; - } - } diff --git a/src/app/core/data/request.effects.ts b/src/app/core/data/request.effects.ts index 9d27717dc6..5fadd316f4 100644 --- a/src/app/core/data/request.effects.ts +++ b/src/app/core/data/request.effects.ts @@ -38,7 +38,7 @@ export class RequestEffects { }), map((entry: RequestEntry) => entry.request), flatMap((request: RestRequest) => { - let body = request.body; + let body; if (isNotEmpty(request.body)) { const serializer = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(request.body.type)); body = serializer.serialize(request.body); diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts index 78c93b8c08..c8cced9e8a 100644 --- a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts @@ -6,6 +6,8 @@ import { RestRequestMethod } from '../data/request.models'; import { DSpaceRESTV2Response } from './dspace-rest-v2-response.model'; import { HttpObserve } from '@angular/common/http/src/client'; +import { isNotEmpty } from '../../shared/empty.util'; +import { DSpaceObject } from '../shared/dspace-object.model'; export interface HttpOptions { body?: any; @@ -59,6 +61,9 @@ export class DSpaceRESTv2Service { request(method: RestRequestMethod, url: string, body?: any, options?: HttpOptions): Observable { const requestOptions: HttpOptions = {}; requestOptions.body = body; + if (method === RestRequestMethod.Post && isNotEmpty(body)) { + requestOptions.body = this.buildFormData(body); + } requestOptions.observe = 'response'; if (options && options.headers) { requestOptions.headers = Object.assign(new HttpHeaders(), options.headers); @@ -74,4 +79,17 @@ export class DSpaceRESTv2Service { }); } + buildFormData(dso: DSpaceObject): FormData { + const form: FormData = new FormData(); + form.append('name', dso.name); + if (dso.metadata) { + for (const i of Object.keys(dso.metadata)) { + if (isNotEmpty(dso.metadata[i].value)) { + form.append(dso.metadata[i].key, dso.metadata[i].value); + } + } + } + return form; + } + } From 6c8e49889174a105e4c012030c843d80f2317356 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 13 Sep 2018 14:19:02 +0200 Subject: [PATCH 29/57] 54472: Small changes --- .../create-collection-page.component.ts | 16 ++++++++++------ .../dspace-rest-v2/dspace-rest-v2.service.ts | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) 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 6cca8f1d09..aea405ea09 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 @@ -10,7 +10,7 @@ import { Router } from '@angular/router'; import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; import { Observable } from 'rxjs/Observable'; import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; -import { map, take } from 'rxjs/operators'; +import { first, map, take } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; @@ -26,7 +26,12 @@ export class CreateCollectionPageComponent { public parentUUID$: Observable; public communityRDObs: Observable>; - public constructor(private collectionDataService: CollectionDataService, private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router) { + public constructor( + private collectionDataService: CollectionDataService, + private communityDataService: CommunityDataService, + private routeService: RouteService, + private router: Router + ) { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); this.parentUUID$.subscribe((uuid: string) => { if (isNotEmpty(uuid)) { @@ -47,10 +52,9 @@ export class CreateCollectionPageComponent { // TODO: metadata for news and provenance ] }); - this.collectionDataService.create(collection, uuid).pipe(take(1)).subscribe((rd: RemoteData) => { - if (rd.hasSucceeded) { - this.router.navigateByUrl(''); - } + this.collectionDataService.create(collection, uuid).subscribe((rd: RemoteData) => { + const url: string = '/collections/' + rd.payload.id; + this.router.navigateByUrl(url); }); }); } diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts index c8cced9e8a..eafa9c46a0 100644 --- a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts @@ -61,7 +61,7 @@ export class DSpaceRESTv2Service { request(method: RestRequestMethod, url: string, body?: any, options?: HttpOptions): Observable { const requestOptions: HttpOptions = {}; requestOptions.body = body; - if (method === RestRequestMethod.Post && isNotEmpty(body)) { + if (method === RestRequestMethod.Post && isNotEmpty(body.name)) { requestOptions.body = this.buildFormData(body); } requestOptions.observe = 'response'; From f9203a5cb693ee49c246c061cc1273cd401dd497 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 14 Sep 2018 12:02:20 +0200 Subject: [PATCH 30/57] 54472: Tests + caching fix attempts intermediate commit --- .../create-collection-page.component.spec.ts | 25 ++++--- .../create-collection-page.component.ts | 18 +++-- .../create-community-page.component.spec.ts | 25 ++++--- .../create-community-page.component.ts | 14 +++- src/app/core/data/comcol-data.service.spec.ts | 69 ++----------------- src/app/core/data/data.service.spec.ts | 8 ++- src/app/core/data/data.service.ts | 10 +-- .../dspace-rest-v2/dspace-rest-v2.service.ts | 2 +- .../core/metadata/metadata.service.spec.ts | 9 +++ src/app/core/shared/operators.ts | 2 +- 10 files changed, 76 insertions(+), 106 deletions(-) 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 efe0e3da97..6196945d80 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 @@ -31,10 +31,13 @@ describe('CreateCollectionPageComponent', () => { name: 'test community' }); + const collection = Object.assign(new Collection(), { + uuid: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4', + name: 'new collection' + }); + const collectionDataServiceStub = { - create: (com, uuid?) => Observable.of({ - response: new DSOSuccessResponse(null,'200',null) - }) + create: (col, uuid?) => Observable.of(new RemoteData(false, false, true, undefined, collection)) }; const communityDataServiceStub = { findById: (uuid) => Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { @@ -46,7 +49,7 @@ describe('CreateCollectionPageComponent', () => { getQueryParameterValue: (param) => Observable.of(community.uuid) }; const routerStub = { - navigateByUrl: (url) => url + navigate: (commands) => commands }; beforeEach(async(() => { @@ -78,22 +81,18 @@ describe('CreateCollectionPageComponent', () => { }; it('should navigate when successful', () => { - spyOn(router, 'navigateByUrl'); + spyOn(router, 'navigate'); comp.onSubmit(data); fixture.detectChanges(); - expect(router.navigateByUrl).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); }); it('should not navigate on failure', () => { - spyOn(router, 'navigateByUrl'); - spyOn(collectionDataService, 'create').and.returnValue(Observable.of({ - response: Object.assign(new ErrorResponse(new RequestError()), { - isSuccessful: false - }) - })); + spyOn(router, 'navigate'); + spyOn(collectionDataService, 'create').and.returnValue(Observable.of(new RemoteData(true, true, false, undefined, collection))); comp.onSubmit(data); fixture.detectChanges(); - expect(router.navigateByUrl).not.toHaveBeenCalled(); + expect(router.navigate).not.toHaveBeenCalled(); }); }); }); 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 aea405ea09..4d94a53035 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 @@ -10,11 +10,14 @@ import { Router } from '@angular/router'; import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; import { Observable } from 'rxjs/Observable'; import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer'; -import { first, map, take } from 'rxjs/operators'; +import { first, flatMap, map, take } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; -import { isNotEmpty } from '../../shared/empty.util'; +import { hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { HttpEvent } from '@angular/common/http'; +import { getSucceededRemoteData } from '../../core/shared/operators'; +import { ObjectCacheService } from '../../core/cache/object-cache.service'; +import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; @Component({ selector: 'ds-create-collection', @@ -30,7 +33,8 @@ export class CreateCollectionPageComponent { private collectionDataService: CollectionDataService, private communityDataService: CommunityDataService, private routeService: RouteService, - private router: Router + private router: Router, + private objectCache: ObjectCacheService ) { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); this.parentUUID$.subscribe((uuid: string) => { @@ -52,9 +56,11 @@ export class CreateCollectionPageComponent { // TODO: metadata for news and provenance ] }); - this.collectionDataService.create(collection, uuid).subscribe((rd: RemoteData) => { - const url: string = '/collections/' + rd.payload.id; - this.router.navigateByUrl(url); + this.collectionDataService.create(collection, uuid).pipe( + flatMap((rd: RemoteData) => this.objectCache.getByUUID(rd.payload.id)), + isNotEmptyOperator() + ).subscribe((col: NormalizedCollection) => { + this.router.navigate(['collections', col.id]); }); }); } 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 3c4c7648da..6c5eb5f591 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 @@ -27,20 +27,23 @@ describe('CreateCommunityPageComponent', () => { name: 'test community' }); + const newCommunity = Object.assign(new Community(), { + uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', + name: 'new community' + }); + const communityDataServiceStub = { findById: (uuid) => Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { uuid: uuid, name: community.name }))), - create: (com, uuid?) => Observable.of({ - response: new DSOSuccessResponse(null,'200',null) - }) + create: (com, uuid?) => Observable.of(new RemoteData(false, false, true, undefined, newCommunity)) }; const routeServiceStub = { getQueryParameterValue: (param) => Observable.of(community.uuid) }; const routerStub = { - navigateByUrl: (url) => url + navigate: (commands) => commands }; beforeEach(async(() => { @@ -70,22 +73,18 @@ describe('CreateCommunityPageComponent', () => { }; it('should navigate when successful', () => { - spyOn(router, 'navigateByUrl'); + spyOn(router, 'navigate'); comp.onSubmit(data); fixture.detectChanges(); - expect(router.navigateByUrl).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); }); it('should not navigate on failure', () => { - spyOn(router, 'navigateByUrl'); - spyOn(communityDataService, 'create').and.returnValue(Observable.of({ - response: Object.assign(new ErrorResponse(new RequestError()), { - isSuccessful: false - }) - })); + spyOn(router, 'navigate'); + spyOn(communityDataService, 'create').and.returnValue(Observable.of(new RemoteData(true, true, false, undefined, newCommunity))); comp.onSubmit(data); fixture.detectChanges(); - expect(router.navigateByUrl).not.toHaveBeenCalled(); + expect(router.navigate).not.toHaveBeenCalled(); }); }); }); 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 afb90141c9..a991bf7127 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 @@ -20,8 +20,12 @@ export class CreateCommunityPageComponent { public parentUUID$: Observable; public communityRDObs: Observable>; - public constructor(private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router) { - this.parentUUID$ = this.routeService.getQueryParameterValue('parent').pipe(take(1)); + public constructor( + private communityDataService: CommunityDataService, + private routeService: RouteService, + private router: Router + ) { + this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); this.parentUUID$.subscribe((uuid: string) => { if (isNotEmpty(uuid)) { this.communityRDObs = this.communityDataService.findById(uuid); @@ -42,7 +46,11 @@ export class CreateCommunityPageComponent { }); this.communityDataService.create(community, uuid).pipe(take(1)).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { - this.router.navigateByUrl(''); + if (uuid) { + this.router.navigate(['communities', uuid]); + } else { + this.router.navigate([]); + } } }); }); diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index ce3c581b31..0c03b64a1d 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -17,6 +17,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Community } from '../shared/community.model'; import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; const LINK_NAME = 'test'; @@ -37,6 +38,7 @@ class TestService extends ComColDataService { protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, + protected http: HttpClient, protected linkPath: string ) { super(); @@ -57,6 +59,8 @@ describe('ComColDataService', () => { const rdbService = {} as RemoteDataBuildService; const store = {} as Store; const EnvConfig = {} as GlobalConfig; + const notificationsService = {} as NotificationsService; + const http = {} as HttpClient; const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; const communitiesEndpoint = 'https://rest.api/core/communities'; @@ -115,6 +119,8 @@ describe('ComColDataService', () => { objectCache, halService, authService, + notificationsService, + http, LINK_NAME ); } @@ -177,67 +183,4 @@ describe('ComColDataService', () => { }); - describe('create', () => { - let community: Community; - const name = 'test community'; - - beforeEach(() => { - community = Object.assign(new Community(), { - name: name - }); - spyOn(service, 'buildCreateParams'); - }); - - describe('when creating a top-level community', () => { - it('should build params without parent UUID', () => { - scheduler.schedule(() => service.create(community).subscribe()); - scheduler.flush(); - expect(service.buildCreateParams).toHaveBeenCalledWith(community); - }); - }); - - describe('when creating a community part of another community', () => { - let parentCommunity: Community; - const parentName = 'test parent community'; - const parentUUID = 'a20da287-e174-466a-9926-f66b9300d347'; - - beforeEach(() => { - parentCommunity = Object.assign(new Community(), { - id: parentUUID, - uuid: parentUUID, - name: parentName - }); - }); - - it('should build params with parent UUID', () => { - scheduler.schedule(() => service.create(community, parentUUID).subscribe()); - scheduler.flush(); - expect(service.buildCreateParams).toHaveBeenCalledWith(community, parentUUID); - }); - }); - }); - - describe('buildCreateParams', () => { - let community: Community; - const name = 'test community'; - let parentCommunity: Community; - const parentName = 'test parent community'; - const parentUUID = 'a20da287-e174-466a-9926-f66b9300d347'; - - beforeEach(() => { - community = Object.assign(new Community(), { - name: name - }); - parentCommunity = Object.assign(new Community(), { - id: parentUUID, - uuid: parentUUID, - name: parentName - }); - }); - - it('should return the correct url parameters', () => { - expect(service.buildCreateParams(community, parentUUID)).toEqual('?name=' + name + '&parent=' + parentUUID); - }); - }); - }); diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index 8abf4e51ca..4faea85755 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -10,6 +10,7 @@ import { Observable } from 'rxjs/Observable'; import { FindAllOptions } from './request.models'; import { SortOptions, SortDirection } from '../cache/models/sort-options.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; const LINK_NAME = 'test' @@ -25,7 +26,8 @@ class TestService extends DataService { protected store: Store, protected linkPath: string, protected halService: HALEndpointService, - protected notificationsService: NotificationsService + protected notificationsService: NotificationsService, + protected http: HttpClient ) { super(); } @@ -44,6 +46,7 @@ describe('DataService', () => { const halService = {} as HALEndpointService; const rdbService = {} as RemoteDataBuildService; const notificationsService = {} as NotificationsService; + const http = {} as HttpClient; const store = {} as Store; const endpoint = 'https://rest.api/core'; @@ -55,7 +58,8 @@ describe('DataService', () => { store, LINK_NAME, halService, - notificationsService + notificationsService, + http ); } diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 13550b2358..0d746816c7 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -18,11 +18,11 @@ import { } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; -import { distinctUntilChanged, map, switchMap, take, withLatestFrom } from 'rxjs/operators'; +import { distinctUntilChanged, flatMap, map, switchMap, take, withLatestFrom } from 'rxjs/operators'; import { configureRequest, filterSuccessfulResponses, - getResponseFromSelflink + getResponseFromSelflink, getSucceededRemoteData } from '../shared/operators'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; @@ -31,6 +31,7 @@ import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.m import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; +import { combineLatest } from 'rxjs/observable/combineLatest'; export abstract class DataService { protected abstract responseCache: ResponseCacheService; @@ -132,7 +133,6 @@ export abstract class DataService ); const payload$ = request$.pipe( - take(1), map((request: RestRequest) => request.href), getResponseFromSelflink(this.responseCache), map((response: ResponseCacheEntry) => { @@ -151,7 +151,9 @@ export abstract class DataService const requestEntry$ = this.requestService.getByUUID(requestId); const responseCache$ = endpoint$.pipe(getResponseFromSelflink(this.responseCache)); - return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$); + return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$).pipe( + getSucceededRemoteData() + ); } } diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts index eafa9c46a0..8c5b5f1ac6 100644 --- a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts @@ -61,7 +61,7 @@ export class DSpaceRESTv2Service { request(method: RestRequestMethod, url: string, body?: any, options?: HttpOptions): Observable { const requestOptions: HttpOptions = {}; requestOptions.body = body; - if (method === RestRequestMethod.Post && isNotEmpty(body.name)) { + if (method === RestRequestMethod.Post && isNotEmpty(body) && isNotEmpty(body.name)) { requestOptions.body = this.buildFormData(body); } requestOptions.observe = 'response'; diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index f8f36a358e..ca3d3b0e95 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -34,6 +34,10 @@ import { MockItem } from '../../shared/mocks/mock-item'; import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; import { BrowseService } from '../browse/browse.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { AuthService } from '../auth/auth.service'; +import { REQUEST } from '@nguniversal/express-engine/tokens'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; /* tslint:disable:max-classes-per-file */ @Component({ @@ -69,6 +73,7 @@ describe('MetadataService', () => { let uuidService: UUIDService; let remoteDataBuildService: RemoteDataBuildService; let itemDataService: ItemDataService; + let authService: AuthService; let location: Location; let router: Router; @@ -115,6 +120,9 @@ describe('MetadataService', () => { { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, { provide: HALEndpointService, useValue: {}}, + { provide: AuthService, useValue: {} }, + { provide: NotificationsService, useValue: {} }, + { provide: HttpClient, useValue: {} }, Meta, Title, ItemDataService, @@ -127,6 +135,7 @@ describe('MetadataService', () => { title = TestBed.get(Title); itemDataService = TestBed.get(ItemDataService); metadataService = TestBed.get(MetadataService); + authService = TestBed.get(AuthService); envConfig = TestBed.get(GLOBAL_CONFIG); diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 350a664e9d..b44be403b8 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -58,7 +58,7 @@ export const getRemoteDataPayload = () => export const getSucceededRemoteData = () => (source: Observable>): Observable> => - source.pipe(first((rd: RemoteData) => rd.hasSucceeded)); + source.pipe(first((rd: RemoteData) => rd.hasSucceeded && !rd.isLoading)); export const toDSpaceObjectListRD = () => (source: Observable>>>): Observable>> => From 6aee6e668c5e0e9e6c598a5f991c0698dee83c7f Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 14 Sep 2018 16:27:41 +0200 Subject: [PATCH 31/57] 54472: DataService create method tests pt1 --- src/app/core/data/data.service.spec.ts | 59 +++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 5 deletions(-) diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index 4faea85755..7ebe536219 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -11,8 +11,15 @@ import { FindAllOptions } from './request.models'; import { SortOptions, SortDirection } from '../cache/models/sort-options.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; +import { AuthService } from '../auth/auth.service'; +import { getMockRequestService } from '../../shared/mocks/mock-request.service'; +import { getMockResponseCacheService } from '../../shared/mocks/mock-response-cache.service'; +import { DSpaceObject } from '../shared/dspace-object.model'; +import { RemoteData } from './remote-data'; +import { RequestEntry } from './request.reducer'; +import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; -const LINK_NAME = 'test' +const LINK_NAME = 'test'; // tslint:disable:max-classes-per-file class NormalizedTestObject extends NormalizedObject { @@ -26,6 +33,7 @@ class TestService extends DataService { protected store: Store, protected linkPath: string, protected halService: HALEndpointService, + protected authService: AuthService, protected notificationsService: NotificationsService, protected http: HttpClient ) { @@ -41,14 +49,19 @@ class TestService extends DataService { describe('DataService', () => { let service: TestService; let options: FindAllOptions; - const responseCache = {} as ResponseCacheService; - const requestService = {} as RequestService; - const halService = {} as HALEndpointService; - const rdbService = {} as RemoteDataBuildService; + const responseCache = getMockResponseCacheService(); + let rdbService = {} as RemoteDataBuildService; + const authService = {} as AuthService; const notificationsService = {} as NotificationsService; const http = {} as HttpClient; const store = {} as Store; const endpoint = 'https://rest.api/core'; + const halService = Object.assign({ + getEndpoint: () => Observable.of(endpoint) + }); + const requestService = Object.assign(getMockRequestService(), { + getByUUID: () => Observable.of(new RequestEntry()) + }); function initTestService(): TestService { return new TestService( @@ -58,6 +71,7 @@ describe('DataService', () => { store, LINK_NAME, halService, + authService, notificationsService, http ); @@ -138,4 +152,39 @@ describe('DataService', () => { }) }); + fdescribe('create', () => { + const dso = new DSpaceObject(); + const successfulRd$ = Observable.of(new RemoteData(false, false, true, undefined, dso)); + const failingRd$ = Observable.of(new RemoteData(false, false, false, undefined, dso)); + + describe('when the request was successful', () => { + beforeEach(() => { + rdbService = getMockRemoteDataBuildService(successfulRd$); + service = initTestService(); + }); + + it('should return a RemoteData of a DSpaceObject', () => { + service.create(dso, undefined).subscribe((rd: RemoteData) => { + expect(rd.payload).toBe(dso); + }); + }); + }); + + describe('when the request was unsuccessful', () => { + beforeEach(() => { + rdbService = getMockRemoteDataBuildService(failingRd$); + service = initTestService(); + }); + + it('should not return anything', () => { + service.create(dso, undefined); + + // TODO: Expect create to emit nothing + }); + }); + + // TODO: Create tests with passing parent + + }); + }); From 9da839e318886f59b49f3a208a8947df2fc50989 Mon Sep 17 00:00:00 2001 From: lotte Date: Mon, 17 Sep 2018 08:47:30 +0200 Subject: [PATCH 32/57] refactoring --- .../builders/remote-data-build.service.ts | 4 ++ src/app/core/core.module.ts | 2 - src/app/core/data/data.service.ts | 26 +++++------- src/app/core/data/request.models.ts | 6 +-- .../single-dso-response-parsing.service.ts | 40 ------------------- src/app/core/shared/hal-endpoint.service.ts | 4 +- 6 files changed, 17 insertions(+), 65 deletions(-) delete mode 100644 src/app/core/data/single-dso-response-parsing.service.ts diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index d934f60e48..841a4483ca 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -22,6 +22,7 @@ import { getResponseFromSelflink, filterSuccessfulResponses } from '../../shared/operators'; +import { ReplaySubject } from 'rxjs/ReplaySubject'; @Injectable() export class RemoteDataBuildService { @@ -34,6 +35,8 @@ export class RemoteDataBuildService { if (typeof href$ === 'string') { href$ = Observable.of(href$); } + href$ = href$.multicast(new ReplaySubject(1)).refCount(); + const requestHref$ = href$.pipe(flatMap((href: string) => this.objectCache.getRequestHrefBySelfLink(href))); @@ -112,6 +115,7 @@ export class RemoteDataBuildService { if (typeof href$ === 'string') { href$ = Observable.of(href$); } + href$ = href$.shareReplay(); const requestEntry$ = href$.pipe(getRequestFromSelflink(this.requestService)); const responseCache$ = href$.pipe(getResponseFromSelflink(this.responseCache)); diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 61568b99a6..dabdfba0ab 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -63,7 +63,6 @@ import { RegistryBitstreamformatsResponseParsingService } from './data/registry- import { NotificationsService } from '../shared/notifications/notifications.service'; import { UploaderService } from '../shared/uploader/uploader.service'; import { DSpaceObjectDataService } from './data/dspace-object-data.service'; -import { SingleDsoResponseParsingService } from './data/single-dso-response-parsing.service'; const IMPORTS = [ CommonModule, @@ -127,7 +126,6 @@ const PROVIDERS = [ UploaderService, UUIDService, DSpaceObjectDataService, - SingleDsoResponseParsingService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 0d746816c7..53327952c1 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -18,20 +18,18 @@ import { } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; -import { distinctUntilChanged, flatMap, map, switchMap, take, withLatestFrom } from 'rxjs/operators'; +import { distinctUntilChanged, first, map, take, tap } from 'rxjs/operators'; import { configureRequest, filterSuccessfulResponses, - getResponseFromSelflink, getSucceededRemoteData + getResponseFromSelflink } from '../shared/operators'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; -import { HttpClient, HttpEvent, HttpHeaders, HttpRequest } from '@angular/common/http'; -import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models'; +import { HttpClient } from '@angular/common/http'; +import { DSOSuccessResponse, ErrorResponse } from '../cache/response-cache.models'; import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; -import { combineLatest } from 'rxjs/observable/combineLatest'; export abstract class DataService { protected abstract responseCache: ResponseCacheService; @@ -118,7 +116,7 @@ export abstract class DataService return this.rdbService.buildSingle(href); } - public create(dso: TDomain, parentUUID: string): Observable> { + create(dso: TDomain, parentUUID: string): Observable> { const requestId = this.requestService.generateRequestId(); const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), @@ -132,7 +130,7 @@ export abstract class DataService configureRequest(this.requestService) ); - const payload$ = request$.pipe( + const selfLink$ = request$.pipe( map((request: RestRequest) => request.href), getResponseFromSelflink(this.responseCache), map((response: ResponseCacheEntry) => { @@ -144,16 +142,12 @@ export abstract class DataService }), filterSuccessfulResponses(), map((entry: ResponseCacheEntry) => entry.response), - map((response: GenericSuccessResponse) => response.payload), + map((response: DSOSuccessResponse) => { + return response.resourceSelfLinks[0]; + }), distinctUntilChanged() ); - - const requestEntry$ = this.requestService.getByUUID(requestId); - const responseCache$ = endpoint$.pipe(getResponseFromSelflink(this.responseCache)); - - return this.rdbService.toRemoteDataObservable(requestEntry$, responseCache$, payload$).pipe( - getSucceededRemoteData() - ); + return this.rdbService.buildSingle(selfLink$) as Observable>; } } diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index f5b9127a6b..7ca152a204 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -1,7 +1,5 @@ import { SortOptions } from '../cache/models/sort-options.model'; import { GenericConstructor } from '../shared/generic-constructor'; -import { GlobalConfig } from '../../../config/global-config.interface'; -import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; import { BrowseEntriesResponseParsingService } from './browse-entries-response-parsing.service'; import { DSOResponseParsingService } from './dso-response-parsing.service'; import { ResponseParsingService } from './parsing.service'; @@ -10,9 +8,7 @@ import { BrowseResponseParsingService } from './browse-response-parsing.service' import { ConfigResponseParsingService } from './config-response-parsing.service'; import { AuthResponseParsingService } from '../auth/auth-response-parsing.service'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; -import { HttpHeaders } from '@angular/common/http'; import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service'; -import { SingleDsoResponseParsingService } from './single-dso-response-parsing.service'; /* tslint:disable:max-classes-per-file */ @@ -231,7 +227,7 @@ export class CreateRequest extends PostRequest { } getResponseParser(): GenericConstructor { - return SingleDsoResponseParsingService; + return DSOResponseParsingService; } } diff --git a/src/app/core/data/single-dso-response-parsing.service.ts b/src/app/core/data/single-dso-response-parsing.service.ts deleted file mode 100644 index 03b9f5bea2..0000000000 --- a/src/app/core/data/single-dso-response-parsing.service.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Inject, Injectable } from '@angular/core'; -import { ResponseParsingService } from './parsing.service'; -import { BaseResponseParsingService } from './base-response-parsing.service'; -import { GlobalConfig } from '../../../config/global-config.interface'; -import { GLOBAL_CONFIG } from '../../../config'; -import { ObjectCacheService } from '../cache/object-cache.service'; -import { RestRequest } from './request.models'; -import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model'; -import { - DSOSuccessResponse, - GenericSuccessResponse, - RestResponse -} from '../cache/response-cache.models'; -import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; -import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; -import { DSpaceObject } from '../shared/dspace-object.model'; -import { NormalizedObject } from '../cache/models/normalized-object.model'; -import { ResourceType } from '../shared/resource-type'; -import { hasNoValue, hasValue } from '../../shared/empty.util'; - -@Injectable() -export class SingleDsoResponseParsingService extends BaseResponseParsingService implements ResponseParsingService { - protected objectFactory = NormalizedObjectFactory; - protected toCache = true; - - constructor( - @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig, - protected objectCache: ObjectCacheService, - ) { super(); - } - - parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse { - const processRequestDTO = this.process(data.payload, request.href); - if (hasNoValue(processRequestDTO)) { - return new GenericSuccessResponse(undefined, data.statusCode) - } - return new GenericSuccessResponse(processRequestDTO, data.statusCode); - } - -} diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts index 3bedeb9915..a0be10f454 100644 --- a/src/app/core/shared/hal-endpoint.service.ts +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -1,5 +1,5 @@ import { Observable } from 'rxjs/Observable'; -import { distinctUntilChanged, map, flatMap, startWith, tap } from 'rxjs/operators'; +import { distinctUntilChanged, map, flatMap, startWith, tap, switchMap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; import { GlobalConfig } from '../../../config/global-config.interface'; @@ -48,7 +48,7 @@ export class HALEndpointService { let currentPath; const pipeArguments = path .map((subPath: string, index: number) => [ - flatMap((href: string) => this.getEndpointMapAt(href)), + switchMap((href: string) => this.getEndpointMapAt(href)), map((endpointMap: EndpointMap) => { if (hasValue(endpointMap) && hasValue(endpointMap[subPath])) { currentPath = endpointMap[subPath]; From 30284ea78f2df462e5a7b58b42e3c3a596fc1ea8 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 17 Sep 2018 10:33:20 +0200 Subject: [PATCH 33/57] 54472: DataService create method tests + buildFormData test pt2 --- .../create-collection-page.component.ts | 12 ++-- src/app/core/data/data.service.spec.ts | 72 +++++++++++++------ .../dspace-rest-v2.service.spec.ts | 12 ++++ 3 files changed, 69 insertions(+), 27 deletions(-) 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 4d94a53035..6429e623bf 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 @@ -33,8 +33,7 @@ export class CreateCollectionPageComponent { private collectionDataService: CollectionDataService, private communityDataService: CommunityDataService, private routeService: RouteService, - private router: Router, - private objectCache: ObjectCacheService + private router: Router ) { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); this.parentUUID$.subscribe((uuid: string) => { @@ -56,11 +55,10 @@ export class CreateCollectionPageComponent { // TODO: metadata for news and provenance ] }); - this.collectionDataService.create(collection, uuid).pipe( - flatMap((rd: RemoteData) => this.objectCache.getByUUID(rd.payload.id)), - isNotEmptyOperator() - ).subscribe((col: NormalizedCollection) => { - this.router.navigate(['collections', col.id]); + this.collectionDataService.create(collection, uuid).subscribe((rd: RemoteData) => { + if (rd.hasSucceeded) { + this.router.navigate(['collections', rd.payload.id]); + } }); }); } diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index 7ebe536219..d16af124e4 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -18,6 +18,12 @@ import { DSpaceObject } from '../shared/dspace-object.model'; import { RemoteData } from './remote-data'; import { RequestEntry } from './request.reducer'; import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; +import { EmptyError } from 'rxjs/util/EmptyError'; +import { ResponseCacheEntry } from '../cache/response-cache.reducer'; +import { ErrorResponse, RestResponse } from '../cache/response-cache.models'; +import { hasValue } from '../../shared/empty.util'; +import { map } from 'rxjs/operators'; +import { RemoteDataError } from './remote-data-error'; const LINK_NAME = 'test'; @@ -49,7 +55,7 @@ class TestService extends DataService { describe('DataService', () => { let service: TestService; let options: FindAllOptions; - const responseCache = getMockResponseCacheService(); + let responseCache = getMockResponseCacheService(); let rdbService = {} as RemoteDataBuildService; const authService = {} as AuthService; const notificationsService = {} as NotificationsService; @@ -57,12 +63,38 @@ describe('DataService', () => { const store = {} as Store; const endpoint = 'https://rest.api/core'; const halService = Object.assign({ - getEndpoint: () => Observable.of(endpoint) + getEndpoint: (linkpath) => Observable.of(endpoint) }); const requestService = Object.assign(getMockRequestService(), { - getByUUID: () => Observable.of(new RequestEntry()) + getByUUID: () => Observable.of(new RequestEntry()), + configure: (request) => request }); + const dso = new DSpaceObject(); + const successfulRd$ = Observable.of(new RemoteData(false, false, true, undefined, dso)); + const successfulResponseCacheEntry = { + response: { + isSuccessful: true, + payload: dso, + toCache: true, + statusCode: '200' + } as RestResponse + } as ResponseCacheEntry; + + function initSuccessfulRemoteDataBuildService(): RemoteDataBuildService { + return { + toRemoteDataObservable: (requestEntry$: Observable, responseCache$: Observable, payload$: Observable) => { + requestEntry$.subscribe(); + responseCache$.subscribe(); + payload$.subscribe(); + return successfulRd$; + } + } as RemoteDataBuildService; + } + function initSuccessfulResponseCacheService(): ResponseCacheService { + return getMockResponseCacheService(Observable.of(new ResponseCacheEntry()), Observable.of(successfulResponseCacheEntry)); + } + function initTestService(): TestService { return new TestService( responseCache, @@ -136,13 +168,13 @@ describe('DataService', () => { }); it('should include all provided options in href', () => { - const sortOptions = new SortOptions('field1', SortDirection.DESC) + const sortOptions = new SortOptions('field1', SortDirection.DESC); options = { currentPage: 6, elementsPerPage: 10, sort: sortOptions, startsWith: 'ab' - } + }; const expected = `${endpoint}?page=${options.currentPage - 1}&size=${options.elementsPerPage}` + `&sort=${sortOptions.field},${sortOptions.direction}&startsWith=${options.startsWith}`; @@ -152,14 +184,12 @@ describe('DataService', () => { }) }); - fdescribe('create', () => { - const dso = new DSpaceObject(); - const successfulRd$ = Observable.of(new RemoteData(false, false, true, undefined, dso)); - const failingRd$ = Observable.of(new RemoteData(false, false, false, undefined, dso)); + describe('create', () => { describe('when the request was successful', () => { beforeEach(() => { - rdbService = getMockRemoteDataBuildService(successfulRd$); + responseCache = initSuccessfulResponseCacheService(); + rdbService = initSuccessfulRemoteDataBuildService(); service = initTestService(); }); @@ -168,23 +198,25 @@ describe('DataService', () => { expect(rd.payload).toBe(dso); }); }); - }); - describe('when the request was unsuccessful', () => { - beforeEach(() => { - rdbService = getMockRemoteDataBuildService(failingRd$); - service = initTestService(); + it('should get the response from cache with the correct url when parent is empty', () => { + const expectedUrl = endpoint; + + service.create(dso, undefined).subscribe((value) => { + expect(responseCache.get).toHaveBeenCalledWith(expectedUrl); + }); }); - it('should not return anything', () => { - service.create(dso, undefined); + it('should get the response from cache with the correct url when parent is not empty', () => { + const parent = 'fake-parent-uuid'; + const expectedUrl = `${endpoint}?parent=${parent}`; - // TODO: Expect create to emit nothing + service.create(dso, parent).subscribe((value) => { + expect(responseCache.get).toHaveBeenCalledWith(expectedUrl); + }); }); }); - // TODO: Create tests with passing parent - }); }); diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.spec.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.spec.ts index 4893908627..256fffaa24 100644 --- a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.spec.ts +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.spec.ts @@ -2,6 +2,7 @@ import { TestBed, inject } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { DSpaceRESTv2Service } from './dspace-rest-v2.service'; +import { DSpaceObject } from '../shared/dspace-object.model'; describe('DSpaceRESTv2Service', () => { let dSpaceRESTv2Service: DSpaceRESTv2Service; @@ -65,4 +66,15 @@ describe('DSpaceRESTv2Service', () => { expect(req.request.method).toBe('GET'); req.error(mockError); }); + + fdescribe('buildFormData', () => { + it('should return the correct data', () => { + const name = 'testname'; + const dso: DSpaceObject = { + name: name + } as DSpaceObject; + const formdata = dSpaceRESTv2Service.buildFormData(dso); + expect(formdata.get('name')).toBe(name); + }); + }); }); From e011ec207c075c42558fcd2d4bb1c7b8a4f39277 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 17 Sep 2018 10:46:47 +0200 Subject: [PATCH 34/57] 54472: Adjustment of tests with new code + removal of fdescribe --- src/app/core/data/data.service.spec.ts | 19 ++++++++++--------- .../dspace-rest-v2.service.spec.ts | 2 +- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index d16af124e4..8a2f5897c2 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -20,7 +20,7 @@ import { RequestEntry } from './request.reducer'; import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service'; import { EmptyError } from 'rxjs/util/EmptyError'; import { ResponseCacheEntry } from '../cache/response-cache.reducer'; -import { ErrorResponse, RestResponse } from '../cache/response-cache.models'; +import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models'; import { hasValue } from '../../shared/empty.util'; import { map } from 'rxjs/operators'; import { RemoteDataError } from './remote-data-error'; @@ -72,21 +72,22 @@ describe('DataService', () => { const dso = new DSpaceObject(); const successfulRd$ = Observable.of(new RemoteData(false, false, true, undefined, dso)); - const successfulResponseCacheEntry = { + const successfulResponseCacheEntry = Object.assign({ response: { isSuccessful: true, payload: dso, toCache: true, - statusCode: '200' - } as RestResponse - } as ResponseCacheEntry; + statusCode: '200', + resourceSelfLinks: [ + endpoint + ] + } as DSOSuccessResponse + }) as ResponseCacheEntry; function initSuccessfulRemoteDataBuildService(): RemoteDataBuildService { return { - toRemoteDataObservable: (requestEntry$: Observable, responseCache$: Observable, payload$: Observable) => { - requestEntry$.subscribe(); - responseCache$.subscribe(); - payload$.subscribe(); + buildSingle: (selfLinks$: Observable) => { + selfLinks$.subscribe(); return successfulRd$; } } as RemoteDataBuildService; diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.spec.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.spec.ts index 256fffaa24..26bd1ba5de 100644 --- a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.spec.ts +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.spec.ts @@ -67,7 +67,7 @@ describe('DSpaceRESTv2Service', () => { req.error(mockError); }); - fdescribe('buildFormData', () => { + describe('buildFormData', () => { it('should return the correct data', () => { const name = 'testname'; const dso: DSpaceObject = { From 0cf65b3f8c1c5247f6a2d76bd2ceac0d67010231 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 29 Oct 2018 11:17:07 +0100 Subject: [PATCH 35/57] 54472: Remove console.log --- src/app/core/auth/auth.service.ts | 1 - src/app/core/data/request.effects.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 4c520e8f30..229c44bcfa 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -116,7 +116,6 @@ export class AuthService { options.headers = headers; return this.authRequestService.postToEndpoint('login', body, options).pipe( map((status: AuthStatus) => { - console.log('yey response'); if (status.authenticated) { return status; } else { diff --git a/src/app/core/data/request.effects.ts b/src/app/core/data/request.effects.ts index 537a0b69b6..5e7bec698b 100644 --- a/src/app/core/data/request.effects.ts +++ b/src/app/core/data/request.effects.ts @@ -42,7 +42,6 @@ export class RequestEffects { }), filter((entry: RequestEntry) => hasValue(entry)), map((entry: RequestEntry) => entry.request), - tap((entry: RequestEntry) => console.log(entry)), flatMap((request: RestRequest) => { let body; if (isNotEmpty(request.body)) { From a49f9bdb031dc866455712502033fff43dbfb135 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 29 Oct 2018 13:09:45 +0100 Subject: [PATCH 36/57] 54472: Intermediate commit --- src/app/core/data/data.service.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index d591b1a8d5..ed32021f34 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,4 +1,4 @@ -import { distinctUntilChanged, filter, first, map, take } from 'rxjs/operators'; +import { distinctUntilChanged, filter, first, map, switchMap, take } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; @@ -13,7 +13,7 @@ import { FindAllOptions, FindAllRequest, FindByIDRequest, - GetRequest + GetRequest, RestRequest } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; @@ -143,6 +143,8 @@ export abstract class DataService ); const selfLink$ = request$.pipe( + map((restRequest: RestRequest) => restRequest.href), + switchMap((href: string) => this.requestService.getByHref(href)), getResponseFromEntry(), map((response: RestResponse) => { if (!response.isSuccessful && response instanceof ErrorResponse) { From 1fc9359133f66d7a05447d0076cfe9218c3ffa18 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Mon, 29 Oct 2018 16:04:18 +0100 Subject: [PATCH 37/57] 54472: Refactored create to work with new response cache --- src/app/core/data/data.service.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index ed32021f34..6a82577aa5 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -25,7 +25,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { HttpClient } from '@angular/common/http'; import { configureRequest, - filterSuccessfulResponses, + filterSuccessfulResponses, getResourceLinksFromResponse, getResponseFromEntry } from '../shared/operators'; import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/response.models'; @@ -138,13 +138,15 @@ export abstract class DataService const request$ = endpoint$.pipe( take(1), - map((endpoint: string) => new CreateRequest(requestId, endpoint, dso)), - configureRequest(this.requestService) + map((endpoint: string) => new CreateRequest(requestId, endpoint, dso)) ); - const selfLink$ = request$.pipe( - map((restRequest: RestRequest) => restRequest.href), - switchMap((href: string) => this.requestService.getByHref(href)), + // Execute the post request + request$.pipe( + configureRequest(this.requestService) + ).subscribe(); + + const selfLink$ = this.requestService.getByUUID(requestId).pipe( getResponseFromEntry(), map((response: RestResponse) => { if (!response.isSuccessful && response instanceof ErrorResponse) { @@ -153,13 +155,17 @@ export abstract class DataService return response; } }), - filterSuccessfulResponses(), - map((response: DSOSuccessResponse) => { - return response.resourceSelfLinks[0]; + map((response: any) => { + if (isNotEmpty(response.resourceSelfLinks)) { + return response.resourceSelfLinks[0]; + } }), distinctUntilChanged() ) as Observable; - return this.rdbService.buildSingle(selfLink$) as Observable>; + + return selfLink$.pipe( + switchMap((selfLink: string) => this.findByHref(selfLink)), + ) } } From dd439109a43c2b10dd5f683ea7385d6e8905a2a8 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 6 Nov 2018 09:57:27 +0100 Subject: [PATCH 38/57] 54472: Send normalized collections/communities instead of FormData --- .../create-collection-page.component.ts | 7 +++++-- .../create-community-page.component.ts | 7 +++++-- src/app/core/data/data.service.ts | 8 ++++++-- 3 files changed, 16 insertions(+), 6 deletions(-) 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 f3dbff9c1f..a8ee329a0f 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 @@ -9,6 +9,8 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; +import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; +import { ResourceType } from '../../core/shared/resource-type'; @Component({ selector: 'ds-create-collection', @@ -36,7 +38,7 @@ export class CreateCollectionPageComponent { onSubmit(data: any) { this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { - const collection = Object.assign(new Collection(), { + const collection = Object.assign(new NormalizedCollection(), { name: data.name, metadata: [ { key: 'dc.description', value: data.introductory }, @@ -44,7 +46,8 @@ export class CreateCollectionPageComponent { { key: 'dc.rights', value: data.copyright }, { key: 'dc.rights.license', value: data.license } // TODO: metadata for news and provenance - ] + ], + type: ResourceType.Collection }); this.collectionDataService.create(collection, uuid).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { 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 8c75af0d64..0d97f24028 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 @@ -8,6 +8,8 @@ import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { take } from 'rxjs/operators'; +import { ResourceType } from '../../core/shared/resource-type'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; @Component({ selector: 'ds-create-community', @@ -34,14 +36,15 @@ export class CreateCommunityPageComponent { onSubmit(data: any) { this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { - const community = Object.assign(new Community(), { + const community = Object.assign(new NormalizedCommunity(), { name: data.name, metadata: [ { key: 'dc.description', value: data.introductory }, { key: 'dc.description.abstract', value: data.description }, { key: 'dc.rights', value: data.copyright } // TODO: metadata for news - ] + ], + type: ResourceType.Community }); this.communityDataService.create(community, uuid).pipe(take(1)).subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 6a82577aa5..db3882ac08 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -30,6 +30,8 @@ import { } from '../shared/operators'; import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/response.models'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; +import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; +import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; export abstract class DataService { protected abstract requestService: RequestService; @@ -128,7 +130,7 @@ export abstract class DataService } } - create(dso: TDomain, parentUUID: string): Observable> { + create(dso: TNormalized, parentUUID: string): Observable> { const requestId = this.requestService.generateRequestId(); const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), @@ -136,9 +138,11 @@ export abstract class DataService map((endpoint: string) => parentUUID ? `${endpoint}?parent=${parentUUID}` : endpoint) ); + const serializedDso = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(dso.type)).serialize(dso); + const request$ = endpoint$.pipe( take(1), - map((endpoint: string) => new CreateRequest(requestId, endpoint, dso)) + map((endpoint: string) => new CreateRequest(requestId, endpoint, JSON.stringify(serializedDso))) ); // Execute the post request From 7a12332d700c8c09cb177aad8902bcb0bd0a5e5a Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 20 Dec 2018 15:54:47 +0100 Subject: [PATCH 39/57] Edit and create communities --- resources/i18n/en.json | 1 + .../create-collection-page.component.ts | 2 +- .../community-form.component.html | 31 +----- .../community-form.component.ts | 94 +++++++++++++++---- .../community-page-routing.module.ts | 12 ++- .../community-page.component.html | 2 + .../community-page.component.ts | 7 -- .../+community-page/community-page.module.ts | 2 + .../create-community-page.component.html | 2 +- .../create-community-page.component.spec.ts | 2 - .../create-community-page.component.ts | 47 ++++------ .../edit-community-page.component.html | 11 +++ .../edit-community-page.component.scss | 1 + .../edit-community-page.component.spec.ts | 90 ++++++++++++++++++ .../edit-community-page.component.ts | 46 +++++++++ .../core/cache/builders/data-build.service.ts | 36 +++++++ .../builders/remote-data-build.service.ts | 3 - .../models/normalized-community.model.ts | 12 +-- .../models/normalized-dspace-object.model.ts | 14 +-- src/app/core/core.module.ts | 4 + .../data/base-response-parsing.service.ts | 20 ++-- src/app/core/data/collection-data.service.ts | 9 +- src/app/core/data/comcol-data.service.spec.ts | 12 ++- src/app/core/data/comcol-data.service.ts | 3 +- src/app/core/data/community-data.service.ts | 6 +- .../config-response-parsing.service.spec.ts | 2 +- src/app/core/data/data.service.ts | 42 +++++++-- src/app/core/data/dso-update-comparator.ts | 12 +++ .../core/data/dspace-object-data.service.ts | 12 ++- src/app/core/data/item-data.service.ts | 6 +- src/app/core/data/paginated-list.ts | 8 ++ src/app/core/data/request.service.ts | 2 +- src/app/core/data/update-comparator.ts | 6 ++ src/app/core/shared/dspace-object.model.ts | 7 +- src/app/core/shared/page-info.model.ts | 3 + .../ds-dynamic-form-control.component.ts | 1 - src/app/shared/form/form.component.html | 2 +- src/app/shared/form/form.component.ts | 4 +- src/app/shared/form/form.service.ts | 2 +- 39 files changed, 427 insertions(+), 151 deletions(-) create mode 100644 src/app/+community-page/edit-community-page/edit-community-page.component.html create mode 100644 src/app/+community-page/edit-community-page/edit-community-page.component.scss create mode 100644 src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts create mode 100644 src/app/+community-page/edit-community-page/edit-community-page.component.ts create mode 100644 src/app/core/cache/builders/data-build.service.ts create mode 100644 src/app/core/data/dso-update-comparator.ts create mode 100644 src/app/core/data/update-comparator.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 814adba9a7..a4f9014980 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -42,6 +42,7 @@ "head": "Collections of this Community" }, "edit": { + "head": "Edit collction", "name": "Name", "description": "Short Description", "introductory": "Introductory text (HTML)", 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 a8ee329a0f..d934e1d7a0 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 @@ -38,7 +38,7 @@ export class CreateCollectionPageComponent { onSubmit(data: any) { this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { - const collection = Object.assign(new NormalizedCollection(), { + const collection = Object.assign(new Collection(), { name: data.name, metadata: [ { key: 'dc.description', value: data.introductory }, diff --git a/src/app/+community-page/community-form/community-form.component.html b/src/app/+community-page/community-form/community-form.component.html index 578e2a0d6a..637506f86d 100644 --- a/src/app/+community-page/community-form/community-form.component.html +++ b/src/app/+community-page/community-form/community-form.component.html @@ -1,27 +1,4 @@ -
-
- - -
{{ 'community.edit.required.name' | translate }}
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
+ + diff --git a/src/app/+community-page/community-form/community-form.component.ts b/src/app/+community-page/community-form/community-form.component.ts index d7797028b7..fd0b490146 100644 --- a/src/app/+community-page/community-form/community-form.component.ts +++ b/src/app/+community-page/community-form/community-form.component.ts @@ -1,39 +1,93 @@ -import { Component, EventEmitter, Output } from '@angular/core'; -import { isNotEmpty } from '../../shared/empty.util'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Location } from '@angular/common'; +import { + DynamicFormService, + DynamicInputModel, + DynamicTextAreaModel +} from '@ng-dynamic-forms/core'; +import { FormGroup } from '@angular/forms'; +import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; +import { Community } from '../../core/shared/community.model'; +import { ResourceType } from '../../core/shared/resource-type'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-community-form', styleUrls: ['./community-form.component.scss'], templateUrl: './community-form.component.html' }) -export class CommunityFormComponent { +export class CommunityFormComponent implements OnInit { - name: string; - description: string; - introductory: string; - copyright: string; - news: string; + @Input() community: Community = new Community(); + formModel: DynamicFormControlModel[] = [ + new DynamicInputModel({ + id: 'title', + name: 'dc.title', + label: 'Name', + required: true, + validators: { + required: null + }, + errorMessages: { + required: 'Please enter a name for this title' + } + }), + new DynamicTextAreaModel({ + id: 'description', + name: 'dc.description', + label: 'Introductory text (HTML)', + }), + new DynamicTextAreaModel({ + id: 'abstract', + name: 'dc.description.abstract', + label: 'Short Description', + }), + new DynamicTextAreaModel({ + id: 'rights', + name: 'dc.rights', + label: 'Copyright text (HTML)', + }), + new DynamicTextAreaModel({ + id: 'tableofcontents', + name: 'dc.description.tableofcontents', + label: 'News (HTML)', + }), + ]; - nameRequiredError = false; + formGroup: FormGroup; - @Output() submitted: EventEmitter = new EventEmitter(); - - public constructor(private location: Location) { + @Output() submitForm: EventEmitter = new EventEmitter(); + public constructor(private location: Location, private formService: DynamicFormService) { } - onSubmit(data: any) { - if (isNotEmpty(data.name)) { - this.submitted.emit(data); - this.nameRequiredError = false; - } else { - this.nameRequiredError = true; - } + ngOnInit(): void { + this.formModel.forEach( + (fieldModel: DynamicInputModel) => { + fieldModel.value = this.community.findMetadata(fieldModel.name); + } + ); + this.formGroup = this.formService.createFormGroup(this.formModel); + } + + onSubmit(event: Event) { + event.stopPropagation(); + const metadata = this.formModel.map( + (fieldModel: DynamicInputModel) => { + return { key: fieldModel.name, value: fieldModel.value } + } + ); + const filteredOldMetadata = this.community.metadata.filter((filter) => !metadata.map((md) => md.key).includes(filter.key)); + const filteredNewMetadata = metadata.filter((md) => isNotEmpty(md.value)); + const newMetadata = [...filteredOldMetadata, ...filteredNewMetadata]; + const updatedCommunity = Object.assign({}, this.community, { + metadata: newMetadata, + type: ResourceType.Community + }); + this.submitForm.emit(updatedCommunity); } cancel() { this.location.back(); } - } diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index 17889d6e48..8fcc2cde6f 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -5,13 +5,23 @@ import { CommunityPageComponent } from './community-page.component'; import { CommunityPageResolver } from './community-page.resolver'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; +import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component'; @NgModule({ imports: [ RouterModule.forChild([ { path: 'create', component: CreateCommunityPageComponent, - canActivate: [AuthenticatedGuard] }, + canActivate: [AuthenticatedGuard] + }, + { path: ':id/edit', + pathMatch: 'full', + component: EditCommunityPageComponent, + canActivate: [AuthenticatedGuard], + resolve: { + community: CommunityPageResolver + } + }, { path: ':id', component: CommunityPageComponent, diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html index 1bf322a688..52bc9fe928 100644 --- a/src/app/+community-page/community-page.component.html +++ b/src/app/+community-page/community-page.component.html @@ -28,6 +28,8 @@ [community]="communityPayload">
+ Edit + diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index ce260aefc0..bfaac33c1c 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -24,8 +24,6 @@ import { hasValue } from '../shared/empty.util'; export class CommunityPageComponent implements OnInit, OnDestroy { communityRD$: Observable>; logoRD$: Observable>; - - private subs: Subscription[] = []; constructor( @@ -42,14 +40,9 @@ export class CommunityPageComponent implements OnInit, OnDestroy { map((rd: RemoteData) => rd.payload), filter((community: Community) => hasValue(community)), mergeMap((community: Community) => community.logo)); - - } ngOnDestroy(): void { this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } - - - } diff --git a/src/app/+community-page/community-page.module.ts b/src/app/+community-page/community-page.module.ts index 292e6aaf9c..23050f40db 100644 --- a/src/app/+community-page/community-page.module.ts +++ b/src/app/+community-page/community-page.module.ts @@ -8,6 +8,7 @@ import { CommunityPageSubCollectionListComponent } from './sub-collection-list/c import { CommunityPageRoutingModule } from './community-page-routing.module'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; import { CommunityFormComponent } from './community-form/community-form.component'; +import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component'; @NgModule({ imports: [ @@ -19,6 +20,7 @@ import { CommunityFormComponent } from './community-form/community-form.componen CommunityPageComponent, CommunityPageSubCollectionListComponent, CreateCommunityPageComponent, + EditCommunityPageComponent, CommunityFormComponent ] }) diff --git a/src/app/+community-page/create-community-page/create-community-page.component.html b/src/app/+community-page/create-community-page/create-community-page.component.html index 35f1deaa73..bb0f5b83af 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.html +++ b/src/app/+community-page/create-community-page/create-community-page.component.html @@ -7,5 +7,5 @@ - + 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 6c5eb5f591..e76b10bde6 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 @@ -8,12 +8,10 @@ import { Observable } from 'rxjs/Observable'; import { RemoteData } from '../../core/data/remote-data'; import { Community } from '../../core/shared/community.model'; import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; -import { BrowserModule } from '@angular/platform-browser'; import { SharedModule } from '../../shared/shared.module'; import { CommonModule } from '@angular/common'; import { CommunityFormComponent } from '../community-form/community-form.component'; import { RouterTestingModule } from '@angular/router/testing'; -import { RequestError } from '../../core/data/request.models'; describe('CreateCommunityPageComponent', () => { let comp: CreateCommunityPageComponent; 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 0d97f24028..5373abfe22 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,4 +1,4 @@ -import { Component } from '@angular/core'; +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'; @@ -7,55 +7,46 @@ import { Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { take } from 'rxjs/operators'; -import { ResourceType } from '../../core/shared/resource-type'; -import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { map, take } from 'rxjs/operators'; +import { getSucceededRemoteData } from '../../core/shared/operators'; @Component({ selector: 'ds-create-community', styleUrls: ['./create-community-page.component.scss'], templateUrl: './create-community-page.component.html' }) -export class CreateCommunityPageComponent { +export class CreateCommunityPageComponent implements OnInit { public parentUUID$: Observable; - public communityRDObs: Observable>; + public parentRD$: Observable>; public constructor( private communityDataService: CommunityDataService, private routeService: RouteService, private router: Router ) { + + } + + ngOnInit(): void { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); - this.parentUUID$.subscribe((uuid: string) => { - if (isNotEmpty(uuid)) { - this.communityRDObs = this.communityDataService.findById(uuid); + this.parentUUID$.subscribe((parentID: string) => { + if (isNotEmpty(parentID)) { + this.parentRD$ = this.communityDataService.findById(parentID); } }); } - onSubmit(data: any) { + onSubmit(community: Community) { this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { - const community = Object.assign(new NormalizedCommunity(), { - name: data.name, - metadata: [ - { key: 'dc.description', value: data.introductory }, - { key: 'dc.description.abstract', value: data.description }, - { key: 'dc.rights', value: data.copyright } - // TODO: metadata for news - ], - type: ResourceType.Community - }); - this.communityDataService.create(community, uuid).pipe(take(1)).subscribe((rd: RemoteData) => { - if (rd.hasSucceeded) { - if (uuid) { - this.router.navigate(['communities', uuid]); - } else { - this.router.navigate([]); - } - } + this.communityDataService.create(community, uuid) + .pipe(getSucceededRemoteData()) + .subscribe((communityRD: RemoteData) => { + const newUUID = communityRD.payload.uuid; + this.router.navigate(['/communities/' + newUUID]); }); }); } + } diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.html b/src/app/+community-page/edit-community-page/edit-community-page.component.html new file mode 100644 index 0000000000..eb9f797b3d --- /dev/null +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.html @@ -0,0 +1,11 @@ +
+
+
+ + +

{{ 'community.edit.sub-head' | translate:{ parent: parent.name } }}

+
+
+
+ +
diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.scss b/src/app/+community-page/edit-community-page/edit-community-page.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.scss @@ -0,0 +1 @@ + diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts new file mode 100644 index 0000000000..6c5eb5f591 --- /dev/null +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts @@ -0,0 +1,90 @@ +import { CreateCommunityPageComponent } from './create-community-page.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { RouteService } from '../../shared/services/route.service'; +import { Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../../core/data/remote-data'; +import { Community } from '../../core/shared/community.model'; +import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; +import { BrowserModule } from '@angular/platform-browser'; +import { SharedModule } from '../../shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { CommunityFormComponent } from '../community-form/community-form.component'; +import { RouterTestingModule } from '@angular/router/testing'; +import { RequestError } from '../../core/data/request.models'; + +describe('CreateCommunityPageComponent', () => { + let comp: CreateCommunityPageComponent; + let fixture: ComponentFixture; + let communityDataService: CommunityDataService; + let routeService: RouteService; + let router: Router; + + const community = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66b9300d347', + name: 'test community' + }); + + const newCommunity = Object.assign(new Community(), { + uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', + name: 'new community' + }); + + const communityDataServiceStub = { + findById: (uuid) => Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { + uuid: uuid, + name: community.name + }))), + create: (com, uuid?) => Observable.of(new RemoteData(false, false, true, undefined, newCommunity)) + }; + const routeServiceStub = { + getQueryParameterValue: (param) => Observable.of(community.uuid) + }; + const routerStub = { + navigate: (commands) => commands + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [CreateCommunityPageComponent, CommunityFormComponent], + providers: [ + { provide: CommunityDataService, useValue: communityDataServiceStub }, + { provide: RouteService, useValue: routeServiceStub }, + { provide: Router, useValue: routerStub } + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateCommunityPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + communityDataService = (comp as any).communityDataService; + routeService = (comp as any).routeService; + router = (comp as any).router; + }); + + describe('onSubmit', () => { + const data = { + name: 'test' + }; + + it('should navigate when successful', () => { + spyOn(router, 'navigate'); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should not navigate on failure', () => { + spyOn(router, 'navigate'); + spyOn(communityDataService, 'create').and.returnValue(Observable.of(new RemoteData(true, true, false, undefined, newCommunity))); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigate).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.ts new file mode 100644 index 0000000000..58805af80a --- /dev/null +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.ts @@ -0,0 +1,46 @@ +import { Component } 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 '../../shared/services/route.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RemoteData } from '../../core/data/remote-data'; +import { isNotEmpty } from '../../shared/empty.util'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { first, map, take, tap } from 'rxjs/operators'; +import { ResourceType } from '../../core/shared/resource-type'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { getSucceededRemoteData } from '../../core/shared/operators'; + +@Component({ + selector: 'ds-edit-community', + styleUrls: ['./edit-community-page.component.scss'], + templateUrl: './edit-community-page.component.html' +}) +export class EditCommunityPageComponent { + + public parentUUID$: Observable; + public parentRD$: Observable>; + public communityRD$: Observable>; + + public constructor( + private communityDataService: CommunityDataService, + private routeService: RouteService, + private router: Router, + private route: ActivatedRoute + ) { + } + + ngOnInit(): void { + this.communityRD$ = this.route.data.pipe(first(), map((data) => data.community)); + } + + onSubmit(community: Community) { + this.communityDataService.update(community) + .pipe(getSucceededRemoteData()) + .subscribe((communityRD: RemoteData) => { + const newUUID = communityRD.payload.uuid; + this.router.navigate(['/communities/' + newUUID]); + }); + } +} diff --git a/src/app/core/cache/builders/data-build.service.ts b/src/app/core/cache/builders/data-build.service.ts new file mode 100644 index 0000000000..8ba3ebee0c --- /dev/null +++ b/src/app/core/cache/builders/data-build.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { NormalizedObject } from '../models/normalized-object.model'; +import { CacheableObject } from '../object-cache.reducer'; +import { getRelationships } from './build-decorators'; +import { NormalizedObjectFactory } from '../models/normalized-object-factory'; +import { map, take } from 'rxjs/operators'; +import { hasValue, isNotEmpty } from '../../../shared/empty.util'; +import { PaginatedList } from '../../data/paginated-list'; + +export function isRestDataObject(halObj: any) { + return isNotEmpty(halObj._links) && hasValue(halObj._links.self); +} + +export function isRestPaginatedList(halObj: any) { + return hasValue(halObj.page) && hasValue(halObj._embedded); +} + +export function isPaginatedList(halObj: any) { + return hasValue(halObj.page) && hasValue(halObj.pageInfo); +} + +@Injectable() +export class DataBuildService { + normalize(domainModel: TDomain): TNormalized { + const normalizedConstructor = NormalizedObjectFactory.getConstructor(domainModel.type); + const relationships = getRelationships(normalizedConstructor) || []; + + const normalizedModel = Object.assign({}, domainModel) as any; + relationships.forEach((key: string) => { + if (hasValue(domainModel[key])) { + domainModel[key] = undefined; + } + }); + return normalizedModel; + } +} diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 1dc255f26e..7b51526432 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -51,10 +51,7 @@ export class RemoteDataBuildService { const requestEntry$ = observableRace( href$.pipe(getRequestFromRequestHref(this.requestService)), requestUUID$.pipe(getRequestFromRequestUUID(this.requestService)), - ).pipe( - take(1) ); - // always use self link if that is cached, only if it isn't, get it via the response. const payload$ = observableCombineLatest( diff --git a/src/app/core/cache/models/normalized-community.model.ts b/src/app/core/cache/models/normalized-community.model.ts index 4ab2408a53..e915d2f50a 100644 --- a/src/app/core/cache/models/normalized-community.model.ts +++ b/src/app/core/cache/models/normalized-community.model.ts @@ -1,4 +1,4 @@ -import { autoserialize, inheritSerialization } from 'cerialize'; +import { autoserialize, deserialize, inheritSerialization, serialize } from 'cerialize'; import { NormalizedDSpaceObject } from './normalized-dspace-object.model'; import { Community } from '../../shared/community.model'; @@ -21,32 +21,32 @@ export class NormalizedCommunity extends NormalizedDSpaceObject { /** * The Bitstream that represents the logo of this Community */ - @autoserialize + @deserialize @relationship(ResourceType.Bitstream, false) logo: string; /** * An array of Communities that are direct parents of this Community */ - @autoserialize + @deserialize @relationship(ResourceType.Community, true) parents: string[]; /** * The Community that owns this Community */ - @autoserialize + @deserialize @relationship(ResourceType.Community, false) owner: string; /** * List of Collections that are owned by this Community */ - @autoserialize + @deserialize @relationship(ResourceType.Collection, true) collections: string[]; - @autoserialize + @deserialize @relationship(ResourceType.Community, true) subcommunities: string[]; diff --git a/src/app/core/cache/models/normalized-dspace-object.model.ts b/src/app/core/cache/models/normalized-dspace-object.model.ts index 92174c40f7..efdfa6dd74 100644 --- a/src/app/core/cache/models/normalized-dspace-object.model.ts +++ b/src/app/core/cache/models/normalized-dspace-object.model.ts @@ -1,4 +1,4 @@ -import { autoserialize, autoserializeAs } from 'cerialize'; +import { autoserialize, autoserializeAs, deserialize, serialize } from 'cerialize'; import { DSpaceObject } from '../../shared/dspace-object.model'; import { Metadatum } from '../../shared/metadatum.model'; @@ -45,12 +45,6 @@ export class NormalizedDSpaceObject extends NormalizedObject { @autoserialize type: ResourceType; - /** - * The name for this DSpaceObject - */ - @autoserialize - name: string; - /** * An array containing all metadata of this DSpaceObject */ @@ -60,13 +54,13 @@ export class NormalizedDSpaceObject extends NormalizedObject { /** * An array of DSpaceObjects that are direct parents of this DSpaceObject */ - @autoserialize + @deserialize parents: string[]; /** * The DSpaceObject that owns this DSpaceObject */ - @autoserialize + @deserialize owner: string; /** @@ -75,7 +69,7 @@ export class NormalizedDSpaceObject extends NormalizedObject { * Repeated here to make the serialization work, * inheritSerialization doesn't seem to work for more than one level */ - @autoserialize + @deserialize _links: { [name: string]: string } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index dcbdbd0049..2191743871 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -63,6 +63,8 @@ import { NotificationsService } from '../shared/notifications/notifications.serv import { UploaderService } from '../shared/uploader/uploader.service'; import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service'; import { DSpaceObjectDataService } from './data/dspace-object-data.service'; +import { DataBuildService } from './cache/builders/data-build.service'; +import { DSOUpdateComparator } from './data/dso-update-comparator'; const IMPORTS = [ CommonModule, @@ -99,6 +101,7 @@ const PROVIDERS = [ ObjectCacheService, PaginationComponentOptions, RegistryService, + DataBuildService, RemoteDataBuildService, RequestService, EndpointMapResponseParsingService, @@ -126,6 +129,7 @@ const PROVIDERS = [ UploaderService, UUIDService, DSpaceObjectDataService, + DSOUpdateComparator, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/data/base-response-parsing.service.ts b/src/app/core/data/base-response-parsing.service.ts index eada156ce9..d5c1c58296 100644 --- a/src/app/core/data/base-response-parsing.service.ts +++ b/src/app/core/data/base-response-parsing.service.ts @@ -8,15 +8,7 @@ import { GenericConstructor } from '../shared/generic-constructor'; import { PaginatedList } from './paginated-list'; import { ResourceType } from '../shared/resource-type'; import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; - -function isObjectLevel(halObj: any) { - return isNotEmpty(halObj._links) && hasValue(halObj._links.self); -} - -function isPaginatedResponse(halObj: any) { - return hasValue(halObj.page) && hasValue(halObj._embedded); -} - +import { isRestDataObject, isRestPaginatedList } from '../cache/builders/data-build.service'; /* tslint:disable:max-classes-per-file */ export abstract class BaseResponseParsingService { @@ -29,11 +21,11 @@ export abstract class BaseResponseParsingService { if (isNotEmpty(data)) { if (hasNoValue(data) || (typeof data !== 'object')) { return data; - } else if (isPaginatedResponse(data)) { + } else if (isRestPaginatedList(data)) { return this.processPaginatedList(data, requestUUID); } else if (Array.isArray(data)) { return this.processArray(data, requestUUID); - } else if (isObjectLevel(data)) { + } else if (isRestDataObject(data)) { data = this.fixBadEPersonRestResponse(data); const object = this.deserialize(data); if (isNotEmpty(data._embedded)) { @@ -43,10 +35,10 @@ export abstract class BaseResponseParsingService { .forEach((property) => { const parsedObj = this.process(data._embedded[property], requestUUID); if (isNotEmpty(parsedObj)) { - if (isPaginatedResponse(data._embedded[property])) { + if (isRestPaginatedList(data._embedded[property])) { object[property] = parsedObj; object[property].page = parsedObj.page.map((obj) => obj.self); - } else if (isObjectLevel(data._embedded[property])) { + } else if (isRestDataObject(data._embedded[property])) { object[property] = parsedObj.self; } else if (Array.isArray(parsedObj)) { object[property] = parsedObj.map((obj) => obj.self) @@ -80,7 +72,7 @@ export abstract class BaseResponseParsingService { list = this.flattenSingleKeyObject(list); } const page: ObjectDomain[] = this.processArray(list, requestUUID); - return new PaginatedList(pageInfo, page); + return new PaginatedList(pageInfo, page, ); } protected processArray(data: any, requestUUID: string): ObjectDomain[] { diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index b936d71c30..ef294c1296 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedCollection } from '../cache/models/normalized-collection.model'; @@ -10,9 +10,10 @@ import { CommunityDataService } from './community-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { AuthService } from '../auth/auth.service'; -import { Community } from '../shared/community.model'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -21,13 +22,15 @@ export class CollectionDataService extends ComColDataService, protected cds: CommunityDataService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient + protected http: HttpClient, + protected comparator: DSOUpdateComparator ) { super(); } diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index 3325dc6ac3..0bf5f85749 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -14,10 +14,12 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestEntry } from './request.reducer'; import { of as observableOf } from 'rxjs'; -import { Community } from '../shared/community.model'; import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; +import { UpdateComparator } from './update-comparator'; const LINK_NAME = 'test'; @@ -30,6 +32,7 @@ class TestService extends ComColDataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected store: Store, protected EnvConfig: GlobalConfig, protected cds: CommunityDataService, @@ -38,6 +41,7 @@ class TestService extends ComColDataService { protected authService: AuthService, protected notificationsService: NotificationsService, protected http: HttpClient, + protected comparator: DSOUpdateComparator, protected linkPath: string ) { super(); @@ -60,6 +64,8 @@ describe('ComColDataService', () => { const EnvConfig = {} as GlobalConfig; const notificationsService = {} as NotificationsService; const http = {} as HttpClient; + const comparator = {} as any; + const dataBuildService = {} as DataBuildService; const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; const options = Object.assign(new FindAllOptions(), { @@ -78,7 +84,7 @@ describe('ComColDataService', () => { const authHeader = 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJlaWQiOiJhNjA4NmIzNC0zOTE4LTQ1YjctOGRkZC05MzI5YTcwMmEyNmEiLCJzZyI6W10sImV4cCI6MTUzNDk0MDcyNX0.RV5GAtiX6cpwBN77P_v16iG9ipeyiO7faNYSNMzq_sQ'; const mockHalService = { - getEndpoint: (linkPath) => Observable.of(communitiesEndpoint) + getEndpoint: (linkPath) => observableOf(communitiesEndpoint) }; function initMockCommunityDataService(): CommunityDataService { @@ -112,6 +118,7 @@ describe('ComColDataService', () => { return new TestService( requestService, rdbService, + dataBuildService, store, EnvConfig, cds, @@ -120,6 +127,7 @@ describe('ComColDataService', () => { authService, notificationsService, http, + comparator, LINK_NAME ); } diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index d3eed88ffd..0616e9c2ed 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -20,8 +20,9 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestEntry } from './request.reducer'; import { getResponseFromEntry } from '../shared/operators'; +import { CacheableObject } from '../cache/object-cache.reducer'; -export abstract class ComColDataService extends DataService { +export abstract class ComColDataService extends DataService { protected abstract cds: CommunityDataService; protected abstract objectCache: ObjectCacheService; protected abstract halService: HALEndpointService; diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 9645a970c6..40d433a245 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -18,6 +18,8 @@ import { Observable } from 'rxjs'; import { PaginatedList } from './paginated-list'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; @Injectable() export class CommunityDataService extends ComColDataService { @@ -28,12 +30,14 @@ export class CommunityDataService extends ComColDataService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient + protected http: HttpClient, + protected comparator: DSOUpdateComparator ) { super(); } diff --git a/src/app/core/data/config-response-parsing.service.spec.ts b/src/app/core/data/config-response-parsing.service.spec.ts index a33c5cf5b5..caf8ef4a19 100644 --- a/src/app/core/data/config-response-parsing.service.spec.ts +++ b/src/app/core/data/config-response-parsing.service.spec.ts @@ -177,7 +177,7 @@ describe('ConfigResponseParsingService', () => { 'https://rest.api/config/submissionsections/traditionalpagetwo', 'https://rest.api/config/submissionsections/upload', 'https://rest.api/config/submissionsections/license' - ]) + ], 'https://rest.api/config/submissiondefinitions/traditional/sections') }); it('should return a ConfigSuccessResponse if data contains a valid config endpoint response', () => { diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 4bbe775f3b..85d6579176 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,4 +1,13 @@ -import { delay, distinctUntilChanged, filter, find, switchMap, map, take, tap } from 'rxjs/operators'; +import { + delay, + distinctUntilChanged, + filter, + find, + switchMap, + map, + take, + tap, first, mergeMap +} from 'rxjs/operators'; import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; @@ -32,10 +41,14 @@ import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/respon import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; +import { CacheableObject } from '../cache/object-cache.reducer'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { UpdateComparator } from './update-comparator'; -export abstract class DataService { +export abstract class DataService { protected abstract requestService: RequestService; protected abstract rdbService: RemoteDataBuildService; + protected abstract dataBuildService: DataBuildService; protected abstract store: Store; protected abstract linkPath: string; protected abstract halService: HALEndpointService; @@ -43,6 +56,7 @@ export abstract class DataService protected abstract authService: AuthService; protected abstract notificationsService: NotificationsService; protected abstract http: HttpClient; + protected abstract comparator: UpdateComparator; public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable @@ -122,15 +136,21 @@ export abstract class DataService * The patch is derived from the differences between the given object and its version in the object cache * @param {DSpaceObject} object The given object */ - update(object: DSpaceObject) { - const oldVersion = this.objectCache.getBySelfLink(object.self); - const operations = compare(oldVersion, object); - if (isNotEmpty(operations)) { - this.objectCache.addPatch(object.self, operations); - } + update(object: TDomain): Observable> { + const oldVersion$ = this.objectCache.getBySelfLink(object.self); + return oldVersion$.pipe(first(), mergeMap((oldVersion: TNormalized) => { + const newVersion = this.dataBuildService.normalize(object); + const operations = this.comparator.compare(oldVersion, newVersion); + if (isNotEmpty(operations)) { + this.objectCache.addPatch(object.self, operations); + } + return this.findById(object.uuid); + } + )); + } - create(dso: TNormalized, parentUUID: string): Observable> { + create(dso: TDomain, parentUUID: string): Observable> { const requestId = this.requestService.generateRequestId(); const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), @@ -138,7 +158,8 @@ export abstract class DataService map((endpoint: string) => parentUUID ? `${endpoint}?parent=${parentUUID}` : endpoint) ); - const serializedDso = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(dso.type)).serialize(dso); + const normalizedObject: TNormalized = this.dataBuildService.normalize(dso); + const serializedDso = new DSpaceRESTv2Serializer(NormalizedObjectFactory.getConstructor(dso.type)).serialize(normalizedObject); const request$ = endpoint$.pipe( take(1), @@ -150,6 +171,7 @@ export abstract class DataService configureRequest(this.requestService) ).subscribe(); + // Resolve self link for new object const selfLink$ = this.requestService.getByUUID(requestId).pipe( getResponseFromEntry(), map((response: RestResponse) => { diff --git a/src/app/core/data/dso-update-comparator.ts b/src/app/core/data/dso-update-comparator.ts new file mode 100644 index 0000000000..245fbfaef0 --- /dev/null +++ b/src/app/core/data/dso-update-comparator.ts @@ -0,0 +1,12 @@ +import { Operation } from 'fast-json-patch/lib/core'; +import { compare } from 'fast-json-patch'; +import { UpdateComparator } from './update-comparator'; +import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model'; +import { Injectable } from '@angular/core'; + +@Injectable() +export class DSOUpdateComparator implements UpdateComparator { + compare(object1: NormalizedDSpaceObject, object2: NormalizedDSpaceObject): Operation[] { + return compare(object1.metadata, object2.metadata).map((operation: Operation) => Object.assign({}, operation, { path: '/metadata' + operation.path })); + } +} diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 3e8d8bdc04..f1d0f21762 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -14,6 +14,8 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; /* tslint:disable:max-classes-per-file */ class DataServiceImpl extends DataService { @@ -22,12 +24,14 @@ class DataServiceImpl extends DataService constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected store: Store, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient) { + protected http: HttpClient, + protected comparator: DSOUpdateComparator) { super(); } @@ -48,12 +52,14 @@ export class DSpaceObjectDataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient) { - this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, authService, notificationsService, http); + protected http: HttpClient, + protected comparator: DSOUpdateComparator) { + this.dataService = new DataServiceImpl(requestService, rdbService, dataBuildService, null, objectCache, halService, authService, notificationsService, http, comparator); } findById(uuid: string): Observable> { diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 68380ddaa2..411daa9b35 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -19,6 +19,8 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { AuthService } from '../auth/auth.service'; import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from './dso-update-comparator'; @Injectable() export class ItemDataService extends DataService { @@ -27,13 +29,15 @@ export class ItemDataService extends DataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected store: Store, private bs: BrowseService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected authService: AuthService, protected notificationsService: NotificationsService, - protected http: HttpClient) { + protected http: HttpClient, + protected comparator: DSOUpdateComparator) { super(); } diff --git a/src/app/core/data/paginated-list.ts b/src/app/core/data/paginated-list.ts index 07d53739d0..8efdccd75d 100644 --- a/src/app/core/data/paginated-list.ts +++ b/src/app/core/data/paginated-list.ts @@ -81,4 +81,12 @@ export class PaginatedList { set last(last: string) { this.pageInfo.last = last; } + + get self(): string { + return this.pageInfo.self; + } + + set self(self: string) { + this.pageInfo.self = self; + } } diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 285ed06545..20bc85a87f 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -80,7 +80,7 @@ export class RequestService { this.store.pipe(select(this.entryFromUUIDSelector(uuid))), this.store.pipe( select(this.originalUUIDFromUUIDSelector(uuid)), - switchMap((originalUUID) => { + mergeMap((originalUUID) => { return this.store.pipe(select(this.entryFromUUIDSelector(originalUUID))) }, )) diff --git a/src/app/core/data/update-comparator.ts b/src/app/core/data/update-comparator.ts new file mode 100644 index 0000000000..884ac585f5 --- /dev/null +++ b/src/app/core/data/update-comparator.ts @@ -0,0 +1,6 @@ +import { NormalizedObject } from '../cache/models/normalized-object.model'; +import { Operation } from 'fast-json-patch/lib/core'; + +export interface UpdateComparator { + compare(object1: TNormalized, object2: TNormalized): Operation[]; +} \ No newline at end of file diff --git a/src/app/core/shared/dspace-object.model.ts b/src/app/core/shared/dspace-object.model.ts index 68338143ba..3e08da151c 100644 --- a/src/app/core/shared/dspace-object.model.ts +++ b/src/app/core/shared/dspace-object.model.ts @@ -34,14 +34,15 @@ export class DSpaceObject implements CacheableObject, ListableObject { /** * The name for this DSpaceObject */ - @autoserialize - name: string; + get name(): string { + return this.findMetadata('dc.title'); + } /** * An array containing all metadata of this DSpaceObject */ @autoserialize - metadata: Metadatum[]; + metadata: Metadatum[] = []; /** * An array of DSpaceObjects that are direct parents of this DSpaceObject diff --git a/src/app/core/shared/page-info.model.ts b/src/app/core/shared/page-info.model.ts index ba2af24dce..4ed281657d 100644 --- a/src/app/core/shared/page-info.model.ts +++ b/src/app/core/shared/page-info.model.ts @@ -39,4 +39,7 @@ export class PageInfo { @autoserialize first: string; + + @autoserialize + self: string; } diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts index 3544bce280..8a3cf52abb 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control.component.ts @@ -96,7 +96,6 @@ export class DsDynamicFormControlComponent extends DynamicFormControlContainerCo } static getFormControlType(model: DynamicFormControlModel): Type | null { - switch (model.type) { case DYNAMIC_FORM_CONTROL_TYPE_ARRAY: diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html index 958c9a6c73..18d6533df4 100644 --- a/src/app/shared/form/form.component.html +++ b/src/app/shared/form/form.component.html @@ -51,7 +51,7 @@
-
diff --git a/src/app/shared/form/form.component.ts b/src/app/shared/form/form.component.ts index 2d74ddf8d4..9848f9feed 100644 --- a/src/app/shared/form/form.component.ts +++ b/src/app/shared/form/form.component.ts @@ -73,7 +73,7 @@ export class FormComponent implements OnDestroy, OnInit { * An event fired when form is valid and submitted . * Event's payload equals to the form content. */ - @Output() submit: EventEmitter> = new EventEmitter>(); + @Output() submitForm: EventEmitter> = new EventEmitter>(); /** * An object of FormGroup type @@ -264,7 +264,7 @@ export class FormComponent implements OnDestroy, OnInit { */ onSubmit(): void { if (this.getFormGroupValidStatus()) { - this.submit.emit(this.formService.getFormData(this.formId)); + this.submitForm.emit(this.formService.getFormData(this.formId)); } else { this.formService.validateAllFormFields(this.formGroup); } diff --git a/src/app/shared/form/form.service.ts b/src/app/shared/form/form.service.ts index ae5ba9f278..9356f86e8c 100644 --- a/src/app/shared/form/form.service.ts +++ b/src/app/shared/form/form.service.ts @@ -98,7 +98,7 @@ export class FormService { const errorKey = this.getValidatorNameFromMap(message); let errorMsg = message; - // if form control model has not errorMessages object, create it + // if form control model has no errorMessages object, create it if (!model.errorMessages) { model.errorMessages = {}; } From d37a2e051d35ae9ea2195b335fc8b51534c28f25 Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 21 Dec 2018 16:22:59 +0100 Subject: [PATCH 40/57] Started fixing tests --- resources/i18n/en.json | 24 ++-- .../collection-form.component.spec.ts | 8 +- .../create-collection-page.component.spec.ts | 44 ++++--- .../create-collection-page.component.ts | 1 - .../community-form.component.html | 5 +- .../community-form.component.spec.ts | 111 ++++++++++++------ .../community-form.component.ts | 40 +++++-- .../create-community-page.component.html | 6 +- .../create-community-page.component.spec.ts | 92 +++++++++------ .../create-community-page.component.ts | 8 +- .../edit-community-page.component.html | 5 +- .../edit-community-page.component.spec.ts | 111 +++++++++++------- .../edit-community-page.component.ts | 13 +- ...-page-sub-community-list.component.spec.ts | 2 - .../search-results.component.spec.ts | 2 - src/app/core/config/config.service.spec.ts | 1 - src/app/core/data/collection-data.service.ts | 2 - src/app/core/data/comcol-data.service.spec.ts | 14 --- src/app/core/data/community-data.service.ts | 2 - .../config-response-parsing.service.spec.ts | 2 +- src/app/core/data/data.service.spec.ts | 30 +++-- src/app/core/data/data.service.ts | 4 +- .../data/dspace-object-data.service.spec.ts | 13 +- .../core/data/dspace-object-data.service.ts | 5 +- src/app/core/data/item-data.service.spec.ts | 13 +- src/app/core/data/item-data.service.ts | 2 - .../integration/integration.service.spec.ts | 2 +- src/app/shared/form/form.component.spec.ts | 4 +- src/app/shared/mocks/mock-item.ts | 3 - .../search-form/search-form.component.spec.ts | 2 - src/app/shared/testing/eperson-mock.ts | 6 +- 31 files changed, 351 insertions(+), 226 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 70a1fc20b0..46d7617f6c 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -44,19 +44,21 @@ "sub-community-list": { "head": "Communities of this Community" }, - "edit": { - "head": "Edit collction", - "name": "Name", - "description": "Short Description", - "introductory": "Introductory text (HTML)", - "copyright": "Copyright text (HTML)", - "news": "News (HTML)", - "submit": "Submit", - "cancel": "Cancel", - "required": { - "name": "Please enter a community name" + "form": { + "title": "Name", + "description": "Introductory text (HTML)", + "abstract": "Short Description", + "rights": "Copyright text (HTML)", + "tableofcontents": "News (HTML)", + "errors": { + "title": { + "required": "Please enter a community name" + } } }, + "edit": { + "head": "Edit collection" + }, "create": { "head": "Create a Community", "sub-head": "Create a Sub-Community for Community {{ parent }}" diff --git a/src/app/+collection-page/collection-form/collection-form.component.spec.ts b/src/app/+collection-page/collection-form/collection-form.component.spec.ts index d2aafa2050..71ae92572d 100644 --- a/src/app/+collection-page/collection-form/collection-form.component.spec.ts +++ b/src/app/+collection-page/collection-form/collection-form.component.spec.ts @@ -4,9 +4,10 @@ import { TranslateModule } from '@ngx-translate/core'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { By } from '@angular/platform-browser'; -import { DebugElement } from '@angular/core'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; import { CollectionFormComponent } from './collection-form.component'; import { Location } from '@angular/common'; +import { DynamicFormService } from '@ng-dynamic-forms/core'; describe('CommunityFormComponent', () => { let comp: CollectionFormComponent; @@ -24,8 +25,9 @@ describe('CommunityFormComponent', () => { imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], declarations: [CollectionFormComponent], providers: [ - { provide: Location, useValue: locationStub } - ] + { provide: Location, useValue: locationStub }, + ], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); 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 6196945d80..837741d593 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 @@ -1,15 +1,11 @@ import { SharedModule } from '../../shared/shared.module'; import { Community } from '../../core/shared/community.model'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; -import { CommonModule } from '@angular/common'; -import { CreateCommunityPageComponent } from '../../+community-page/create-community-page/create-community-page.component'; +import { CommonModule, Location } from '@angular/common'; import { Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; -import { CommunityFormComponent } from '../../+community-page/community-form/community-form.component'; -import { Observable } from 'rxjs/Observable'; +import { Observable } from 'rxjs'; import { CommunityDataService } from '../../core/data/community-data.service'; -import { RequestError } from '../../core/data/request.models'; import { RouteService } from '../../shared/services/route.service'; import { RemoteData } from '../../core/data/remote-data'; import { CreateCollectionPageComponent } from './create-collection-page.component'; @@ -17,6 +13,8 @@ import { CollectionDataService } from '../../core/data/collection-data.service'; import { Collection } from '../../core/shared/collection.model'; import { RouterTestingModule } from '@angular/router/testing'; import { CollectionFormComponent } from '../collection-form/collection-form.component'; +import { of as observableOf } from 'rxjs'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('CreateCollectionPageComponent', () => { let comp: CreateCollectionPageComponent; @@ -28,25 +26,33 @@ describe('CreateCollectionPageComponent', () => { const community = Object.assign(new Community(), { uuid: 'a20da287-e174-466a-9926-f66b9300d347', - name: 'test community' + metadata: [{ + key: 'dc.title', + value: 'test collection' + }] }); const collection = Object.assign(new Collection(), { uuid: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4', - name: 'new collection' - }); + metadata: [{ + key: 'dc.title', + value: 'new collection' + }] }); const collectionDataServiceStub = { - create: (col, uuid?) => Observable.of(new RemoteData(false, false, true, undefined, collection)) + create: (col, uuid?) => observableOf(new RemoteData(false, false, true, undefined, collection)) }; const communityDataServiceStub = { - findById: (uuid) => Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { + findById: (uuid) => observableOf(new RemoteData(false, false, true, null, Object.assign(new Community(), { uuid: uuid, - name: community.name + metadata: [{ + key: 'dc.title', + value: community.name + }] }))) }; const routeServiceStub = { - getQueryParameterValue: (param) => Observable.of(community.uuid) + getQueryParameterValue: (param) => observableOf(community.uuid) }; const routerStub = { navigate: (commands) => commands @@ -55,13 +61,14 @@ describe('CreateCollectionPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], - declarations: [CreateCollectionPageComponent, CollectionFormComponent], + declarations: [CreateCollectionPageComponent], providers: [ { provide: CollectionDataService, useValue: collectionDataServiceStub }, { provide: CommunityDataService, useValue: communityDataServiceStub }, { provide: RouteService, useValue: routeServiceStub }, { provide: Router, useValue: routerStub } - ] + ], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); @@ -77,7 +84,10 @@ describe('CreateCollectionPageComponent', () => { describe('onSubmit', () => { const data = { - name: 'test' + metadata: [{ + key: 'dc.title', + value:'test' + }] }; it('should navigate when successful', () => { @@ -89,7 +99,7 @@ describe('CreateCollectionPageComponent', () => { it('should not navigate on failure', () => { spyOn(router, 'navigate'); - spyOn(collectionDataService, 'create').and.returnValue(Observable.of(new RemoteData(true, true, false, undefined, collection))); + spyOn(collectionDataService, 'create').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, collection))); comp.onSubmit(data); fixture.detectChanges(); expect(router.navigate).not.toHaveBeenCalled(); 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 d934e1d7a0..1bb4c31596 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 @@ -9,7 +9,6 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; -import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; import { ResourceType } from '../../core/shared/resource-type'; @Component({ diff --git a/src/app/+community-page/community-form/community-form.component.html b/src/app/+community-page/community-form/community-form.component.html index 637506f86d..c47f077022 100644 --- a/src/app/+community-page/community-form/community-form.component.html +++ b/src/app/+community-page/community-form/community-form.component.html @@ -1,4 +1,3 @@ - - + [formModel]="formModel" (submitForm)="onSubmit()"> \ No newline at end of file diff --git a/src/app/+community-page/community-form/community-form.component.spec.ts b/src/app/+community-page/community-form/community-form.component.spec.ts index ea17d15942..b90a2b0713 100644 --- a/src/app/+community-page/community-form/community-form.component.spec.ts +++ b/src/app/+community-page/community-form/community-form.component.spec.ts @@ -1,31 +1,65 @@ import { CommunityFormComponent } from './community-form.component'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { SharedModule } from '../../shared/shared.module'; import { TranslateModule } from '@ngx-translate/core'; -import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; -import { By } from '@angular/platform-browser'; -import { DebugElement } from '@angular/core'; import { Location } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { + DynamicFormService, + DynamicInputControlModel, + DynamicInputModel +} from '@ng-dynamic-forms/core'; +import { FormControl, FormGroup } from '@angular/forms'; +import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; +import { Community } from '../../core/shared/community.model'; +import { ResourceType } from '../../core/shared/resource-type'; -describe('CommunityFormComponent', () => { +fdescribe('CommunityFormComponent', () => { let comp: CommunityFormComponent; let fixture: ComponentFixture; let location: Location; + const formServiceStub: any = { + createFormGroup: (formModel: DynamicFormControlModel[]) => { + const controls = {}; + formModel.forEach((controlModel) => { + controls[controlModel.id] = new FormControl((controlModel as any).value); + }); + return new FormGroup(controls); + } + }; + const titleMD = { key: 'dc.title', value: 'Community Title' }; + const randomMD = { key: 'dc.random', value: 'Random metadata excluded from form' }; + const abstractMD = { key: 'dc.description.abstract', value: 'Community description' }; + const newTitleMD = { key: 'dc.title', value: 'New Community Title' }; + const formModel = [ + new DynamicInputModel({ + id: 'title', + name: newTitleMD.key, + value: newTitleMD.value + }), + new DynamicInputModel({ + id: 'abstract', + name: abstractMD.key, + value: abstractMD.value + }) + ]; /* tslint:disable:no-empty */ const locationStub = { - back: () => {} + back: () => { + } }; /* tslint:enable:no-empty */ beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + imports: [TranslateModule.forRoot(), RouterTestingModule], declarations: [CommunityFormComponent], providers: [ - { provide: Location, useValue: locationStub } - ] + { provide: Location, useValue: locationStub }, + { provide: DynamicFormService, useValue: formServiceStub } + ], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); @@ -34,38 +68,41 @@ describe('CommunityFormComponent', () => { comp = fixture.componentInstance; fixture.detectChanges(); location = (comp as any).location; + comp.formModel = formModel; }); - describe('when submitting', () => { - let input: DebugElement; - let submit: DebugElement; - let cancel: DebugElement; - let error: DebugElement; - + describe('onSubmit', () => { beforeEach(() => { - input = fixture.debugElement.query(By.css('input#community-name')); - submit = fixture.debugElement.query(By.css('button#community-submit')); - cancel = fixture.debugElement.query(By.css('button#community-cancel')); - error = fixture.debugElement.query(By.css('div.invalid-feedback')); + spyOn(comp.submitForm, 'emit'); }); - it('should display an error when leaving name empty', () => { - const el = input.nativeElement; + it('should update emit the new version of the community', () => { + comp.community = Object.assign( + new Community(), + { + metadata: [ + titleMD, + randomMD + ] + } + ); - el.value = ''; - el.dispatchEvent(new Event('input')); - submit.nativeElement.click(); - fixture.detectChanges(); + comp.onSubmit(); - expect(error.nativeElement.style.display).not.toEqual('none'); - }); - - it('should navigate back when pressing cancel', () => { - spyOn(location, 'back'); - cancel.nativeElement.click(); - fixture.detectChanges(); - - expect(location.back).toHaveBeenCalled(); - }); - }) + expect(comp.submitForm.emit).toHaveBeenCalledWith( + Object.assign( + {}, + new Community(), + { + metadata: [ + randomMD, + newTitleMD, + abstractMD + ], + type: ResourceType.Community + }, + ) + ); + }) + }); }); diff --git a/src/app/+community-page/community-form/community-form.component.ts b/src/app/+community-page/community-form/community-form.component.ts index fd0b490146..c470cbbf91 100644 --- a/src/app/+community-page/community-form/community-form.component.ts +++ b/src/app/+community-page/community-form/community-form.component.ts @@ -9,7 +9,11 @@ import { FormGroup } from '@angular/forms'; import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; import { Community } from '../../core/shared/community.model'; import { ResourceType } from '../../core/shared/resource-type'; -import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { isNotEmpty } from '../../shared/empty.util'; +import { TranslateService } from '@ngx-translate/core'; + +const LABEL_KEY_PREFIX = 'community.form.'; +const ERROR_KEY_PREFIX = 'community.form.errors.'; @Component({ selector: 'ds-community-form', @@ -17,40 +21,34 @@ import { hasValue, isNotEmpty } from '../../shared/empty.util'; templateUrl: './community-form.component.html' }) export class CommunityFormComponent implements OnInit { - @Input() community: Community = new Community(); formModel: DynamicFormControlModel[] = [ new DynamicInputModel({ id: 'title', name: 'dc.title', - label: 'Name', required: true, validators: { required: null }, errorMessages: { required: 'Please enter a name for this title' - } + }, }), new DynamicTextAreaModel({ id: 'description', name: 'dc.description', - label: 'Introductory text (HTML)', }), new DynamicTextAreaModel({ id: 'abstract', name: 'dc.description.abstract', - label: 'Short Description', }), new DynamicTextAreaModel({ id: 'rights', name: 'dc.rights', - label: 'Copyright text (HTML)', }), new DynamicTextAreaModel({ id: 'tableofcontents', name: 'dc.description.tableofcontents', - label: 'News (HTML)', }), ]; @@ -58,7 +56,9 @@ export class CommunityFormComponent implements OnInit { @Output() submitForm: EventEmitter = new EventEmitter(); - public constructor(private location: Location, private formService: DynamicFormService) { + public constructor(private location: Location, + private formService: DynamicFormService, + private translate: TranslateService) { } ngOnInit(): void { @@ -68,10 +68,14 @@ export class CommunityFormComponent implements OnInit { } ); this.formGroup = this.formService.createFormGroup(this.formModel); + this.updateFieldTranslations(); + this.translate.onLangChange + .subscribe(() => { + this.updateFieldTranslations(); + }); } - onSubmit(event: Event) { - event.stopPropagation(); + onSubmit() { const metadata = this.formModel.map( (fieldModel: DynamicInputModel) => { return { key: fieldModel.name, value: fieldModel.value } @@ -87,7 +91,17 @@ export class CommunityFormComponent implements OnInit { this.submitForm.emit(updatedCommunity); } - cancel() { - this.location.back(); + private updateFieldTranslations() { + this.formModel.forEach( + (fieldModel: DynamicInputModel) => { + fieldModel.label = this.translate.instant(LABEL_KEY_PREFIX + fieldModel.id); + if (isNotEmpty(fieldModel.validators)) { + fieldModel.errorMessages = {}; + Object.keys(fieldModel.validators).forEach((key) => { + fieldModel.errorMessages[key] = this.translate.instant(ERROR_KEY_PREFIX + fieldModel.id + '.' + key); + }); + } + } + ); } } diff --git a/src/app/+community-page/create-community-page/create-community-page.component.html b/src/app/+community-page/create-community-page/create-community-page.component.html index bb0f5b83af..aeb4713b52 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.html +++ b/src/app/+community-page/create-community-page/create-community-page.component.html @@ -1,9 +1,9 @@
- - -

{{ 'community.create.sub-head' | translate:{ parent: community.name } }}

+ + +

{{ 'community.create.sub-head' | translate:{ parent: parent.name } }}

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 e76b10bde6..83db9561cc 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,55 +4,76 @@ import { CommunityDataService } from '../../core/data/community-data.service'; import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; -import { Observable } from 'rxjs/Observable'; +import { of as observableOf } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Community } from '../../core/shared/community.model'; -import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; import { SharedModule } from '../../shared/shared.module'; import { CommonModule } from '@angular/common'; -import { CommunityFormComponent } from '../community-form/community-form.component'; import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; -describe('CreateCommunityPageComponent', () => { +fdescribe('CreateCommunityPageComponent', () => { let comp: CreateCommunityPageComponent; let fixture: ComponentFixture; let communityDataService: CommunityDataService; let routeService: RouteService; let router: Router; - const community = Object.assign(new Community(), { - uuid: 'a20da287-e174-466a-9926-f66b9300d347', - name: 'test community' - }); + let community; + let newCommunity; + let communityDataServiceStub; + let routeServiceStub; + let routerStub; - const newCommunity = Object.assign(new Community(), { - uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', - name: 'new community' - }); + function initializeVars() { + community = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66b9300d347', + metadata: [{ + key: 'dc.title', + value: 'test community' + }] + }); - const communityDataServiceStub = { - findById: (uuid) => Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { - uuid: uuid, - name: community.name - }))), - create: (com, uuid?) => Observable.of(new RemoteData(false, false, true, undefined, newCommunity)) - }; - const routeServiceStub = { - getQueryParameterValue: (param) => Observable.of(community.uuid) - }; - const routerStub = { - navigate: (commands) => commands - }; + newCommunity = Object.assign(new Community(), { + uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', + metadata: [{ + key: 'dc.title', + value: 'new community' + }] + }); + + communityDataServiceStub = { + findById: (uuid) => observableOf(new RemoteData(false, false, true, null, Object.assign(new Community(), { + uuid: uuid, + metadata: [{ + key: 'dc.title', + value: community.name + }] + }))), + create: (com, uuid?) => observableOf(new RemoteData(false, false, true, undefined, newCommunity)) + + }; + + routeServiceStub = { + getQueryParameterValue: (param) => observableOf(community.uuid) + }; + routerStub = { + navigate: (commands) => commands + }; + + } beforeEach(async(() => { + initializeVars(); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], - declarations: [CreateCommunityPageComponent, CommunityFormComponent], + declarations: [CreateCommunityPageComponent], providers: [ { provide: CommunityDataService, useValue: communityDataServiceStub }, { provide: RouteService, useValue: routeServiceStub }, - { provide: Router, useValue: routerStub } - ] + { provide: Router, useValue: routerStub }, + ], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); @@ -66,10 +87,15 @@ describe('CreateCommunityPageComponent', () => { }); describe('onSubmit', () => { - const data = { - name: 'test' - }; - + let data; + beforeEach(() => { + data = Object.assign(new Community(), { + metadata: [{ + key: 'dc.title', + value: 'test' + }] + }); + }); it('should navigate when successful', () => { spyOn(router, 'navigate'); comp.onSubmit(data); @@ -79,7 +105,7 @@ describe('CreateCommunityPageComponent', () => { it('should not navigate on failure', () => { spyOn(router, 'navigate'); - spyOn(communityDataService, 'create').and.returnValue(Observable.of(new RemoteData(true, true, false, undefined, newCommunity))); + spyOn(communityDataService, 'create').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, newCommunity))); comp.onSubmit(data); fixture.detectChanges(); expect(router.navigate).not.toHaveBeenCalled(); 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 5373abfe22..8871d33547 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 @@ -5,7 +5,7 @@ import { Observable } from 'rxjs'; import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; -import { isNotEmpty } from '../../shared/empty.util'; +import { isNotEmpty, isNotUndefined } from '../../shared/empty.util'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { map, take } from 'rxjs/operators'; import { getSucceededRemoteData } from '../../core/shared/operators'; @@ -42,8 +42,10 @@ export class CreateCommunityPageComponent implements OnInit { this.communityDataService.create(community, uuid) .pipe(getSucceededRemoteData()) .subscribe((communityRD: RemoteData) => { - const newUUID = communityRD.payload.uuid; - this.router.navigate(['/communities/' + newUUID]); + if (isNotUndefined(communityRD)) { + const newUUID = communityRD.payload.uuid; + this.router.navigate(['/communities/' + newUUID]); + } }); }); } diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.html b/src/app/+community-page/edit-community-page/edit-community-page.component.html index eb9f797b3d..1dc6442307 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.html +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.html @@ -1,10 +1,7 @@
- - -

{{ 'community.edit.sub-head' | translate:{ parent: parent.name } }}

-
+
diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts index 6c5eb5f591..84edd47e1d 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts @@ -1,65 +1,91 @@ -import { CreateCommunityPageComponent } from './create-community-page.component'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CommunityDataService } from '../../core/data/community-data.service'; import { RouteService } from '../../shared/services/route.service'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; -import { Observable } from 'rxjs/Observable'; +import { of as observableOf } from 'rxjs'; import { RemoteData } from '../../core/data/remote-data'; import { Community } from '../../core/shared/community.model'; -import { DSOSuccessResponse, ErrorResponse } from '../../core/cache/response-cache.models'; -import { BrowserModule } from '@angular/platform-browser'; import { SharedModule } from '../../shared/shared.module'; import { CommonModule } from '@angular/common'; -import { CommunityFormComponent } from '../community-form/community-form.component'; import { RouterTestingModule } from '@angular/router/testing'; -import { RequestError } from '../../core/data/request.models'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { EditCommunityPageComponent } from './edit-community-page.component'; +import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; -describe('CreateCommunityPageComponent', () => { - let comp: CreateCommunityPageComponent; - let fixture: ComponentFixture; +fdescribe('EditCommunityPageComponent', () => { + let comp: EditCommunityPageComponent; + let fixture: ComponentFixture; let communityDataService: CommunityDataService; let routeService: RouteService; let router: Router; - const community = Object.assign(new Community(), { - uuid: 'a20da287-e174-466a-9926-f66b9300d347', - name: 'test community' - }); + let community; + let newCommunity; + let communityDataServiceStub; + let routeServiceStub; + let routerStub; + let routeStub; - const newCommunity = Object.assign(new Community(), { - uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', - name: 'new community' - }); + function initializeVars() { + community = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66b9300d347', + metadata: [{ + key: 'dc.title', + value: 'test community' + }] + }); - const communityDataServiceStub = { - findById: (uuid) => Observable.of(new RemoteData(false, false, true, null, Object.assign(new Community(), { - uuid: uuid, - name: community.name - }))), - create: (com, uuid?) => Observable.of(new RemoteData(false, false, true, undefined, newCommunity)) - }; - const routeServiceStub = { - getQueryParameterValue: (param) => Observable.of(community.uuid) - }; - const routerStub = { - navigate: (commands) => commands - }; + newCommunity = Object.assign(new Community(), { + uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', + metadata: [{ + key: 'dc.title', + value: 'new community' + }] + }); + + communityDataServiceStub = { + findById: (uuid) => observableOf(new RemoteData(false, false, true, null, Object.assign(new Community(), { + uuid: uuid, + metadata: [{ + key: 'dc.title', + value: community.name + }] + }))), + update: (com, uuid?) => observableOf(new RemoteData(false, false, true, undefined, newCommunity)) + + }; + + routeServiceStub = { + getQueryParameterValue: (param) => observableOf(community.uuid) + }; + routerStub = { + navigate: (commands) => commands + }; + + routeStub = { + data: observableOf(community) + }; + + } beforeEach(async(() => { + initializeVars(); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], - declarations: [CreateCommunityPageComponent, CommunityFormComponent], + declarations: [EditCommunityPageComponent], providers: [ { provide: CommunityDataService, useValue: communityDataServiceStub }, { provide: RouteService, useValue: routeServiceStub }, - { provide: Router, useValue: routerStub } - ] + { provide: Router, useValue: routerStub }, + { provide: ActivatedRoute, useValue: routeStub }, + ], + schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); beforeEach(() => { - fixture = TestBed.createComponent(CreateCommunityPageComponent); + fixture = TestBed.createComponent(EditCommunityPageComponent); comp = fixture.componentInstance; fixture.detectChanges(); communityDataService = (comp as any).communityDataService; @@ -68,10 +94,15 @@ describe('CreateCommunityPageComponent', () => { }); describe('onSubmit', () => { - const data = { - name: 'test' - }; - + let data; + beforeEach(() => { + data = Object.assign(new Community(), { + metadata: [{ + key: 'dc.title', + value: 'test' + }] + }); + }); it('should navigate when successful', () => { spyOn(router, 'navigate'); comp.onSubmit(data); @@ -81,7 +112,7 @@ describe('CreateCommunityPageComponent', () => { it('should not navigate on failure', () => { spyOn(router, 'navigate'); - spyOn(communityDataService, 'create').and.returnValue(Observable.of(new RemoteData(true, true, false, undefined, newCommunity))); + spyOn(communityDataService, 'update').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, newCommunity))); comp.onSubmit(data); fixture.detectChanges(); expect(router.navigate).not.toHaveBeenCalled(); diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.ts index 58805af80a..1528c9e8d5 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.ts +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.ts @@ -5,11 +5,8 @@ import { Observable } from 'rxjs'; import { RouteService } from '../../shared/services/route.service'; import { ActivatedRoute, Router } from '@angular/router'; import { RemoteData } from '../../core/data/remote-data'; -import { isNotEmpty } from '../../shared/empty.util'; -import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { first, map, take, tap } from 'rxjs/operators'; -import { ResourceType } from '../../core/shared/resource-type'; -import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { isNotUndefined } from '../../shared/empty.util'; +import { first, map } from 'rxjs/operators'; import { getSucceededRemoteData } from '../../core/shared/operators'; @Component({ @@ -39,8 +36,10 @@ export class EditCommunityPageComponent { this.communityDataService.update(community) .pipe(getSucceededRemoteData()) .subscribe((communityRD: RemoteData) => { - const newUUID = communityRD.payload.uuid; - this.router.navigate(['/communities/' + newUUID]); + if (isNotUndefined(communityRD)) { + const newUUID = communityRD.payload.uuid; + this.router.navigate(['/communities/' + newUUID]); + } }); } } diff --git a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts index 0c985e37f9..ace748c7de 100644 --- a/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts +++ b/src/app/+community-page/sub-community-list/community-page-sub-community-list.component.spec.ts @@ -17,7 +17,6 @@ describe('SubCommunityList Component', () => { let fixture: ComponentFixture; const subcommunities = [Object.assign(new Community(), { - name: 'SubCommunity 1', id: '123456789-1', metadata: [ { @@ -27,7 +26,6 @@ describe('SubCommunityList Component', () => { }] }), Object.assign(new Community(), { - name: 'SubCommunity 2', id: '123456789-2', metadata: [ { diff --git a/src/app/+search-page/search-results/search-results.component.spec.ts b/src/app/+search-page/search-results/search-results.component.spec.ts index 54463d916d..b7ac11553a 100644 --- a/src/app/+search-page/search-results/search-results.component.spec.ts +++ b/src/app/+search-page/search-results/search-results.component.spec.ts @@ -111,7 +111,6 @@ export const objects = [ id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', type: ResourceType.Community, - name: 'OR2017 - Demonstration', metadata: [ { key: 'dc.description', @@ -161,7 +160,6 @@ export const objects = [ id: '9076bd16-e69a-48d6-9e41-0238cb40d863', uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', type: ResourceType.Community, - name: 'Sample Community', metadata: [ { key: 'dc.description', diff --git a/src/app/core/config/config.service.spec.ts b/src/app/core/config/config.service.spec.ts index 8e9f7db27a..44cfdee358 100644 --- a/src/app/core/config/config.service.spec.ts +++ b/src/app/core/config/config.service.spec.ts @@ -36,7 +36,6 @@ describe('ConfigService', () => { const scopedEndpoint = `${serviceEndpoint}/${scopeName}`; const searchEndpoint = `${serviceEndpoint}/${BROWSE}?uuid=${scopeID}`; - function initTestService(): TestService { return new TestService( requestService, diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index ef294c1296..b08b1005b7 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -9,7 +9,6 @@ import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { DataBuildService } from '../cache/builders/data-build.service'; @@ -27,7 +26,6 @@ export class CollectionDataService extends ComColDataService { protected cds: CommunityDataService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, - protected authService: AuthService, protected notificationsService: NotificationsService, protected http: HttpClient, protected comparator: DSOUpdateComparator, @@ -56,7 +53,6 @@ describe('ComColDataService', () => { let requestService: RequestService; let cds: CommunityDataService; let objectCache: ObjectCacheService; - let authService: AuthService; let halService: any = {}; const rdbService = {} as RemoteDataBuildService; @@ -106,14 +102,6 @@ describe('ComColDataService', () => { }); } - function initMockAuthService(): AuthService { - return jasmine.createSpyObj('authService', { - buildAuthHeader: cold('c-', { - c: authHeader - }) - }); - } - function initTestService(): TestService { return new TestService( requestService, @@ -124,7 +112,6 @@ describe('ComColDataService', () => { cds, objectCache, halService, - authService, notificationsService, http, comparator, @@ -137,7 +124,6 @@ describe('ComColDataService', () => { requestService = getMockRequestService(); objectCache = initMockObjectCacheService(); halService = mockHalService; - authService = initMockAuthService(); service = initTestService(); }); diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 40d433a245..63fbe3a21a 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -10,7 +10,6 @@ import { Community } from '../shared/community.model'; import { ComColDataService } from './comcol-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { AuthService } from '../auth/auth.service'; import { FindAllOptions, FindAllRequest } from './request.models'; import { RemoteData } from './remote-data'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; @@ -34,7 +33,6 @@ export class CommunityDataService extends ComColDataService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, - protected authService: AuthService, protected notificationsService: NotificationsService, protected http: HttpClient, protected comparator: DSOUpdateComparator diff --git a/src/app/core/data/config-response-parsing.service.spec.ts b/src/app/core/data/config-response-parsing.service.spec.ts index caf8ef4a19..a33c5cf5b5 100644 --- a/src/app/core/data/config-response-parsing.service.spec.ts +++ b/src/app/core/data/config-response-parsing.service.spec.ts @@ -177,7 +177,7 @@ describe('ConfigResponseParsingService', () => { 'https://rest.api/config/submissionsections/traditionalpagetwo', 'https://rest.api/config/submissionsections/upload', 'https://rest.api/config/submissionsections/license' - ], 'https://rest.api/config/submissiondefinitions/traditional/sections') + ]) }); it('should return a ConfigSuccessResponse if data contains a valid config endpoint response', () => { diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index 7da709abd5..ad6fb5f096 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -12,6 +12,11 @@ import { of as observableOf } from 'rxjs'; import { ObjectCacheService } from '../cache/object-cache.service'; import { Operation } from '../../../../node_modules/fast-json-patch'; import { DSpaceObject } from '../shared/dspace-object.model'; +import { AuthService } from '../auth/auth.service'; +import { UpdateComparator } from './update-comparator'; +import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; const endpoint = 'https://rest.api/core'; @@ -23,10 +28,14 @@ class TestService extends DataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, + protected dataBuildService: DataBuildService, protected store: Store, protected linkPath: string, protected halService: HALEndpointService, - protected objectCache: ObjectCacheService + protected objectCache: ObjectCacheService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: UpdateComparator ) { super(); } @@ -42,6 +51,10 @@ describe('DataService', () => { const requestService = {} as RequestService; const halService = {} as HALEndpointService; const rdbService = {} as RemoteDataBuildService; + const notificationsService = {} as NotificationsService; + const http = {} as HttpClient; + const comparator = {} as any; + const dataBuildService = {} as DataBuildService; const objectCache = { addPatch: () => { /* empty */ @@ -56,13 +69,16 @@ describe('DataService', () => { return new TestService( requestService, rdbService, + dataBuildService, store, endpoint, halService, - objectCache + objectCache, + notificationsService, + http, + comparator, ); } - service = initTestService(); describe('getFindAllHref', () => { @@ -134,7 +150,7 @@ describe('DataService', () => { let selfLink; beforeEach(() => { - operations = [{ op: 'replace', path: '/name', value: 'random string' } as Operation]; + operations = [{ op: 'replace', path: '/metadata/dc.title', value: 'random string' } as Operation]; selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7'; spyOn(objectCache, 'addPatch'); }); @@ -153,16 +169,16 @@ describe('DataService', () => { const name1 = 'random string'; const name2 = 'another random string'; beforeEach(() => { - operations = [{ op: 'replace', path: '/name', value: name2 } as Operation]; + operations = [{ op: 'replace', path: '/metadata/dc.title', value: name2 } as Operation]; selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7'; dso = new DSpaceObject(); dso.self = selfLink; - dso.name = name1; + dso.metadata = [{ key: 'dc.title', value: name1 }]; dso2 = new DSpaceObject(); dso2.self = selfLink; - dso2.name = name2; + dso2.metadata = [{ key: 'dc.title', value: name2 }]; spyOn(objectCache, 'getBySelfLink').and.returnValue(dso); spyOn(objectCache, 'addPatch'); diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 85d6579176..47be86c296 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -29,7 +29,6 @@ import { NormalizedObject } from '../cache/models/normalized-object.model'; import { compare, Operation } from 'fast-json-patch'; import { ObjectCacheService } from '../cache/object-cache.service'; import { DSpaceObject } from '../shared/dspace-object.model'; -import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { @@ -53,7 +52,6 @@ export abstract class DataService; @@ -155,7 +153,7 @@ export abstract class DataService parentUUID ? `${endpoint}?parent=${parentUUID}` : endpoint) + map((endpoint: string) => parentUUID ? `${endpoint}?parentCommunity=${parentUUID}` : endpoint) ); const normalizedObject: TNormalized = this.dataBuildService.normalize(dso); diff --git a/src/app/core/data/dspace-object-data.service.spec.ts b/src/app/core/data/dspace-object-data.service.spec.ts index cdddcb7ce6..2d478b8f73 100644 --- a/src/app/core/data/dspace-object-data.service.spec.ts +++ b/src/app/core/data/dspace-object-data.service.spec.ts @@ -7,6 +7,9 @@ import { FindByIDRequest } from './request.models'; import { RequestService } from './request.service'; import { DSpaceObjectDataService } from './dspace-object-data.service'; import { ObjectCacheService } from '../cache/object-cache.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; describe('DSpaceObjectDataService', () => { let scheduler: TestScheduler; @@ -40,12 +43,20 @@ describe('DSpaceObjectDataService', () => { }) }); objectCache = {} as ObjectCacheService; + const notificationsService = {} as NotificationsService; + const http = {} as HttpClient; + const comparator = {} as any; + const dataBuildService = {} as DataBuildService; service = new DSpaceObjectDataService( requestService, rdbService, + dataBuildService, + objectCache, halService, - objectCache + notificationsService, + http, + comparator ) }); diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index f1d0f21762..9a069c4d61 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -11,7 +11,6 @@ import { RemoteData } from './remote-data'; import { RequestService } from './request.service'; import { FindAllOptions } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { DataBuildService } from '../cache/builders/data-build.service'; @@ -28,7 +27,6 @@ class DataServiceImpl extends DataService protected store: Store, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, - protected authService: AuthService, protected notificationsService: NotificationsService, protected http: HttpClient, protected comparator: DSOUpdateComparator) { @@ -55,11 +53,10 @@ export class DSpaceObjectDataService { protected dataBuildService: DataBuildService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, - protected authService: AuthService, protected notificationsService: NotificationsService, protected http: HttpClient, protected comparator: DSOUpdateComparator) { - this.dataService = new DataServiceImpl(requestService, rdbService, dataBuildService, null, objectCache, halService, authService, notificationsService, http, comparator); + this.dataService = new DataServiceImpl(requestService, rdbService, dataBuildService, null, objectCache, halService, notificationsService, http, comparator); } findById(uuid: string): Observable> { diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts index bb67fc8412..1be361cb9d 100644 --- a/src/app/core/data/item-data.service.spec.ts +++ b/src/app/core/data/item-data.service.spec.ts @@ -9,6 +9,9 @@ import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { ObjectCacheService } from '../cache/object-cache.service'; import { FindAllOptions } from './request.models'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { HttpClient } from '@angular/common/http'; +import { DataBuildService } from '../cache/builders/data-build.service'; describe('ItemDataService', () => { let scheduler: TestScheduler; @@ -34,6 +37,10 @@ describe('ItemDataService', () => { const scopedEndpoint = `${itemBrowseEndpoint}?scope=${scopeID}`; const serviceEndpoint = `https://rest.api/core/items`; const browseError = new Error('getBrowseURL failed'); + const notificationsService = {} as NotificationsService; + const http = {} as HttpClient; + const comparator = {} as any; + const dataBuildService = {} as DataBuildService; function initMockBrowseService(isSuccessful: boolean) { const obs = isSuccessful ? @@ -48,10 +55,14 @@ describe('ItemDataService', () => { return new ItemDataService( requestService, rdbService, + dataBuildService, store, bs, + objectCache, halEndpointService, - objectCache + notificationsService, + http, + comparator ); } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 411daa9b35..2fb2c017dc 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -17,7 +17,6 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { FindAllOptions } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; -import { AuthService } from '../auth/auth.service'; import { HttpClient } from '@angular/common/http'; import { DataBuildService } from '../cache/builders/data-build.service'; import { DSOUpdateComparator } from './dso-update-comparator'; @@ -34,7 +33,6 @@ export class ItemDataService extends DataService { private bs: BrowseService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, - protected authService: AuthService, protected notificationsService: NotificationsService, protected http: HttpClient, protected comparator: DSOUpdateComparator) { diff --git a/src/app/core/integration/integration.service.spec.ts b/src/app/core/integration/integration.service.spec.ts index 158f4b0680..152d7ab165 100644 --- a/src/app/core/integration/integration.service.spec.ts +++ b/src/app/core/integration/integration.service.spec.ts @@ -40,7 +40,7 @@ describe('IntegrationService', () => { findOptions = new IntegrationSearchOptions(uuid, name, metadata); - function initTestService(): TestService { + function initTestService(): TestService { return new TestService( requestService, halService diff --git a/src/app/shared/form/form.component.spec.ts b/src/app/shared/form/form.component.spec.ts index 06676d191e..38e95b5c49 100644 --- a/src/app/shared/form/form.component.spec.ts +++ b/src/app/shared/form/form.component.spec.ts @@ -350,13 +350,13 @@ describe('FormComponent test suite', () => { const control = formComp.formGroup.get(['dc_title']); control.setValue('Test Title'); formState.testForm.valid = true; - spyOn(formComp.submit, 'emit'); + spyOn(formComp.submitForm, 'emit'); form.next(formState.testForm); formFixture.detectChanges(); formComp.onSubmit(); - expect(formComp.submit.emit).toHaveBeenCalled(); + expect(formComp.submitForm.emit).toHaveBeenCalled(); }); it('should not emit submit Event on form submit whether the form is not valid', () => { diff --git a/src/app/shared/mocks/mock-item.ts b/src/app/shared/mocks/mock-item.ts index f3db69a0f2..2e5c764ee2 100644 --- a/src/app/shared/mocks/mock-item.ts +++ b/src/app/shared/mocks/mock-item.ts @@ -51,7 +51,6 @@ export const MockItem: Item = Object.assign(new Item(), { id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713', type: 'bitstream', - name: 'test_word.docx', metadata: [ { key: 'dc.title', @@ -86,7 +85,6 @@ export const MockItem: Item = Object.assign(new Item(), { id: '99b00f3c-1cc6-4689-8158-91965bee6b28', uuid: '99b00f3c-1cc6-4689-8158-91965bee6b28', type: 'bitstream', - name: 'test_pdf.pdf', metadata: [ { key: 'dc.title', @@ -102,7 +100,6 @@ export const MockItem: Item = Object.assign(new Item(), { id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', type: 'item', - name: 'Test PowerPoint Document', metadata: [ { key: 'dc.creator', diff --git a/src/app/shared/search-form/search-form.component.spec.ts b/src/app/shared/search-form/search-form.component.spec.ts index 30f5801cc2..004d0c5b21 100644 --- a/src/app/shared/search-form/search-form.component.spec.ts +++ b/src/app/shared/search-form/search-form.component.spec.ts @@ -121,7 +121,6 @@ export const objects: DSpaceObject[] = [ id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', type: ResourceType.Community, - name: 'OR2017 - Demonstration', metadata: [ { key: 'dc.description', @@ -171,7 +170,6 @@ export const objects: DSpaceObject[] = [ id: '9076bd16-e69a-48d6-9e41-0238cb40d863', uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', type: ResourceType.Community, - name: 'Sample Community', metadata: [ { key: 'dc.description', diff --git a/src/app/shared/testing/eperson-mock.ts b/src/app/shared/testing/eperson-mock.ts index f163a490b9..ef27f4983d 100644 --- a/src/app/shared/testing/eperson-mock.ts +++ b/src/app/shared/testing/eperson-mock.ts @@ -13,8 +13,12 @@ export const EPersonMock: EPerson = Object.assign(new EPerson(),{ id: 'testid', uuid: 'testid', type: 'eperson', - name: 'User Test', metadata: [ + { + key: 'dc.title', + language: null, + value: 'User Test' + }, { key: 'eperson.firstname', language: null, From a35dffbe95a81283e9166868f5f3eee5c4a6274c Mon Sep 17 00:00:00 2001 From: lotte Date: Sun, 23 Dec 2018 00:07:24 +0100 Subject: [PATCH 41/57] refactoring comcol forms --- resources/i18n/en.json | 25 ++-- .../collection-form.component.html | 35 ----- .../collection-form.component.scss | 7 - .../collection-form.component.ts | 87 +++++++------ .../create-collection-page.component.html | 8 +- .../create-collection-page.component.ts | 60 ++++----- .../edit-collection-page.component.html | 8 ++ .../edit-community-page.component.scss | 1 + .../edit-community-page.component.spec.ts | 121 ++++++++++++++++++ .../edit-community-page.component.ts | 45 +++++++ .../community-form.component.scss | 7 - .../community-form.component.ts | 73 +---------- .../create-community-page.component.ts | 47 +------ .../comcol-form/comcol-form.component.html} | 4 +- .../comcol-form/comcol-form.component.ts | 77 +++++++++++ .../create-comcol-page.component.ts | 55 ++++++++ 16 files changed, 420 insertions(+), 240 deletions(-) delete mode 100644 src/app/+collection-page/collection-form/collection-form.component.html delete mode 100644 src/app/+collection-page/collection-form/collection-form.component.scss create mode 100644 src/app/+collection-page/edit-collection-page/edit-collection-page.component.html create mode 100644 src/app/+collection-page/edit-collection-page/edit-community-page.component.scss create mode 100644 src/app/+collection-page/edit-collection-page/edit-community-page.component.spec.ts create mode 100644 src/app/+collection-page/edit-collection-page/edit-community-page.component.ts delete mode 100644 src/app/+community-page/community-form/community-form.component.scss rename src/app/{+community-page/community-form/community-form.component.html => comcol-forms/comcol-form/comcol-form.component.html} (66%) create mode 100644 src/app/comcol-forms/comcol-form/comcol-form.component.ts create mode 100644 src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 46d7617f6c..1f85b8ddac 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -14,20 +14,23 @@ } } }, - "edit": { - "name": "Name", - "description": "Short Description", - "introductory": "Introductory text (HTML)", - "copyright": "Copyright text (HTML)", - "news": "News (HTML)", + "form": { + "title": "Name", + "description": "Introductory text (HTML)", + "abstract": "Short Description", + "rights": "Copyright text (HTML)", + "tableofcontents": "News (HTML)", "license": "License", "provenance": "Provenance", - "submit": "Submit", - "cancel": "Cancel", - "required": { - "name": "Please enter a collection name" + "errors": { + "title": { + "required": "Please enter a collection name" + } } }, + "edit": { + "head": "Edit Collection" + }, "create": { "head": "Create a Collection", "sub-head": "Create a Collection for Community {{ parent }}" @@ -57,7 +60,7 @@ } }, "edit": { - "head": "Edit collection" + "head": "Edit Community" }, "create": { "head": "Create a Community", diff --git a/src/app/+collection-page/collection-form/collection-form.component.html b/src/app/+collection-page/collection-form/collection-form.component.html deleted file mode 100644 index 85135af15d..0000000000 --- a/src/app/+collection-page/collection-form/collection-form.component.html +++ /dev/null @@ -1,35 +0,0 @@ -
-
- - -
{{ 'collection.edit.required.name' | translate }}
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
diff --git a/src/app/+collection-page/collection-form/collection-form.component.scss b/src/app/+collection-page/collection-form/collection-form.component.scss deleted file mode 100644 index d5811186e7..0000000000 --- a/src/app/+collection-page/collection-form/collection-form.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import '../../../styles/variables.scss'; - -// temporary fix for bootstrap 4 beta btn color issue -.btn-secondary { - background-color: $input-bg; - color: $input-color; -} diff --git a/src/app/+collection-page/collection-form/collection-form.component.ts b/src/app/+collection-page/collection-form/collection-form.component.ts index 4238eb3811..03bd9bfda9 100644 --- a/src/app/+collection-page/collection-form/collection-form.component.ts +++ b/src/app/+collection-page/collection-form/collection-form.component.ts @@ -1,41 +1,56 @@ -import { Component, EventEmitter, Output } from '@angular/core'; -import { isNotEmpty } from '../../shared/empty.util'; -import { Location } from '@angular/common'; +import { Component, Input } from '@angular/core'; +import { + DynamicInputModel, + DynamicTextAreaModel +} from '@ng-dynamic-forms/core'; +import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; +import { ResourceType } from '../../core/shared/resource-type'; +import { Collection } from '../../core/shared/collection.model'; +import { ComColFormComponent } from '../../comcol-forms/comcol-form/comcol-form.component'; @Component({ selector: 'ds-collection-form', - styleUrls: ['./collection-form.component.scss'], - templateUrl: './collection-form.component.html' + styleUrls: ['../../comcol-forms/comcol-form.component.scss'], + templateUrl: '../../comcol-forms/comcol-form/comcol-form.component.html' }) -export class CollectionFormComponent { - - name: string; - description: string; - introductory: string; - copyright: string; - news: string; - license: string; - provenance: string; - - nameRequiredError = false; - - @Output() submitted: EventEmitter = new EventEmitter(); - - public constructor(private location: Location) { - - } - - onSubmit(data: any) { - if (isNotEmpty(data.name)) { - this.submitted.emit(data); - this.nameRequiredError = false; - } else { - this.nameRequiredError = true; - } - } - - cancel() { - this.location.back(); - } - +export class CollectionFormComponent extends ComColFormComponent { + @Input() dso: Collection = new Collection(); + type = ResourceType.Collection; + formModel: DynamicFormControlModel[] = [ + new DynamicInputModel({ + id: 'title', + name: 'dc.title', + required: true, + validators: { + required: null + }, + errorMessages: { + required: 'Please enter a name for this title' + }, + }), + new DynamicTextAreaModel({ + id: 'description', + name: 'dc.description', + }), + new DynamicTextAreaModel({ + id: 'abstract', + name: 'dc.description.abstract', + }), + new DynamicTextAreaModel({ + id: 'rights', + name: 'dc.rights', + }), + new DynamicTextAreaModel({ + id: 'tableofcontents', + name: 'dc.description.tableofcontents', + }), + new DynamicTextAreaModel({ + id: 'license', + name: 'dc.rights.license', + }), + new DynamicTextAreaModel({ + id: 'provenance', + name: 'dc.description.provenance', + }), + ]; } diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.html b/src/app/+collection-page/create-collection-page/create-collection-page.component.html index 1d1f8deb6c..aa9569b6b8 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.html +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.html @@ -1,11 +1,11 @@
- - -

{{ 'collection.create.sub-head' | translate:{ parent: community.name } }}

+ + +

{{ 'collection.create.sub-head' | translate:{ parent: parent.name } }}

- +
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 1bb4c31596..6ece21c388 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,58 +1,54 @@ -import { Component } from '@angular/core'; -import { Community } from '../../core/shared/community.model'; -import { CommunityDataService } from '../../core/data/community-data.service'; -import { CollectionDataService } from '../../core/data/collection-data.service'; -import { Collection } from '../../core/shared/collection.model'; +import { Component, OnInit } from '@angular/core'; +import {Collection} from '../../core/shared/collection.model'; +import {CollectionDataService} from '../../core/data/collection-data.service'; +import { Observable } from 'rxjs'; import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; -import { Observable } from 'rxjs'; -import { take } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; -import { isNotEmpty } from '../../shared/empty.util'; -import { ResourceType } from '../../core/shared/resource-type'; +import { isNotEmpty, isNotUndefined } from '../../shared/empty.util'; +import { take } from 'rxjs/operators'; +import { getSucceededRemoteData } from '../../core/shared/operators'; +import {Community} from '../../core/shared/community.model'; +import {CommunityDataService} from '../../core/data/community-data.service'; @Component({ selector: 'ds-create-collection', styleUrls: ['./create-collection-page.component.scss'], templateUrl: './create-collection-page.component.html' }) -export class CreateCollectionPageComponent { +export class CreateCommunityPageComponent implements OnInit { public parentUUID$: Observable; - public communityRDObs: Observable>; + public parentRD$: Observable>; public constructor( - private collectionDataService: CollectionDataService, private communityDataService: CommunityDataService, + private collectionDataService: CollectionDataService, private routeService: RouteService, private router: Router ) { + + } + + ngOnInit(): void { this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); - this.parentUUID$.subscribe((uuid: string) => { - if (isNotEmpty(uuid)) { - this.communityRDObs = this.communityDataService.findById(uuid); + this.parentUUID$.subscribe((parentID: string) => { + if (isNotEmpty(parentID)) { + this.parentRD$ = this.communityDataService.findById(parentID); } }); } - onSubmit(data: any) { + onSubmit(collection: Collection) { this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { - const collection = Object.assign(new Collection(), { - name: data.name, - metadata: [ - { key: 'dc.description', value: data.introductory }, - { key: 'dc.description.abstract', value: data.description }, - { key: 'dc.rights', value: data.copyright }, - { key: 'dc.rights.license', value: data.license } - // TODO: metadata for news and provenance - ], - type: ResourceType.Collection - }); - this.collectionDataService.create(collection, uuid).subscribe((rd: RemoteData) => { - if (rd.hasSucceeded) { - this.router.navigate(['collections', rd.payload.id]); - } - }); + this.collectionDataService.create(collection, uuid) + .pipe(getSucceededRemoteData()) + .subscribe((collectionRD: RemoteData) => { + if (isNotUndefined(collectionRD)) { + const newUUID = collectionRD.payload.uuid; + this.router.navigate(['/collections/' + newUUID]); + } + }); }); } diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html new file mode 100644 index 0000000000..1dc6442307 --- /dev/null +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html @@ -0,0 +1,8 @@ +
+
+
+ +
+
+ +
diff --git a/src/app/+collection-page/edit-collection-page/edit-community-page.component.scss b/src/app/+collection-page/edit-collection-page/edit-community-page.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/+collection-page/edit-collection-page/edit-community-page.component.scss @@ -0,0 +1 @@ + diff --git a/src/app/+collection-page/edit-collection-page/edit-community-page.component.spec.ts b/src/app/+collection-page/edit-collection-page/edit-community-page.component.spec.ts new file mode 100644 index 0000000000..84edd47e1d --- /dev/null +++ b/src/app/+collection-page/edit-collection-page/edit-community-page.component.spec.ts @@ -0,0 +1,121 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { RouteService } from '../../shared/services/route.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; +import { RemoteData } from '../../core/data/remote-data'; +import { Community } from '../../core/shared/community.model'; +import { SharedModule } from '../../shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { EditCommunityPageComponent } from './edit-community-page.component'; +import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; + +fdescribe('EditCommunityPageComponent', () => { + let comp: EditCommunityPageComponent; + let fixture: ComponentFixture; + let communityDataService: CommunityDataService; + let routeService: RouteService; + let router: Router; + + let community; + let newCommunity; + let communityDataServiceStub; + let routeServiceStub; + let routerStub; + let routeStub; + + function initializeVars() { + community = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66b9300d347', + metadata: [{ + key: 'dc.title', + value: 'test community' + }] + }); + + newCommunity = Object.assign(new Community(), { + uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', + metadata: [{ + key: 'dc.title', + value: 'new community' + }] + }); + + communityDataServiceStub = { + findById: (uuid) => observableOf(new RemoteData(false, false, true, null, Object.assign(new Community(), { + uuid: uuid, + metadata: [{ + key: 'dc.title', + value: community.name + }] + }))), + update: (com, uuid?) => observableOf(new RemoteData(false, false, true, undefined, newCommunity)) + + }; + + routeServiceStub = { + getQueryParameterValue: (param) => observableOf(community.uuid) + }; + routerStub = { + navigate: (commands) => commands + }; + + routeStub = { + data: observableOf(community) + }; + + } + + beforeEach(async(() => { + initializeVars(); + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [EditCommunityPageComponent], + providers: [ + { provide: CommunityDataService, useValue: communityDataServiceStub }, + { provide: RouteService, useValue: routeServiceStub }, + { provide: Router, useValue: routerStub }, + { provide: ActivatedRoute, useValue: routeStub }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditCommunityPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + communityDataService = (comp as any).communityDataService; + routeService = (comp as any).routeService; + router = (comp as any).router; + }); + + describe('onSubmit', () => { + let data; + beforeEach(() => { + data = Object.assign(new Community(), { + metadata: [{ + key: 'dc.title', + value: 'test' + }] + }); + }); + it('should navigate when successful', () => { + spyOn(router, 'navigate'); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should not navigate on failure', () => { + spyOn(router, 'navigate'); + spyOn(communityDataService, 'update').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, newCommunity))); + comp.onSubmit(data); + fixture.detectChanges(); + expect(router.navigate).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/src/app/+collection-page/edit-collection-page/edit-community-page.component.ts b/src/app/+collection-page/edit-collection-page/edit-community-page.component.ts new file mode 100644 index 0000000000..236aaff05a --- /dev/null +++ b/src/app/+collection-page/edit-collection-page/edit-community-page.component.ts @@ -0,0 +1,45 @@ +import { Component } 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 '../../shared/services/route.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RemoteData } from '../../core/data/remote-data'; +import { isNotUndefined } from '../../shared/empty.util'; +import { first, map } from 'rxjs/operators'; +import { getSucceededRemoteData } from '../../core/shared/operators'; + +@Component({ + selector: 'ds-edit-community', + styleUrls: ['./edit-community-page.component.scss'], + templateUrl: './edit-collection-page.component.html' +}) +export class EditCommunityPageComponent { + + public parentUUID$: Observable; + public parentRD$: Observable>; + public communityRD$: Observable>; + + public constructor( + private communityDataService: CommunityDataService, + private routeService: RouteService, + private router: Router, + private route: ActivatedRoute + ) { + } + + ngOnInit(): void { + this.communityRD$ = this.route.data.pipe(first(), map((data) => data.community)); + } + + onSubmit(community: Community) { + this.communityDataService.update(community) + .pipe(getSucceededRemoteData()) + .subscribe((communityRD: RemoteData) => { + if (isNotUndefined(communityRD)) { + const newUUID = communityRD.payload.uuid; + this.router.navigate(['/communities/' + newUUID]); + } + }); + } +} diff --git a/src/app/+community-page/community-form/community-form.component.scss b/src/app/+community-page/community-form/community-form.component.scss deleted file mode 100644 index d5811186e7..0000000000 --- a/src/app/+community-page/community-form/community-form.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import '../../../styles/variables.scss'; - -// temporary fix for bootstrap 4 beta btn color issue -.btn-secondary { - background-color: $input-bg; - color: $input-color; -} diff --git a/src/app/+community-page/community-form/community-form.component.ts b/src/app/+community-page/community-form/community-form.component.ts index c470cbbf91..8dbd2d6490 100644 --- a/src/app/+community-page/community-form/community-form.component.ts +++ b/src/app/+community-page/community-form/community-form.component.ts @@ -1,27 +1,21 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Location } from '@angular/common'; +import { Component, Input, OnInit, Output } from '@angular/core'; import { - DynamicFormService, DynamicInputModel, DynamicTextAreaModel } from '@ng-dynamic-forms/core'; -import { FormGroup } from '@angular/forms'; import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; import { Community } from '../../core/shared/community.model'; import { ResourceType } from '../../core/shared/resource-type'; -import { isNotEmpty } from '../../shared/empty.util'; -import { TranslateService } from '@ngx-translate/core'; - -const LABEL_KEY_PREFIX = 'community.form.'; -const ERROR_KEY_PREFIX = 'community.form.errors.'; +import { ComColFormComponent } from '../../comcol-forms/comcol-form/comcol-form.component'; @Component({ selector: 'ds-community-form', - styleUrls: ['./community-form.component.scss'], - templateUrl: './community-form.component.html' + styleUrls: ['../../comcol-forms/comcol-form.component.scss'], + templateUrl: '../../comcol-forms/comcol-form/comcol-form.component.html' }) -export class CommunityFormComponent implements OnInit { - @Input() community: Community = new Community(); +export class CommunityFormComponent extends ComColFormComponent { + @Input() dso: Community = new Community(); + type = ResourceType.Community; formModel: DynamicFormControlModel[] = [ new DynamicInputModel({ id: 'title', @@ -51,57 +45,4 @@ export class CommunityFormComponent implements OnInit { name: 'dc.description.tableofcontents', }), ]; - - formGroup: FormGroup; - - @Output() submitForm: EventEmitter = new EventEmitter(); - - public constructor(private location: Location, - private formService: DynamicFormService, - private translate: TranslateService) { - } - - ngOnInit(): void { - this.formModel.forEach( - (fieldModel: DynamicInputModel) => { - fieldModel.value = this.community.findMetadata(fieldModel.name); - } - ); - this.formGroup = this.formService.createFormGroup(this.formModel); - this.updateFieldTranslations(); - this.translate.onLangChange - .subscribe(() => { - this.updateFieldTranslations(); - }); - } - - onSubmit() { - const metadata = this.formModel.map( - (fieldModel: DynamicInputModel) => { - return { key: fieldModel.name, value: fieldModel.value } - } - ); - const filteredOldMetadata = this.community.metadata.filter((filter) => !metadata.map((md) => md.key).includes(filter.key)); - const filteredNewMetadata = metadata.filter((md) => isNotEmpty(md.value)); - const newMetadata = [...filteredOldMetadata, ...filteredNewMetadata]; - const updatedCommunity = Object.assign({}, this.community, { - metadata: newMetadata, - type: ResourceType.Community - }); - this.submitForm.emit(updatedCommunity); - } - - private updateFieldTranslations() { - this.formModel.forEach( - (fieldModel: DynamicInputModel) => { - fieldModel.label = this.translate.instant(LABEL_KEY_PREFIX + fieldModel.id); - if (isNotEmpty(fieldModel.validators)) { - fieldModel.errorMessages = {}; - Object.keys(fieldModel.validators).forEach((key) => { - fieldModel.errorMessages[key] = this.translate.instant(ERROR_KEY_PREFIX + fieldModel.id + '.' + key); - }); - } - } - ); - } } 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 8871d33547..98156f5a8a 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,54 +1,21 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } 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 '../../shared/services/route.service'; import { Router } from '@angular/router'; -import { RemoteData } from '../../core/data/remote-data'; -import { isNotEmpty, isNotUndefined } from '../../shared/empty.util'; -import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { map, take } from 'rxjs/operators'; -import { getSucceededRemoteData } from '../../core/shared/operators'; +import { CreateComColPageComponent } from '../../comcol-forms/create-comcol-page/create-comcol-page.component'; @Component({ selector: 'ds-create-community', styleUrls: ['./create-community-page.component.scss'], templateUrl: './create-community-page.component.html' }) -export class CreateCommunityPageComponent implements OnInit { - - public parentUUID$: Observable; - public parentRD$: Observable>; - +export class CreateCommunityPageComponent extends CreateComColPageComponent { public constructor( - private communityDataService: CommunityDataService, - private routeService: RouteService, - private router: Router + protected communityDataService: CommunityDataService, + protected routeService: RouteService, + protected router: Router ) { - + super(communityDataService, communityDataService, routeService, router); } - - ngOnInit(): void { - this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); - this.parentUUID$.subscribe((parentID: string) => { - if (isNotEmpty(parentID)) { - this.parentRD$ = this.communityDataService.findById(parentID); - } - }); - } - - onSubmit(community: Community) { - this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { - this.communityDataService.create(community, uuid) - .pipe(getSucceededRemoteData()) - .subscribe((communityRD: RemoteData) => { - if (isNotUndefined(communityRD)) { - const newUUID = communityRD.payload.uuid; - this.router.navigate(['/communities/' + newUUID]); - } - }); - }); - } - - } diff --git a/src/app/+community-page/community-form/community-form.component.html b/src/app/comcol-forms/comcol-form/comcol-form.component.html similarity index 66% rename from src/app/+community-page/community-form/community-form.component.html rename to src/app/comcol-forms/comcol-form/comcol-form.component.html index c47f077022..720ad0c1cf 100644 --- a/src/app/+community-page/community-form/community-form.component.html +++ b/src/app/comcol-forms/comcol-form/comcol-form.component.html @@ -1,3 +1,3 @@ \ No newline at end of file + [formId]="'comcol-form-id'" + [formModel]="formModel" (submitForm)="onSubmit()"> diff --git a/src/app/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/comcol-forms/comcol-form/comcol-form.component.ts new file mode 100644 index 0000000000..d49e197f5d --- /dev/null +++ b/src/app/comcol-forms/comcol-form/comcol-form.component.ts @@ -0,0 +1,77 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Location } from '@angular/common'; +import { + DynamicFormService, + DynamicInputModel +} from '@ng-dynamic-forms/core'; +import { FormGroup } from '@angular/forms'; +import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; +import { TranslateService } from '@ngx-translate/core'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { isNotEmpty } from '../../shared/empty.util'; +import { ResourceType } from '../../core/shared/resource-type'; + +@Component({ + selector: 'ds-comcol-form', + // styleUrls: ['./comcol-form.component.scss'], + templateUrl: './comcol-form.component.html' +}) +export class ComColFormComponent implements OnInit { + @Input() dso: T; + type; + LABEL_KEY_PREFIX = this.type + '.form.'; + ERROR_KEY_PREFIX = this.type + '.form.errors.'; + formModel: DynamicFormControlModel[]; + formGroup: FormGroup; + + @Output() submitForm: EventEmitter = new EventEmitter(); + + public constructor(private location: Location, + private formService: DynamicFormService, + private translate: TranslateService) { + } + + ngOnInit(): void { + this.formModel.forEach( + (fieldModel: DynamicInputModel) => { + fieldModel.value = this.dso.findMetadata(fieldModel.name); + } + ); + this.formGroup = this.formService.createFormGroup(this.formModel); + this.updateFieldTranslations(); + this.translate.onLangChange + .subscribe(() => { + this.updateFieldTranslations(); + }); + } + + onSubmit() { + const metadata = this.formModel.map( + (fieldModel: DynamicInputModel) => { + return { key: fieldModel.name, value: fieldModel.value } + } + ); + const filteredOldMetadata = this.dso.metadata.filter((filter) => !metadata.map((md) => md.key).includes(filter.key)); + const filteredNewMetadata = metadata.filter((md) => isNotEmpty(md.value)); + const newMetadata = [...filteredOldMetadata, ...filteredNewMetadata]; + const updatedDSO = Object.assign({}, this.dso, { + metadata: newMetadata, + type: ResourceType.Community + }); + this.submitForm.emit(updatedDSO); + } + + private updateFieldTranslations() { + this.formModel.forEach( + (fieldModel: DynamicInputModel) => { + fieldModel.label = this.translate.instant(this.LABEL_KEY_PREFIX + fieldModel.id); + if (isNotEmpty(fieldModel.validators)) { + fieldModel.errorMessages = {}; + Object.keys(fieldModel.validators).forEach((key) => { + fieldModel.errorMessages[key] = this.translate.instant(this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key); + }); + } + } + ); + } +} diff --git a/src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts b/src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts new file mode 100644 index 0000000000..e36f45ad18 --- /dev/null +++ b/src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts @@ -0,0 +1,55 @@ +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 '../../shared/services/route.service'; +import { Router } from '@angular/router'; +import { RemoteData } from '../../core/data/remote-data'; +import { isNotEmpty, isNotUndefined } from '../../shared/empty.util'; +import { take } from 'rxjs/operators'; +import { getSucceededRemoteData } from '../../core/shared/operators'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { DataService } from '../../core/data/data.service'; + +@Component({ + selector: 'ds-create-community', + styleUrls: ['./create-community-page.component.scss'], + templateUrl: './create-community-page.component.html' +}) +export class CreateComColPageComponent implements OnInit { + protected frontendURL: string; + public parentUUID$: Observable; + public parentRD$: Observable>; + + public constructor( + protected dsoDataService: DataService, + protected parentoDataService: CommunityDataService, + protected routeService: RouteService, + protected router: Router + ) { + + } + + ngOnInit(): void { + this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); + this.parentUUID$.subscribe((parentID: string) => { + if (isNotEmpty(parentID)) { + this.parentRD$ = this.parentoDataService.findById(parentID); + } + }); + } + + onSubmit(dso: T) { + this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { + this.dsoDataService.create(dso, uuid) + .pipe(getSucceededRemoteData()) + .subscribe((dsoRD: RemoteData) => { + if (isNotUndefined(dsoRD)) { + const newUUID = dsoRD.payload.uuid; + this.router.navigate([frontendURL + newUUID]); + } + }); + }); + } + +} From e0ed960d4c814fec1413f40648bfdc3b3d1052ee Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 2 Jan 2019 07:44:38 +0100 Subject: [PATCH 42/57] refactoring continued --- .../create-collection-page.component.ts | 61 +++++-------------- .../create-community-page.component.ts | 4 +- .../create-comcol-page.component.ts | 17 +++--- .../edit-comcol-page.component.ts | 45 ++++++++++++++ 4 files changed, 73 insertions(+), 54 deletions(-) create mode 100644 src/app/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts 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 6ece21c388..8dbc9183e5 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,55 +1,26 @@ -import { Component, OnInit } from '@angular/core'; -import {Collection} from '../../core/shared/collection.model'; -import {CollectionDataService} from '../../core/data/collection-data.service'; -import { Observable } from 'rxjs'; +import { Component } from '@angular/core'; +import { CommunityDataService } from '../../core/data/community-data.service'; import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; -import { RemoteData } from '../../core/data/remote-data'; -import { isNotEmpty, isNotUndefined } from '../../shared/empty.util'; -import { take } from 'rxjs/operators'; -import { getSucceededRemoteData } from '../../core/shared/operators'; -import {Community} from '../../core/shared/community.model'; -import {CommunityDataService} from '../../core/data/community-data.service'; +import { CreateComColPageComponent } from '../../comcol-forms/create-comcol-page/create-comcol-page.component'; +import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; +import { Collection } from '../../core/shared/collection.model'; +import { CollectionDataService } from '../../core/data/collection-data.service'; @Component({ - selector: 'ds-create-collection', - styleUrls: ['./create-collection-page.component.scss'], - templateUrl: './create-collection-page.component.html' + selector: 'ds-create-community', + styleUrls: ['./create-community-page.component.scss'], + templateUrl: './create-community-page.component.html' }) -export class CreateCommunityPageComponent implements OnInit { - - public parentUUID$: Observable; - public parentRD$: Observable>; +export class CreateCommunityPageComponent extends CreateComColPageComponent { + protected frontendURL = 'collections'; public constructor( - private communityDataService: CommunityDataService, - private collectionDataService: CollectionDataService, - private routeService: RouteService, - private router: Router + protected communityDataService: CommunityDataService, + protected collectionDataService: CollectionDataService, + protected routeService: RouteService, + protected router: Router ) { - + super(collectionDataService, communityDataService, routeService, router); } - - ngOnInit(): void { - this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); - this.parentUUID$.subscribe((parentID: string) => { - if (isNotEmpty(parentID)) { - this.parentRD$ = this.communityDataService.findById(parentID); - } - }); - } - - onSubmit(collection: Collection) { - this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { - this.collectionDataService.create(collection, uuid) - .pipe(getSucceededRemoteData()) - .subscribe((collectionRD: RemoteData) => { - if (isNotUndefined(collectionRD)) { - const newUUID = collectionRD.payload.uuid; - this.router.navigate(['/collections/' + newUUID]); - } - }); - }); - } - } 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 98156f5a8a..5a3cf1f4a8 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 @@ -4,13 +4,15 @@ import { CommunityDataService } from '../../core/data/community-data.service'; import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; import { CreateComColPageComponent } from '../../comcol-forms/create-comcol-page/create-comcol-page.component'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; @Component({ selector: 'ds-create-community', styleUrls: ['./create-community-page.component.scss'], templateUrl: './create-community-page.component.html' }) -export class CreateCommunityPageComponent extends CreateComColPageComponent { +export class CreateCommunityPageComponent extends CreateComColPageComponent { + protected frontendURL = 'communities'; public constructor( protected communityDataService: CommunityDataService, protected routeService: RouteService, diff --git a/src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts b/src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts index e36f45ad18..18f23cf528 100644 --- a/src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts +++ b/src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts @@ -10,20 +10,21 @@ import { take } from 'rxjs/operators'; import { getSucceededRemoteData } from '../../core/shared/operators'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DataService } from '../../core/data/data.service'; +import { NormalizedDSpaceObject } from '../../core/cache/models/normalized-dspace-object.model'; @Component({ selector: 'ds-create-community', styleUrls: ['./create-community-page.component.scss'], templateUrl: './create-community-page.component.html' }) -export class CreateComColPageComponent implements OnInit { +export class CreateComColPageComponent implements OnInit { protected frontendURL: string; public parentUUID$: Observable; - public parentRD$: Observable>; + public parentRD$: Observable>; public constructor( - protected dsoDataService: DataService, - protected parentoDataService: CommunityDataService, + protected dsoDataService: DataService, + protected parentDataService: CommunityDataService, protected routeService: RouteService, protected router: Router ) { @@ -34,19 +35,19 @@ export class CreateComColPageComponent implements OnInit this.parentUUID$ = this.routeService.getQueryParameterValue('parent'); this.parentUUID$.subscribe((parentID: string) => { if (isNotEmpty(parentID)) { - this.parentRD$ = this.parentoDataService.findById(parentID); + this.parentRD$ = this.parentDataService.findById(parentID); } }); } - onSubmit(dso: T) { + onSubmit(dso: TDomain) { this.parentUUID$.pipe(take(1)).subscribe((uuid: string) => { this.dsoDataService.create(dso, uuid) .pipe(getSucceededRemoteData()) - .subscribe((dsoRD: RemoteData) => { + .subscribe((dsoRD: RemoteData) => { if (isNotUndefined(dsoRD)) { const newUUID = dsoRD.payload.uuid; - this.router.navigate([frontendURL + newUUID]); + this.router.navigate([this.frontendURL + newUUID]); } }); }); diff --git a/src/app/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts b/src/app/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts new file mode 100644 index 0000000000..5e11a71c2e --- /dev/null +++ b/src/app/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts @@ -0,0 +1,45 @@ +import {Component, OnInit} from '@angular/core'; +import { Community } from '../../core/shared/community.model'; +import { Observable } from 'rxjs'; +import { RouteService } from '../../shared/services/route.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RemoteData } from '../../core/data/remote-data'; +import { isNotUndefined } from '../../shared/empty.util'; +import { first, map } from 'rxjs/operators'; +import { getSucceededRemoteData } from '../../core/shared/operators'; +import { DataService } from '../../core/data/data.service'; +import { NormalizedDSpaceObject } from '../../core/cache/models/normalized-dspace-object.model'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; + +@Component({ + selector: 'ds-edit-community', + styleUrls: ['./edit-community-page.component.scss'], + templateUrl: './edit-community-page.component.html' +}) +export class EditComColPageComponent implements OnInit { + protected frontendURL: string; + public dsoRD$: Observable>; + + public constructor( + protected dsoDataService: DataService, + private routeService: RouteService, + private router: Router, + private route: ActivatedRoute + ) { + } + + ngOnInit(): void { + this.dsoRD$ = this.route.data.pipe(first(), map((data) => data.dso)); + } + + onSubmit(dso: TDomain) { + this.dsoDataService.update(dso) + .pipe(getSucceededRemoteData()) + .subscribe((dsoRD: RemoteData) => { + if (isNotUndefined(dsoRD)) { + const newUUID = dsoRD.payload.uuid; + this.router.navigate([this.frontendURL + newUUID]); + } + }); + } +} From 1ebd6f0e86a2f0e06edf43c7f90fc6e391411b5e Mon Sep 17 00:00:00 2001 From: lotte Date: Wed, 2 Jan 2019 16:51:41 +0100 Subject: [PATCH 43/57] Finished refactoring, guards, docs, tests --- .../collection-form.component.spec.ts | 73 ----------- .../collection-form.component.ts | 23 +++- .../collection-page-routing.module.ts | 18 ++- .../collection-page.component.ts | 3 +- .../collection-page.module.ts | 2 + .../create-collection-page.component.html | 13 +- .../create-collection-page.component.spec.ts | 108 ---------------- .../create-collection-page.component.ts | 15 ++- .../create-collection-page.guard.spec.ts | 67 ++++++++++ .../create-collection-page.guard.ts | 46 +++++++ .../edit-collection-page.component.html | 4 +- ...ss => edit-collection-page.component.scss} | 0 .../edit-collection-page.component.ts | 28 ++++ .../edit-community-page.component.spec.ts | 121 ------------------ .../edit-community-page.component.ts | 45 ------- .../community-form.component.ts | 30 +++-- .../community-page-routing.module.ts | 12 +- .../community-page.component.html | 1 - .../create-community-page.component.html | 2 +- .../create-community-page.component.ts | 8 +- .../create-community-page.guard.spec.ts | 67 ++++++++++ .../create-community-page.guard.ts | 46 +++++++ .../edit-community-page.component.html | 2 +- .../edit-community-page.component.ts | 41 ++---- .../edit-comcol-page.component.ts | 45 ------- .../config-response-parsing.service.spec.ts | 8 +- src/app/core/data/data.service.spec.ts | 30 +++-- src/app/core/data/update-comparator.ts | 2 +- .../core/metadata/metadata.service.spec.ts | 4 + src/app/core/metadata/metadata.service.ts | 6 +- src/app/core/shared/operators.ts | 4 + .../comcol-form/comcol-form.component.html | 0 .../comcol-form/comcol-form.component.scss} | 0 .../comcol-form.component.spec.ts} | 59 +++++---- .../comcol-form/comcol-form.component.ts | 53 ++++++-- .../create-comcol-page.component.spec.ts} | 29 +++-- .../create-comcol-page.component.ts | 43 +++++-- .../edit-comcol-page.component.spec.ts} | 31 +++-- .../edit-comcol-page.component.ts | 56 ++++++++ src/app/shared/shared.module.ts | 6 + src/app/shared/testing/utils.ts | 2 +- 41 files changed, 600 insertions(+), 553 deletions(-) delete mode 100644 src/app/+collection-page/collection-form/collection-form.component.spec.ts delete mode 100644 src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts create mode 100644 src/app/+collection-page/create-collection-page/create-collection-page.guard.spec.ts create mode 100644 src/app/+collection-page/create-collection-page/create-collection-page.guard.ts rename src/app/+collection-page/edit-collection-page/{edit-community-page.component.scss => edit-collection-page.component.scss} (100%) create mode 100644 src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts delete mode 100644 src/app/+collection-page/edit-collection-page/edit-community-page.component.spec.ts delete mode 100644 src/app/+collection-page/edit-collection-page/edit-community-page.component.ts create mode 100644 src/app/+community-page/create-community-page/create-community-page.guard.spec.ts create mode 100644 src/app/+community-page/create-community-page/create-community-page.guard.ts delete mode 100644 src/app/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts rename src/app/{ => shared}/comcol-forms/comcol-form/comcol-form.component.html (100%) rename src/app/shared/{mocks/mock-response-cache.service.ts => comcol-forms/comcol-form/comcol-form.component.scss} (100%) rename src/app/{+community-page/community-form/community-form.component.spec.ts => shared/comcol-forms/comcol-form/comcol-form.component.spec.ts} (58%) rename src/app/{ => shared}/comcol-forms/comcol-form/comcol-form.component.ts (65%) rename src/app/{+community-page/create-community-page/create-community-page.component.spec.ts => shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts} (70%) rename src/app/{ => shared}/comcol-forms/create-comcol-page/create-comcol-page.component.ts (54%) rename src/app/{+community-page/edit-community-page/edit-community-page.component.spec.ts => shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.spec.ts} (71%) create mode 100644 src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts diff --git a/src/app/+collection-page/collection-form/collection-form.component.spec.ts b/src/app/+collection-page/collection-form/collection-form.component.spec.ts deleted file mode 100644 index 71ae92572d..0000000000 --- a/src/app/+collection-page/collection-form/collection-form.component.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { SharedModule } from '../../shared/shared.module'; -import { TranslateModule } from '@ngx-translate/core'; -import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; -import { By } from '@angular/platform-browser'; -import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; -import { CollectionFormComponent } from './collection-form.component'; -import { Location } from '@angular/common'; -import { DynamicFormService } from '@ng-dynamic-forms/core'; - -describe('CommunityFormComponent', () => { - let comp: CollectionFormComponent; - let fixture: ComponentFixture - let location: Location; - - /* tslint:disable:no-empty */ - const locationStub = { - back: () => {} - }; - /* tslint:enable:no-empty */ - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], - declarations: [CollectionFormComponent], - providers: [ - { provide: Location, useValue: locationStub }, - ], - schemas: [NO_ERRORS_SCHEMA] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CollectionFormComponent); - comp = fixture.componentInstance; - fixture.detectChanges(); - location = (comp as any).location; - }); - - describe('when submitting', () => { - let input: DebugElement; - let submit: DebugElement; - let cancel: DebugElement; - let error: DebugElement; - - beforeEach(() => { - input = fixture.debugElement.query(By.css('input#collection-name')); - submit = fixture.debugElement.query(By.css('button#collection-submit')); - cancel = fixture.debugElement.query(By.css('button#collection-cancel')); - error = fixture.debugElement.query(By.css('div.invalid-feedback')); - }); - - it('should display an error when leaving name empty', () => { - const el = input.nativeElement; - - el.value = ''; - el.dispatchEvent(new Event('input')); - submit.nativeElement.click(); - fixture.detectChanges(); - - expect(error.nativeElement.style.display).not.toEqual('none'); - }); - - it('should navigate back when pressing cancel', () => { - spyOn(location, 'back'); - cancel.nativeElement.click(); - fixture.detectChanges(); - - expect(location.back).toHaveBeenCalled(); - }); - }) -}); diff --git a/src/app/+collection-page/collection-form/collection-form.component.ts b/src/app/+collection-page/collection-form/collection-form.component.ts index 03bd9bfda9..22f2f1271d 100644 --- a/src/app/+collection-page/collection-form/collection-form.component.ts +++ b/src/app/+collection-page/collection-form/collection-form.component.ts @@ -6,16 +6,31 @@ import { import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; import { ResourceType } from '../../core/shared/resource-type'; import { Collection } from '../../core/shared/collection.model'; -import { ComColFormComponent } from '../../comcol-forms/comcol-form/comcol-form.component'; +import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component'; +/** + * Form used for creating and editing collections + */ @Component({ selector: 'ds-collection-form', - styleUrls: ['../../comcol-forms/comcol-form.component.scss'], - templateUrl: '../../comcol-forms/comcol-form/comcol-form.component.html' + styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'], + templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html' }) export class CollectionFormComponent extends ComColFormComponent { + /** + * @type {Collection} A new collection when a collection is being created, an existing Input collection when a collection is being edited + */ @Input() dso: Collection = new Collection(); - type = ResourceType.Collection; + + /** + * @type {ResourceType.Collection} This is a collection-type form + */ + protected type = ResourceType.Collection; + + /** + * The dynamic form fields used for creating/editing a collection + * @type {(DynamicInputModel | DynamicTextAreaModel)[]} + */ formModel: DynamicFormControlModel[] = [ new DynamicInputModel({ id: 'title', diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index 0b1245578c..ec53796e61 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -5,13 +5,26 @@ import { CollectionPageComponent } from './collection-page.component'; import { CollectionPageResolver } from './collection-page.resolver'; import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; +import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component'; +import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard'; @NgModule({ imports: [ RouterModule.forChild([ - { path: 'create', + { + path: 'create', component: CreateCollectionPageComponent, - canActivate: [AuthenticatedGuard] }, + canActivate: [AuthenticatedGuard, CreateCollectionPageGuard] + }, + { + path: ':id/edit', + pathMatch: 'full', + component: EditCollectionPageComponent, + canActivate: [AuthenticatedGuard], + resolve: { + dso: CollectionPageResolver + } + }, { path: ':id', component: CollectionPageComponent, @@ -24,6 +37,7 @@ import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; ], providers: [ CollectionPageResolver, + CreateCollectionPageGuard ] }) export class CollectionPageRoutingModule { diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index b76c0a7520..7c4f2b92ac 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -55,7 +55,8 @@ export class CollectionPageComponent implements OnInit, OnDestroy { ngOnInit(): void { this.collectionRD$ = this.route.data.pipe( - map((data) => data.collection) + map((data) => data.collection), + tap((data) => this.collectionId = data.payload.id) ); this.logoRD$ = this.collectionRD$.pipe( map((rd: RemoteData) => rd.payload), diff --git a/src/app/+collection-page/collection-page.module.ts b/src/app/+collection-page/collection-page.module.ts index a6316e9189..63a03f4299 100644 --- a/src/app/+collection-page/collection-page.module.ts +++ b/src/app/+collection-page/collection-page.module.ts @@ -8,6 +8,7 @@ import { CollectionPageRoutingModule } from './collection-page-routing.module'; import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component'; import { CollectionFormComponent } from './collection-form/collection-form.component'; import { SearchPageModule } from '../+search-page/search-page.module'; +import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component'; @NgModule({ imports: [ @@ -19,6 +20,7 @@ import { SearchPageModule } from '../+search-page/search-page.module'; declarations: [ CollectionPageComponent, CreateCollectionPageComponent, + EditCollectionPageComponent, CollectionFormComponent ] }) diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.component.html b/src/app/+collection-page/create-collection-page/create-collection-page.component.html index aa9569b6b8..b3f4361bc6 100644 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.html +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.html @@ -1,11 +1,8 @@
-
-
- - -

{{ 'collection.create.sub-head' | translate:{ parent: parent.name } }}

-
+
+
+

{{'collection.create.sub-head' | translate:{ parent: (parentRD$| async)?.payload.name } }}

+
-
- +
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 deleted file mode 100644 index 837741d593..0000000000 --- a/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { SharedModule } from '../../shared/shared.module'; -import { Community } from '../../core/shared/community.model'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { CommonModule, Location } from '@angular/common'; -import { Router } from '@angular/router'; -import { TranslateModule } from '@ngx-translate/core'; -import { Observable } from 'rxjs'; -import { CommunityDataService } from '../../core/data/community-data.service'; -import { RouteService } from '../../shared/services/route.service'; -import { RemoteData } from '../../core/data/remote-data'; -import { CreateCollectionPageComponent } from './create-collection-page.component'; -import { CollectionDataService } from '../../core/data/collection-data.service'; -import { Collection } from '../../core/shared/collection.model'; -import { RouterTestingModule } from '@angular/router/testing'; -import { CollectionFormComponent } from '../collection-form/collection-form.component'; -import { of as observableOf } from 'rxjs'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; - -describe('CreateCollectionPageComponent', () => { - let comp: CreateCollectionPageComponent; - let fixture: ComponentFixture; - let collectionDataService: CollectionDataService; - let communityDataService: CommunityDataService; - let routeService: RouteService; - let router: Router; - - const community = Object.assign(new Community(), { - uuid: 'a20da287-e174-466a-9926-f66b9300d347', - metadata: [{ - key: 'dc.title', - value: 'test collection' - }] - }); - - const collection = Object.assign(new Collection(), { - uuid: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4', - metadata: [{ - key: 'dc.title', - value: 'new collection' - }] }); - - const collectionDataServiceStub = { - create: (col, uuid?) => observableOf(new RemoteData(false, false, true, undefined, collection)) - }; - const communityDataServiceStub = { - findById: (uuid) => observableOf(new RemoteData(false, false, true, null, Object.assign(new Community(), { - uuid: uuid, - metadata: [{ - key: 'dc.title', - value: community.name - }] - }))) - }; - const routeServiceStub = { - getQueryParameterValue: (param) => observableOf(community.uuid) - }; - const routerStub = { - navigate: (commands) => commands - }; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], - declarations: [CreateCollectionPageComponent], - providers: [ - { provide: CollectionDataService, useValue: collectionDataServiceStub }, - { provide: CommunityDataService, useValue: communityDataServiceStub }, - { provide: RouteService, useValue: routeServiceStub }, - { provide: Router, useValue: routerStub } - ], - schemas: [NO_ERRORS_SCHEMA] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CreateCollectionPageComponent); - comp = fixture.componentInstance; - fixture.detectChanges(); - collectionDataService = (comp as any).collectionDataService; - communityDataService = (comp as any).communityDataService; - routeService = (comp as any).routeService; - router = (comp as any).router; - }); - - describe('onSubmit', () => { - const data = { - metadata: [{ - key: 'dc.title', - value:'test' - }] - }; - - it('should navigate when successful', () => { - spyOn(router, 'navigate'); - comp.onSubmit(data); - fixture.detectChanges(); - expect(router.navigate).toHaveBeenCalled(); - }); - - it('should not navigate on failure', () => { - spyOn(router, 'navigate'); - spyOn(collectionDataService, 'create').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, collection))); - comp.onSubmit(data); - fixture.detectChanges(); - expect(router.navigate).not.toHaveBeenCalled(); - }); - }); -}); 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 8dbc9183e5..52497694b9 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 @@ -2,18 +2,21 @@ import { Component } from '@angular/core'; import { CommunityDataService } from '../../core/data/community-data.service'; import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; -import { CreateComColPageComponent } from '../../comcol-forms/create-comcol-page/create-comcol-page.component'; +import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component'; import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; import { Collection } from '../../core/shared/collection.model'; import { CollectionDataService } from '../../core/data/collection-data.service'; +/** + * Component that represents the page where a user can create a new Collection + */ @Component({ - selector: 'ds-create-community', - styleUrls: ['./create-community-page.component.scss'], - templateUrl: './create-community-page.component.html' + selector: 'ds-create-collection', + styleUrls: ['./create-collection-page.component.scss'], + templateUrl: './create-collection-page.component.html' }) -export class CreateCommunityPageComponent extends CreateComColPageComponent { - protected frontendURL = 'collections'; +export class CreateCollectionPageComponent extends CreateComColPageComponent { + protected frontendURL = '/collections/'; public constructor( protected communityDataService: CommunityDataService, diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.guard.spec.ts b/src/app/+collection-page/create-collection-page/create-collection-page.guard.spec.ts new file mode 100644 index 0000000000..5d21ae36b3 --- /dev/null +++ b/src/app/+collection-page/create-collection-page/create-collection-page.guard.spec.ts @@ -0,0 +1,67 @@ +import { CreateCollectionPageGuard } from './create-collection-page.guard'; +import { MockRouter } from '../../shared/mocks/mock-router'; +import { RemoteData } from '../../core/data/remote-data'; +import { Community } from '../../core/shared/community.model'; +import { of as observableOf } from 'rxjs'; +import { first } from 'rxjs/operators'; + +describe('CreateCollectionPageGuard', () => { + describe('canActivate', () => { + let guard: CreateCollectionPageGuard; + let router; + let communityDataServiceStub: any; + + beforeEach(() => { + communityDataServiceStub = { + findById: (id: string) => { + if (id === 'valid-id') { + return observableOf(new RemoteData(false, false, true, null, new Community())); + } else if (id === 'invalid-id') { + return observableOf(new RemoteData(false, false, true, null, undefined)); + } else if (id === 'error-id') { + return observableOf(new RemoteData(false, false, false, null, new Community())); + } + } + }; + router = new MockRouter(); + + guard = new CreateCollectionPageGuard(router, communityDataServiceStub); + }); + + it('should return true when the parent ID resolves to a community', () => { + guard.canActivate({ queryParams: { parent: 'valid-id' } } as any, undefined) + .pipe(first()) + .subscribe( + (canActivate) => + expect(canActivate).toEqual(true) + ); + }); + + it('should return false when no parent ID has been provided', () => { + guard.canActivate({ queryParams: { } } as any, undefined) + .pipe(first()) + .subscribe( + (canActivate) => + expect(canActivate).toEqual(false) + ); + }); + + it('should return false when the parent ID does not resolve to a community', () => { + guard.canActivate({ queryParams: { parent: 'invalid-id' } } as any, undefined) + .pipe(first()) + .subscribe( + (canActivate) => + expect(canActivate).toEqual(false) + ); + }); + + it('should return false when the parent ID resolves to an error response', () => { + guard.canActivate({ queryParams: { parent: 'error-id' } } as any, undefined) + .pipe(first()) + .subscribe( + (canActivate) => + expect(canActivate).toEqual(false) + ); + }); + }); +}); diff --git a/src/app/+collection-page/create-collection-page/create-collection-page.guard.ts b/src/app/+collection-page/create-collection-page/create-collection-page.guard.ts new file mode 100644 index 0000000000..4cd842e926 --- /dev/null +++ b/src/app/+collection-page/create-collection-page/create-collection-page.guard.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; + +import { hasNoValue, hasValue } from '../../shared/empty.util'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { Community } from '../../core/shared/community.model'; +import { getFinishedRemoteData } from '../../core/shared/operators'; +import { map, tap } from 'rxjs/operators'; +import { Observable, of as observableOf } from 'rxjs'; + +/** + * Prevent creation of a collection without a parent community provided + * @class CreateCollectionPageGuard + */ +@Injectable() +export class CreateCollectionPageGuard implements CanActivate { + public constructor(private router: Router, private communityService: CommunityDataService) { + } + + /** + * True when either a parent ID query parameter has been provided and the parent ID resolves to a valid parent community + * Reroutes to a 404 page when the page cannot be activated + * @method canActivate + */ + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + const parentID = route.queryParams.parent; + if (hasNoValue(parentID)) { + this.router.navigate(['/404']); + return observableOf(false); + } + const parent: Observable> = this.communityService.findById(parentID) + .pipe( + getFinishedRemoteData(), + ); + + return parent.pipe( + map((communityRD: RemoteData) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)), + tap((isValid: boolean) => { + if (!isValid) { + this.router.navigate(['/404']); + } + }) + ); + } +} diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html index 1dc6442307..1308af919f 100644 --- a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html @@ -1,8 +1,8 @@
- +
- +
diff --git a/src/app/+collection-page/edit-collection-page/edit-community-page.component.scss b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.scss similarity index 100% rename from src/app/+collection-page/edit-collection-page/edit-community-page.component.scss rename to src/app/+collection-page/edit-collection-page/edit-collection-page.component.scss diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts new file mode 100644 index 0000000000..2ffb4925a0 --- /dev/null +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts @@ -0,0 +1,28 @@ +import { Component } from '@angular/core'; +import { RouteService } from '../../shared/services/route.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component'; +import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; +import { Collection } from '../../core/shared/collection.model'; +import { CollectionDataService } from '../../core/data/collection-data.service'; + +/** + * Component that represents the page where a user can edit an existing Collection + */ +@Component({ + selector: 'ds-edit-collection', + styleUrls: ['./edit-collection-page.component.scss'], + templateUrl: './edit-collection-page.component.html' +}) +export class EditCollectionPageComponent extends EditComColPageComponent { + protected frontendURL = '/collections/'; + + public constructor( + protected collectionDataService: CollectionDataService, + protected routeService: RouteService, + protected router: Router, + protected route: ActivatedRoute + ) { + super(collectionDataService, routeService, router, route); + } +} diff --git a/src/app/+collection-page/edit-collection-page/edit-community-page.component.spec.ts b/src/app/+collection-page/edit-collection-page/edit-community-page.component.spec.ts deleted file mode 100644 index 84edd47e1d..0000000000 --- a/src/app/+collection-page/edit-collection-page/edit-community-page.component.spec.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { CommunityDataService } from '../../core/data/community-data.service'; -import { RouteService } from '../../shared/services/route.service'; -import { ActivatedRoute, Router } from '@angular/router'; -import { TranslateModule } from '@ngx-translate/core'; -import { of as observableOf } from 'rxjs'; -import { RemoteData } from '../../core/data/remote-data'; -import { Community } from '../../core/shared/community.model'; -import { SharedModule } from '../../shared/shared.module'; -import { CommonModule } from '@angular/common'; -import { RouterTestingModule } from '@angular/router/testing'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { EditCommunityPageComponent } from './edit-community-page.component'; -import { ActivatedRouteStub } from '../../shared/testing/active-router-stub'; - -fdescribe('EditCommunityPageComponent', () => { - let comp: EditCommunityPageComponent; - let fixture: ComponentFixture; - let communityDataService: CommunityDataService; - let routeService: RouteService; - let router: Router; - - let community; - let newCommunity; - let communityDataServiceStub; - let routeServiceStub; - let routerStub; - let routeStub; - - function initializeVars() { - community = Object.assign(new Community(), { - uuid: 'a20da287-e174-466a-9926-f66b9300d347', - metadata: [{ - key: 'dc.title', - value: 'test community' - }] - }); - - newCommunity = Object.assign(new Community(), { - uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', - metadata: [{ - key: 'dc.title', - value: 'new community' - }] - }); - - communityDataServiceStub = { - findById: (uuid) => observableOf(new RemoteData(false, false, true, null, Object.assign(new Community(), { - uuid: uuid, - metadata: [{ - key: 'dc.title', - value: community.name - }] - }))), - update: (com, uuid?) => observableOf(new RemoteData(false, false, true, undefined, newCommunity)) - - }; - - routeServiceStub = { - getQueryParameterValue: (param) => observableOf(community.uuid) - }; - routerStub = { - navigate: (commands) => commands - }; - - routeStub = { - data: observableOf(community) - }; - - } - - beforeEach(async(() => { - initializeVars(); - TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], - declarations: [EditCommunityPageComponent], - providers: [ - { provide: CommunityDataService, useValue: communityDataServiceStub }, - { provide: RouteService, useValue: routeServiceStub }, - { provide: Router, useValue: routerStub }, - { provide: ActivatedRoute, useValue: routeStub }, - ], - schemas: [NO_ERRORS_SCHEMA] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(EditCommunityPageComponent); - comp = fixture.componentInstance; - fixture.detectChanges(); - communityDataService = (comp as any).communityDataService; - routeService = (comp as any).routeService; - router = (comp as any).router; - }); - - describe('onSubmit', () => { - let data; - beforeEach(() => { - data = Object.assign(new Community(), { - metadata: [{ - key: 'dc.title', - value: 'test' - }] - }); - }); - it('should navigate when successful', () => { - spyOn(router, 'navigate'); - comp.onSubmit(data); - fixture.detectChanges(); - expect(router.navigate).toHaveBeenCalled(); - }); - - it('should not navigate on failure', () => { - spyOn(router, 'navigate'); - spyOn(communityDataService, 'update').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, newCommunity))); - comp.onSubmit(data); - fixture.detectChanges(); - expect(router.navigate).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/app/+collection-page/edit-collection-page/edit-community-page.component.ts b/src/app/+collection-page/edit-collection-page/edit-community-page.component.ts deleted file mode 100644 index 236aaff05a..0000000000 --- a/src/app/+collection-page/edit-collection-page/edit-community-page.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Component } 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 '../../shared/services/route.service'; -import { ActivatedRoute, Router } from '@angular/router'; -import { RemoteData } from '../../core/data/remote-data'; -import { isNotUndefined } from '../../shared/empty.util'; -import { first, map } from 'rxjs/operators'; -import { getSucceededRemoteData } from '../../core/shared/operators'; - -@Component({ - selector: 'ds-edit-community', - styleUrls: ['./edit-community-page.component.scss'], - templateUrl: './edit-collection-page.component.html' -}) -export class EditCommunityPageComponent { - - public parentUUID$: Observable; - public parentRD$: Observable>; - public communityRD$: Observable>; - - public constructor( - private communityDataService: CommunityDataService, - private routeService: RouteService, - private router: Router, - private route: ActivatedRoute - ) { - } - - ngOnInit(): void { - this.communityRD$ = this.route.data.pipe(first(), map((data) => data.community)); - } - - onSubmit(community: Community) { - this.communityDataService.update(community) - .pipe(getSucceededRemoteData()) - .subscribe((communityRD: RemoteData) => { - if (isNotUndefined(communityRD)) { - const newUUID = communityRD.payload.uuid; - this.router.navigate(['/communities/' + newUUID]); - } - }); - } -} diff --git a/src/app/+community-page/community-form/community-form.component.ts b/src/app/+community-page/community-form/community-form.component.ts index 8dbd2d6490..9ae6f0955d 100644 --- a/src/app/+community-page/community-form/community-form.component.ts +++ b/src/app/+community-page/community-form/community-form.component.ts @@ -1,21 +1,33 @@ -import { Component, Input, OnInit, Output } from '@angular/core'; -import { - DynamicInputModel, - DynamicTextAreaModel -} from '@ng-dynamic-forms/core'; +import { Component, Input } from '@angular/core'; +import { DynamicInputModel, DynamicTextAreaModel } from '@ng-dynamic-forms/core'; import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; import { Community } from '../../core/shared/community.model'; import { ResourceType } from '../../core/shared/resource-type'; -import { ComColFormComponent } from '../../comcol-forms/comcol-form/comcol-form.component'; +import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component'; +/** + * Form used for creating and editing communities + */ @Component({ selector: 'ds-community-form', - styleUrls: ['../../comcol-forms/comcol-form.component.scss'], - templateUrl: '../../comcol-forms/comcol-form/comcol-form.component.html' + styleUrls: ['../../shared/comcol-forms/comcol-form/comcol-form.component.scss'], + templateUrl: '../../shared/comcol-forms/comcol-form/comcol-form.component.html' }) export class CommunityFormComponent extends ComColFormComponent { + /** + * @type {Community} A new community when a community is being created, an existing Input community when a community is being edited + */ @Input() dso: Community = new Community(); - type = ResourceType.Community; + + /** + * @type {ResourceType.Community} This is a community-type form + */ + protected type = ResourceType.Community; + + /** + * The dynamic form fields used for creating/editing a community + * @type {(DynamicInputModel | DynamicTextAreaModel)[]} + */ formModel: DynamicFormControlModel[] = [ new DynamicInputModel({ id: 'title', diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index 8fcc2cde6f..4cd803a3a5 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -6,20 +6,23 @@ import { CommunityPageResolver } from './community-page.resolver'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component'; +import { CreateCommunityPageGuard } from './create-community-page/create-community-page.guard'; @NgModule({ imports: [ RouterModule.forChild([ - { path: 'create', + { + path: 'create', component: CreateCommunityPageComponent, - canActivate: [AuthenticatedGuard] + canActivate: [AuthenticatedGuard, CreateCommunityPageGuard] }, - { path: ':id/edit', + { + path: ':id/edit', pathMatch: 'full', component: EditCommunityPageComponent, canActivate: [AuthenticatedGuard], resolve: { - community: CommunityPageResolver + dso: CommunityPageResolver } }, { @@ -34,6 +37,7 @@ import { EditCommunityPageComponent } from './edit-community-page/edit-community ], providers: [ CommunityPageResolver, + CreateCommunityPageGuard ] }) export class CommunityPageRoutingModule { diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html index a223ecd49c..b3a2f90bb3 100644 --- a/src/app/+community-page/community-page.component.html +++ b/src/app/+community-page/community-page.component.html @@ -28,7 +28,6 @@
- Edit diff --git a/src/app/+community-page/create-community-page/create-community-page.component.html b/src/app/+community-page/create-community-page/create-community-page.component.html index aeb4713b52..55a080d2a1 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.html +++ b/src/app/+community-page/create-community-page/create-community-page.component.html @@ -1,7 +1,7 @@
- +

{{ 'community.create.sub-head' | translate:{ parent: parent.name } }}

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 5a3cf1f4a8..47fb065038 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 @@ -3,16 +3,20 @@ import { Community } from '../../core/shared/community.model'; import { CommunityDataService } from '../../core/data/community-data.service'; import { RouteService } from '../../shared/services/route.service'; import { Router } from '@angular/router'; -import { CreateComColPageComponent } from '../../comcol-forms/create-comcol-page/create-comcol-page.component'; +import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component'; import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +/** + * Component that represents the page where a user can create a new Community + */ @Component({ selector: 'ds-create-community', styleUrls: ['./create-community-page.component.scss'], templateUrl: './create-community-page.component.html' }) export class CreateCommunityPageComponent extends CreateComColPageComponent { - protected frontendURL = 'communities'; + protected frontendURL = '/communities/'; + public constructor( protected communityDataService: CommunityDataService, protected routeService: RouteService, diff --git a/src/app/+community-page/create-community-page/create-community-page.guard.spec.ts b/src/app/+community-page/create-community-page/create-community-page.guard.spec.ts new file mode 100644 index 0000000000..0cc7232871 --- /dev/null +++ b/src/app/+community-page/create-community-page/create-community-page.guard.spec.ts @@ -0,0 +1,67 @@ +import { CreateCommunityPageGuard } from './create-community-page.guard'; +import { MockRouter } from '../../shared/mocks/mock-router'; +import { RemoteData } from '../../core/data/remote-data'; +import { Community } from '../../core/shared/community.model'; +import { of as observableOf } from 'rxjs'; +import { first } from 'rxjs/operators'; + +describe('CreateCommunityPageGuard', () => { + describe('canActivate', () => { + let guard: CreateCommunityPageGuard; + let router; + let communityDataServiceStub: any; + + beforeEach(() => { + communityDataServiceStub = { + findById: (id: string) => { + if (id === 'valid-id') { + return observableOf(new RemoteData(false, false, true, null, new Community())); + } else if (id === 'invalid-id') { + return observableOf(new RemoteData(false, false, true, null, undefined)); + } else if (id === 'error-id') { + return observableOf(new RemoteData(false, false, false, null, new Community())); + } + } + }; + router = new MockRouter(); + + guard = new CreateCommunityPageGuard(router, communityDataServiceStub); + }); + + it('should return true when the parent ID resolves to a community', () => { + guard.canActivate({ queryParams: { parent: 'valid-id' } } as any, undefined) + .pipe(first()) + .subscribe( + (canActivate) => + expect(canActivate).toEqual(true) + ); + }); + + it('should return true when no parent ID has been provided', () => { + guard.canActivate({ queryParams: { } } as any, undefined) + .pipe(first()) + .subscribe( + (canActivate) => + expect(canActivate).toEqual(true) + ); + }); + + it('should return false when the parent ID does not resolve to a community', () => { + guard.canActivate({ queryParams: { parent: 'invalid-id' } } as any, undefined) + .pipe(first()) + .subscribe( + (canActivate) => + expect(canActivate).toEqual(false) + ); + }); + + it('should return false when the parent ID resolves to an error response', () => { + guard.canActivate({ queryParams: { parent: 'error-id' } } as any, undefined) + .pipe(first()) + .subscribe( + (canActivate) => + expect(canActivate).toEqual(false) + ); + }); + }); +}); diff --git a/src/app/+community-page/create-community-page/create-community-page.guard.ts b/src/app/+community-page/create-community-page/create-community-page.guard.ts new file mode 100644 index 0000000000..2ee5cb6064 --- /dev/null +++ b/src/app/+community-page/create-community-page/create-community-page.guard.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; + +import { hasNoValue, hasValue } from '../../shared/empty.util'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { RemoteData } from '../../core/data/remote-data'; +import { Community } from '../../core/shared/community.model'; +import { getFinishedRemoteData } from '../../core/shared/operators'; +import { map, tap } from 'rxjs/operators'; +import { Observable, of as observableOf } from 'rxjs'; + +/** + * Prevent creation of a community with an invalid parent community provided + * @class CreateCommunityPageGuard + */ +@Injectable() +export class CreateCommunityPageGuard implements CanActivate { + public constructor(private router: Router, private communityService: CommunityDataService) { + } + + /** + * True when either NO parent ID query parameter has been provided, or the parent ID resolves to a valid parent community + * Reroutes to a 404 page when the page cannot be activated + * @method canActivate + */ + canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { + const parentID = route.queryParams.parent; + if (hasNoValue(parentID)) { + return observableOf(true); + } + + const parent: Observable> = this.communityService.findById(parentID) + .pipe( + getFinishedRemoteData(), + ); + + return parent.pipe( + map((communityRD: RemoteData) => hasValue(communityRD) && communityRD.hasSucceeded && hasValue(communityRD.payload)), + tap((isValid: boolean) => { + if (!isValid) { + this.router.navigate(['/404']); + } + }) + ); + } +} diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.html b/src/app/+community-page/edit-community-page/edit-community-page.component.html index 1dc6442307..98649243cc 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.html +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.html @@ -4,5 +4,5 @@
- +
diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.ts index 1528c9e8d5..8db5eab204 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.ts +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.ts @@ -1,45 +1,28 @@ import { Component } 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 '../../shared/services/route.service'; import { ActivatedRoute, Router } from '@angular/router'; -import { RemoteData } from '../../core/data/remote-data'; -import { isNotUndefined } from '../../shared/empty.util'; -import { first, map } from 'rxjs/operators'; -import { getSucceededRemoteData } from '../../core/shared/operators'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component'; +/** + * Component that represents the page where a user can edit an existing Community + */ @Component({ selector: 'ds-edit-community', styleUrls: ['./edit-community-page.component.scss'], templateUrl: './edit-community-page.component.html' }) -export class EditCommunityPageComponent { - - public parentUUID$: Observable; - public parentRD$: Observable>; - public communityRD$: Observable>; +export class EditCommunityPageComponent extends EditComColPageComponent { + protected frontendURL = '/communities/'; public constructor( - private communityDataService: CommunityDataService, - private routeService: RouteService, - private router: Router, - private route: ActivatedRoute + protected communityDataService: CommunityDataService, + protected routeService: RouteService, + protected router: Router, + protected route: ActivatedRoute ) { - } - - ngOnInit(): void { - this.communityRD$ = this.route.data.pipe(first(), map((data) => data.community)); - } - - onSubmit(community: Community) { - this.communityDataService.update(community) - .pipe(getSucceededRemoteData()) - .subscribe((communityRD: RemoteData) => { - if (isNotUndefined(communityRD)) { - const newUUID = communityRD.payload.uuid; - this.router.navigate(['/communities/' + newUUID]); - } - }); + super(communityDataService, routeService, router, route); } } diff --git a/src/app/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts b/src/app/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts deleted file mode 100644 index 5e11a71c2e..0000000000 --- a/src/app/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts +++ /dev/null @@ -1,45 +0,0 @@ -import {Component, OnInit} from '@angular/core'; -import { Community } from '../../core/shared/community.model'; -import { Observable } from 'rxjs'; -import { RouteService } from '../../shared/services/route.service'; -import { ActivatedRoute, Router } from '@angular/router'; -import { RemoteData } from '../../core/data/remote-data'; -import { isNotUndefined } from '../../shared/empty.util'; -import { first, map } from 'rxjs/operators'; -import { getSucceededRemoteData } from '../../core/shared/operators'; -import { DataService } from '../../core/data/data.service'; -import { NormalizedDSpaceObject } from '../../core/cache/models/normalized-dspace-object.model'; -import { DSpaceObject } from '../../core/shared/dspace-object.model'; - -@Component({ - selector: 'ds-edit-community', - styleUrls: ['./edit-community-page.component.scss'], - templateUrl: './edit-community-page.component.html' -}) -export class EditComColPageComponent implements OnInit { - protected frontendURL: string; - public dsoRD$: Observable>; - - public constructor( - protected dsoDataService: DataService, - private routeService: RouteService, - private router: Router, - private route: ActivatedRoute - ) { - } - - ngOnInit(): void { - this.dsoRD$ = this.route.data.pipe(first(), map((data) => data.dso)); - } - - onSubmit(dso: TDomain) { - this.dsoDataService.update(dso) - .pipe(getSucceededRemoteData()) - .subscribe((dsoRD: RemoteData) => { - if (isNotUndefined(dsoRD)) { - const newUUID = dsoRD.payload.uuid; - this.router.navigate([this.frontendURL + newUUID]); - } - }); - } -} diff --git a/src/app/core/data/config-response-parsing.service.spec.ts b/src/app/core/data/config-response-parsing.service.spec.ts index a33c5cf5b5..a2c5cbbadc 100644 --- a/src/app/core/data/config-response-parsing.service.spec.ts +++ b/src/app/core/data/config-response-parsing.service.spec.ts @@ -161,7 +161,13 @@ describe('ConfigResponseParsingService', () => { page: { size: 20, totalElements: 2, totalPages: 1, number: 0 } }, statusCode: '500' }; - const pageinfo = Object.assign(new PageInfo(), { elementsPerPage: 4, totalElements: 4, totalPages: 1, currentPage: 1 }); + const pageinfo = Object.assign(new PageInfo(), { + elementsPerPage: 4, + totalElements: 4, + totalPages: 1, + currentPage: 1, + self: 'https://rest.api/config/submissiondefinitions/traditional/sections' + }); const definitions = Object.assign(new SubmissionDefinitionsModel(), { isDefault: true, diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index ad6fb5f096..9a690e3c4b 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -5,18 +5,17 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv import { CoreState } from '../core.reducers'; import { Store } from '@ngrx/store'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { Observable } from 'rxjs'; +import { Observable, of as observableOf } from 'rxjs'; import { FindAllOptions } from './request.models'; -import { SortOptions, SortDirection } from '../cache/models/sort-options.model'; -import { of as observableOf } from 'rxjs'; +import { SortDirection, SortOptions } from '../cache/models/sort-options.model'; import { ObjectCacheService } from '../cache/object-cache.service'; import { Operation } from '../../../../node_modules/fast-json-patch'; import { DSpaceObject } from '../shared/dspace-object.model'; -import { AuthService } from '../auth/auth.service'; import { UpdateComparator } from './update-comparator'; import { HttpClient } from '@angular/common/http'; import { DataBuildService } from '../cache/builders/data-build.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { compare } from 'fast-json-patch'; const endpoint = 'https://rest.api/core'; @@ -45,6 +44,12 @@ class TestService extends DataService { } } +class DummyComparator implements UpdateComparator { + compare(object1: NormalizedTestObject, object2: NormalizedTestObject): Operation[] { + return compare((object1 as any).metadata, (object2 as any).metadata); + } + +} describe('DataService', () => { let service: TestService; let options: FindAllOptions; @@ -53,8 +58,10 @@ describe('DataService', () => { const rdbService = {} as RemoteDataBuildService; const notificationsService = {} as NotificationsService; const http = {} as HttpClient; - const comparator = {} as any; - const dataBuildService = {} as DataBuildService; + const comparator = new DummyComparator() as any; + const dataBuildService = { + normalize: (object) => object + } as DataBuildService; const objectCache = { addPatch: () => { /* empty */ @@ -136,7 +143,7 @@ describe('DataService', () => { elementsPerPage: 10, sort: sortOptions, startsWith: 'ab' - } + }; const expected = `${endpoint}?page=${options.currentPage - 1}&size=${options.elementsPerPage}` + `&sort=${sortOptions.field},${sortOptions.direction}&startsWith=${options.startsWith}`; @@ -169,7 +176,7 @@ describe('DataService', () => { const name1 = 'random string'; const name2 = 'another random string'; beforeEach(() => { - operations = [{ op: 'replace', path: '/metadata/dc.title', value: name2 } as Operation]; + operations = [{ op: 'replace', path: '/0/value', value: name2 } as Operation]; selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7'; dso = new DSpaceObject(); @@ -180,17 +187,18 @@ describe('DataService', () => { dso2.self = selfLink; dso2.metadata = [{ key: 'dc.title', value: name2 }]; - spyOn(objectCache, 'getBySelfLink').and.returnValue(dso); + spyOn(service, 'findById').and.returnValues(observableOf(dso)); + spyOn(objectCache, 'getBySelfLink').and.returnValues(observableOf(dso)); spyOn(objectCache, 'addPatch'); }); it('should call addPatch on the object cache with the right parameters when there are differences', () => { - service.update(dso2); + service.update(dso2).subscribe(); expect(objectCache.addPatch).toHaveBeenCalledWith(selfLink, operations); }); it('should not call addPatch on the object cache with the right parameters when there are no differences', () => { - service.update(dso); + service.update(dso).subscribe(); expect(objectCache.addPatch).not.toHaveBeenCalled(); }); }); diff --git a/src/app/core/data/update-comparator.ts b/src/app/core/data/update-comparator.ts index 884ac585f5..f064d7f3f2 100644 --- a/src/app/core/data/update-comparator.ts +++ b/src/app/core/data/update-comparator.ts @@ -3,4 +3,4 @@ import { Operation } from 'fast-json-patch/lib/core'; export interface UpdateComparator { compare(object1: TNormalized, object2: TNormalized): Operation[]; -} \ No newline at end of file +} diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 21ace8a250..ef1d69b8b5 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -35,6 +35,8 @@ import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { EmptyError } from 'rxjs/internal-compatibility'; +import { DataBuildService } from '../cache/builders/data-build.service'; +import { DSOUpdateComparator } from '../data/dso-update-comparator'; /* tslint:disable:max-classes-per-file */ @Component({ @@ -117,6 +119,8 @@ describe('MetadataService', () => { { provide: AuthService, useValue: {} }, { provide: NotificationsService, useValue: {} }, { provide: HttpClient, useValue: {} }, + { provide: DataBuildService, useValue: {} }, + { provide: DSOUpdateComparator, useValue: {} }, Meta, Title, ItemDataService, diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index e73613fddb..9dbfae3f90 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -67,7 +67,7 @@ export class MetadataService { public processRemoteData(remoteData: Observable>): void { remoteData.pipe(map((rd: RemoteData) => rd.payload), filter((co: CacheableObject) => hasValue(co)), - take(1),) + take(1)) .subscribe((dspaceObject: DSpaceObject) => { if (!this.initialized) { this.initialize(dspaceObject); @@ -269,7 +269,7 @@ export class MetadataService { const item = this.currentObject.value as Item; item.getFiles() .pipe( - find((files) => isNotEmpty(files)), + first((files) => isNotEmpty(files)), catchError((error) => { console.debug(error.message); return [] @@ -277,7 +277,7 @@ export class MetadataService { .subscribe((bitstreams: Bitstream[]) => { for (const bitstream of bitstreams) { bitstream.format.pipe( - take(1), + first(), catchError((error: Error) => { console.debug(error.message); return [] diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 388c4289e2..09cb86e010 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -62,6 +62,10 @@ export const getSucceededRemoteData = () => (source: Observable>): Observable> => source.pipe(find((rd: RemoteData) => rd.hasSucceeded)); +export const getFinishedRemoteData = () => + (source: Observable>): Observable> => + source.pipe(find((rd: RemoteData) => !rd.isLoading)); + export const toDSpaceObjectListRD = () => (source: Observable>>>): Observable>> => source.pipe( diff --git a/src/app/comcol-forms/comcol-form/comcol-form.component.html b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.html similarity index 100% rename from src/app/comcol-forms/comcol-form/comcol-form.component.html rename to src/app/shared/comcol-forms/comcol-form/comcol-form.component.html diff --git a/src/app/shared/mocks/mock-response-cache.service.ts b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.scss similarity index 100% rename from src/app/shared/mocks/mock-response-cache.service.ts rename to src/app/shared/comcol-forms/comcol-form/comcol-form.component.scss diff --git a/src/app/+community-page/community-form/community-form.component.spec.ts b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.spec.ts similarity index 58% rename from src/app/+community-page/community-form/community-form.component.spec.ts rename to src/app/shared/comcol-forms/comcol-form/comcol-form.component.spec.ts index b90a2b0713..a6f5e0d45a 100644 --- a/src/app/+community-page/community-form/community-form.component.spec.ts +++ b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.spec.ts @@ -1,41 +1,46 @@ -import { CommunityFormComponent } from './community-form.component'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; import { Location } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { - DynamicFormService, - DynamicInputControlModel, - DynamicInputModel -} from '@ng-dynamic-forms/core'; +import { DynamicFormService, DynamicInputModel } from '@ng-dynamic-forms/core'; import { FormControl, FormGroup } from '@angular/forms'; import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; -import { Community } from '../../core/shared/community.model'; -import { ResourceType } from '../../core/shared/resource-type'; +import { Community } from '../../../core/shared/community.model'; +import { ResourceType } from '../../../core/shared/resource-type'; +import { ComColFormComponent } from './comcol-form.component'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { hasValue } from '../../empty.util'; +import { Metadatum } from '../../../core/shared/metadatum.model'; -fdescribe('CommunityFormComponent', () => { - let comp: CommunityFormComponent; - let fixture: ComponentFixture; +describe('ComColFormComponent', () => { + let comp: ComColFormComponent; + let fixture: ComponentFixture>; let location: Location; const formServiceStub: any = { - createFormGroup: (formModel: DynamicFormControlModel[]) => { + createFormGroup: (fModel: DynamicFormControlModel[]) => { const controls = {}; - formModel.forEach((controlModel) => { - controls[controlModel.id] = new FormControl((controlModel as any).value); - }); - return new FormGroup(controls); + if (hasValue(fModel)) { + fModel.forEach((controlModel) => { + controls[controlModel.id] = new FormControl((controlModel as any).value); + }); + return new FormGroup(controls); + } + return undefined; } }; - const titleMD = { key: 'dc.title', value: 'Community Title' }; - const randomMD = { key: 'dc.random', value: 'Random metadata excluded from form' }; - const abstractMD = { key: 'dc.description.abstract', value: 'Community description' }; - const newTitleMD = { key: 'dc.title', value: 'New Community Title' }; + const titleMD = { key: 'dc.title', value: 'Community Title' } as Metadatum; + const randomMD = { key: 'dc.random', value: 'Random metadata excluded from form' } as Metadatum; + const abstractMD = { + key: 'dc.description.abstract', + value: 'Community description' + } as Metadatum; + const newTitleMD = { key: 'dc.title', value: 'New Community Title' } as Metadatum; const formModel = [ new DynamicInputModel({ id: 'title', name: newTitleMD.key, - value: newTitleMD.value + value: 'New Community Title' }), new DynamicInputModel({ id: 'abstract', @@ -54,7 +59,7 @@ fdescribe('CommunityFormComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule], - declarations: [CommunityFormComponent], + declarations: [ComColFormComponent], providers: [ { provide: Location, useValue: locationStub }, { provide: DynamicFormService, useValue: formServiceStub } @@ -64,20 +69,22 @@ fdescribe('CommunityFormComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(CommunityFormComponent); + fixture = TestBed.createComponent(ComColFormComponent); comp = fixture.componentInstance; + comp.formModel = []; + comp.dso = new Community(); fixture.detectChanges(); location = (comp as any).location; - comp.formModel = formModel; }); describe('onSubmit', () => { beforeEach(() => { spyOn(comp.submitForm, 'emit'); + comp.formModel = formModel; }); - it('should update emit the new version of the community', () => { - comp.community = Object.assign( + it('should emit the new version of the community', () => { + comp.dso = Object.assign( new Community(), { metadata: [ diff --git a/src/app/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts similarity index 65% rename from src/app/comcol-forms/comcol-form/comcol-form.component.ts rename to src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts index d49e197f5d..19050e2bc2 100644 --- a/src/app/comcol-forms/comcol-form/comcol-form.component.ts +++ b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts @@ -7,23 +7,50 @@ import { import { FormGroup } from '@angular/forms'; import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model'; import { TranslateService } from '@ngx-translate/core'; -import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { isNotEmpty } from '../../shared/empty.util'; -import { ResourceType } from '../../core/shared/resource-type'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { isNotEmpty } from '../../empty.util'; +import { ResourceType } from '../../../core/shared/resource-type'; @Component({ selector: 'ds-comcol-form', - // styleUrls: ['./comcol-form.component.scss'], + styleUrls: ['./comcol-form.component.scss'], templateUrl: './comcol-form.component.html' }) export class ComColFormComponent implements OnInit { + /** + * DSpaceObject that the form represents + */ @Input() dso: T; - type; - LABEL_KEY_PREFIX = this.type + '.form.'; - ERROR_KEY_PREFIX = this.type + '.form.errors.'; + + /** + * Type of DSpaceObject that the form represents + */ + protected type; + + /** + * @type {string} Key prefix used to generate form labels + */ + LABEL_KEY_PREFIX = '.form.'; + + /** + * @type {string} Key prefix used to generate form error messages + */ + ERROR_KEY_PREFIX = '.form.errors.'; + + /** + * The form model that represents the fields in the form + */ formModel: DynamicFormControlModel[]; + + /** + * The form group of this form + */ formGroup: FormGroup; + /** + * Emits DSO when the form is submitted + * @type {EventEmitter} + */ @Output() submitForm: EventEmitter = new EventEmitter(); public constructor(private location: Location, @@ -38,6 +65,7 @@ export class ComColFormComponent implements OnInit { } ); this.formGroup = this.formService.createFormGroup(this.formModel); + this.updateFieldTranslations(); this.translate.onLangChange .subscribe(() => { @@ -45,6 +73,9 @@ export class ComColFormComponent implements OnInit { }); } + /** + * Checks which new fields where added and sends the updated version of the DSO to the parent component + */ onSubmit() { const metadata = this.formModel.map( (fieldModel: DynamicInputModel) => { @@ -53,6 +84,7 @@ export class ComColFormComponent implements OnInit { ); const filteredOldMetadata = this.dso.metadata.filter((filter) => !metadata.map((md) => md.key).includes(filter.key)); const filteredNewMetadata = metadata.filter((md) => isNotEmpty(md.value)); + const newMetadata = [...filteredOldMetadata, ...filteredNewMetadata]; const updatedDSO = Object.assign({}, this.dso, { metadata: newMetadata, @@ -61,14 +93,17 @@ export class ComColFormComponent implements OnInit { this.submitForm.emit(updatedDSO); } + /** + * Used the update translations of errors and labels on init and on language change + */ private updateFieldTranslations() { this.formModel.forEach( (fieldModel: DynamicInputModel) => { - fieldModel.label = this.translate.instant(this.LABEL_KEY_PREFIX + fieldModel.id); + fieldModel.label = this.translate.instant(this.type + this.LABEL_KEY_PREFIX + fieldModel.id); if (isNotEmpty(fieldModel.validators)) { fieldModel.errorMessages = {}; Object.keys(fieldModel.validators).forEach((key) => { - fieldModel.errorMessages[key] = this.translate.instant(this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key); + fieldModel.errorMessages[key] = this.translate.instant(this.type + this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key); }); } } diff --git a/src/app/+community-page/create-community-page/create-community-page.component.spec.ts b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts similarity index 70% rename from src/app/+community-page/create-community-page/create-community-page.component.spec.ts rename to src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts index 83db9561cc..fd3464ba5e 100644 --- a/src/app/+community-page/create-community-page/create-community-page.component.spec.ts +++ b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts @@ -1,21 +1,25 @@ -import { CreateCommunityPageComponent } from './create-community-page.component'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { CommunityDataService } from '../../core/data/community-data.service'; -import { RouteService } from '../../shared/services/route.service'; +import { CommunityDataService } from '../../../core/data/community-data.service'; +import { RouteService } from '../../services/route.service'; import { Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; -import { RemoteData } from '../../core/data/remote-data'; -import { Community } from '../../core/shared/community.model'; -import { SharedModule } from '../../shared/shared.module'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Community } from '../../../core/shared/community.model'; +import { SharedModule } from '../../shared.module'; import { CommonModule } from '@angular/common'; import { RouterTestingModule } from '@angular/router/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { NormalizedDSpaceObject } from '../../../core/cache/models/normalized-dspace-object.model'; +import { CreateComColPageComponent } from './create-comcol-page.component'; +import { DataService } from '../../../core/data/data.service'; -fdescribe('CreateCommunityPageComponent', () => { - let comp: CreateCommunityPageComponent; - let fixture: ComponentFixture; +describe('CreateComColPageComponent', () => { + let comp: CreateComColPageComponent; + let fixture: ComponentFixture>; let communityDataService: CommunityDataService; + let dsoDataService: CommunityDataService; let routeService: RouteService; let router: Router; @@ -67,8 +71,8 @@ fdescribe('CreateCommunityPageComponent', () => { initializeVars(); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], - declarations: [CreateCommunityPageComponent], providers: [ + { provide: DataService, useValue: communityDataServiceStub }, { provide: CommunityDataService, useValue: communityDataServiceStub }, { provide: RouteService, useValue: routeServiceStub }, { provide: Router, useValue: routerStub }, @@ -78,9 +82,10 @@ fdescribe('CreateCommunityPageComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(CreateCommunityPageComponent); + fixture = TestBed.createComponent(CreateComColPageComponent); comp = fixture.componentInstance; fixture.detectChanges(); + dsoDataService = (comp as any).dsoDataService; communityDataService = (comp as any).communityDataService; routeService = (comp as any).routeService; router = (comp as any).router; @@ -105,7 +110,7 @@ fdescribe('CreateCommunityPageComponent', () => { it('should not navigate on failure', () => { spyOn(router, 'navigate'); - spyOn(communityDataService, 'create').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, newCommunity))); + spyOn(dsoDataService, 'create').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, newCommunity))); comp.onSubmit(data); fixture.detectChanges(); expect(router.navigate).not.toHaveBeenCalled(); diff --git a/src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts similarity index 54% rename from src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts rename to src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts index 18f23cf528..2bcd9f9bd0 100644 --- a/src/app/comcol-forms/create-comcol-page/create-comcol-page.component.ts +++ b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts @@ -1,25 +1,38 @@ import { Component, OnInit } from '@angular/core'; -import { Community } from '../../core/shared/community.model'; -import { CommunityDataService } from '../../core/data/community-data.service'; +import { Community } from '../../../core/shared/community.model'; +import { CommunityDataService } from '../../../core/data/community-data.service'; import { Observable } from 'rxjs'; -import { RouteService } from '../../shared/services/route.service'; +import { RouteService } from '../../services/route.service'; import { Router } from '@angular/router'; -import { RemoteData } from '../../core/data/remote-data'; -import { isNotEmpty, isNotUndefined } from '../../shared/empty.util'; +import { RemoteData } from '../../../core/data/remote-data'; +import { isNotEmpty, isNotUndefined } from '../../empty.util'; import { take } from 'rxjs/operators'; -import { getSucceededRemoteData } from '../../core/shared/operators'; -import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { DataService } from '../../core/data/data.service'; -import { NormalizedDSpaceObject } from '../../core/cache/models/normalized-dspace-object.model'; +import { getSucceededRemoteData } from '../../../core/shared/operators'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { DataService } from '../../../core/data/data.service'; +import { NormalizedDSpaceObject } from '../../../core/cache/models/normalized-dspace-object.model'; +/** + * Component representing the create page for communities and collections + */ @Component({ - selector: 'ds-create-community', - styleUrls: ['./create-community-page.component.scss'], - templateUrl: './create-community-page.component.html' + selector: 'ds-create-comcol', + template: '' }) export class CreateComColPageComponent implements OnInit { + /** + * Frontend endpoint where for this type of DSP + */ protected frontendURL: string; + + /** + * The provided UUID for the parent community + */ public parentUUID$: Observable; + + /** + * The parent community of the object that is to be created + */ public parentRD$: Observable>; public constructor( @@ -40,6 +53,10 @@ export class CreateComColPageComponent { this.dsoDataService.create(dso, uuid) @@ -49,7 +66,7 @@ export class CreateComColPageComponent { - let comp: EditCommunityPageComponent; - let fixture: ComponentFixture; +describe('EditComColPageComponent', () => { + let comp: EditComColPageComponent; + let fixture: ComponentFixture>; let communityDataService: CommunityDataService; + let dsoDataService: CommunityDataService; let routeService: RouteService; let router: Router; @@ -73,9 +76,8 @@ fdescribe('EditCommunityPageComponent', () => { initializeVars(); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], - declarations: [EditCommunityPageComponent], providers: [ - { provide: CommunityDataService, useValue: communityDataServiceStub }, + { provide: DataService, useValue: communityDataServiceStub }, { provide: RouteService, useValue: routeServiceStub }, { provide: Router, useValue: routerStub }, { provide: ActivatedRoute, useValue: routeStub }, @@ -85,9 +87,10 @@ fdescribe('EditCommunityPageComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(EditCommunityPageComponent); + fixture = TestBed.createComponent(EditComColPageComponent); comp = fixture.componentInstance; fixture.detectChanges(); + dsoDataService = (comp as any).dsoDataService; communityDataService = (comp as any).communityDataService; routeService = (comp as any).routeService; router = (comp as any).router; @@ -112,7 +115,7 @@ fdescribe('EditCommunityPageComponent', () => { it('should not navigate on failure', () => { spyOn(router, 'navigate'); - spyOn(communityDataService, 'update').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, newCommunity))); + spyOn(dsoDataService, 'update').and.returnValue(observableOf(new RemoteData(true, true, false, undefined, newCommunity))); comp.onSubmit(data); fixture.detectChanges(); expect(router.navigate).not.toHaveBeenCalled(); diff --git a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts new file mode 100644 index 0000000000..8f03280fc0 --- /dev/null +++ b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts @@ -0,0 +1,56 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { RouteService } from '../../services/route.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RemoteData } from '../../../core/data/remote-data'; +import { isNotUndefined } from '../../empty.util'; +import { first, map } from 'rxjs/operators'; +import { getSucceededRemoteData } from '../../../core/shared/operators'; +import { DataService } from '../../../core/data/data.service'; +import { NormalizedDSpaceObject } from '../../../core/cache/models/normalized-dspace-object.model'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; + +/** + * Component representing the edit page for communities and collections + */ +@Component({ + selector: 'ds-edit-comcol', + template: '' +}) +export class EditComColPageComponent implements OnInit { + /** + * Frontend endpoint where for this type of DSP + */ + protected frontendURL: string; + /** + * The initial DSO object + */ + public dsoRD$: Observable>; + + public constructor( + protected dsoDataService: DataService, + protected routeService: RouteService, + protected router: Router, + protected route: ActivatedRoute + ) { + } + + ngOnInit(): void { + this.dsoRD$ = this.route.data.pipe(first(), map((data) => data.dso)); + } + + /** + * @param {TDomain} dso The updated version of the DSO + * Updates an existing DSO based on the submitted user data and navigates to the editted object's home page + */ + onSubmit(dso: TDomain) { + this.dsoDataService.update(dso) + .pipe(getSucceededRemoteData()) + .subscribe((dsoRD: RemoteData) => { + if (isNotUndefined(dsoRD)) { + const newUUID = dsoRD.payload.uuid; + this.router.navigate([this.frontendURL + newUUID]); + } + }); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index b7d42e4856..a261590b47 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -86,6 +86,9 @@ import { CapitalizePipe } from './utils/capitalize.pipe'; import { ObjectKeysPipe } from './utils/object-keys-pipe'; import { MomentModule } from 'ngx-moment'; import { MenuModule } from './menu/menu.module'; +import { ComColFormComponent } from './comcol-forms/comcol-form/comcol-form.component'; +import { CreateComColPageComponent } from './comcol-forms/create-comcol-page/create-comcol-page.component'; +import { EditComColPageComponent } from './comcol-forms/edit-comcol-page/edit-comcol-page.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -129,6 +132,9 @@ const COMPONENTS = [ ComcolPageContentComponent, ComcolPageHeaderComponent, ComcolPageLogoComponent, + ComColFormComponent, + CreateComColPageComponent, + EditComColPageComponent, DsDynamicFormComponent, DsDynamicFormControlComponent, DsDynamicListComponent, diff --git a/src/app/shared/testing/utils.ts b/src/app/shared/testing/utils.ts index 8714358100..cd17a1b1f5 100644 --- a/src/app/shared/testing/utils.ts +++ b/src/app/shared/testing/utils.ts @@ -41,4 +41,4 @@ export function spyOnOperator(obj: any, prop: string): any { }); return spyOn(obj, prop); -} \ No newline at end of file +} From 773973cdef1b6b3991de51a8d8dd1b8bc1371d34 Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 4 Jan 2019 14:16:35 +0100 Subject: [PATCH 44/57] fixed missing take 1 --- .../create-comcol-page/create-comcol-page.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2bcd9f9bd0..f8bed5b814 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 @@ -46,7 +46,7 @@ export class CreateComColPageComponent { + this.parentUUID$.pipe(take(1)).subscribe((parentID: string) => { if (isNotEmpty(parentID)) { this.parentRD$ = this.parentDataService.findById(parentID); } From ab5cc1c961685beafbcce9020b8c7fdc63642ad8 Mon Sep 17 00:00:00 2001 From: lotte Date: Fri, 4 Jan 2019 16:30:25 +0100 Subject: [PATCH 45/57] 58522: implemented delete for coll and comms --- resources/i18n/en.json | 26 ++++++- .../collection-page-routing.module.ts | 10 +++ .../collection-page.module.ts | 2 + .../delete-community-page.component.html | 19 +++++ .../delete-community-page.component.scss | 1 + .../delete-community-page.component.ts | 33 +++++++++ .../edit-collection-page.component.html | 7 +- .../edit-collection-page.component.ts | 4 +- .../community-page-routing.module.ts | 10 +++ .../+community-page/community-page.module.ts | 3 + .../delete-community-page.component.html | 19 +++++ .../delete-community-page.component.scss | 1 + .../delete-community-page.component.ts | 30 ++++++++ .../edit-community-page.component.html | 14 ++-- .../edit-community-page.component.ts | 3 +- src/app/core/data/comcol-data.service.spec.ts | 2 +- src/app/core/data/comcol-data.service.ts | 2 +- src/app/core/data/data.service.ts | 29 ++++++-- .../core/data/dspace-object-data.service.ts | 2 +- src/app/core/data/request.models.ts | 11 +++ .../delete-comcol-page.component.ts | 71 +++++++++++++++++++ .../edit-comcol-page.component.spec.ts | 8 --- .../edit-comcol-page.component.ts | 4 +- src/app/shared/shared.module.ts | 2 + 24 files changed, 281 insertions(+), 32 deletions(-) create mode 100644 src/app/+collection-page/delete-collection-page/delete-community-page.component.html create mode 100644 src/app/+collection-page/delete-collection-page/delete-community-page.component.scss create mode 100644 src/app/+collection-page/delete-collection-page/delete-community-page.component.ts create mode 100644 src/app/+community-page/delete-community-page/delete-community-page.component.html create mode 100644 src/app/+community-page/delete-community-page/delete-community-page.component.scss create mode 100644 src/app/+community-page/delete-community-page/delete-community-page.component.ts create mode 100644 src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 1f85b8ddac..bcaccc83a3 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -29,11 +29,22 @@ } }, "edit": { - "head": "Edit Collection" + "head": "Edit Collection", + "delete": "Delete this collection" }, "create": { "head": "Create a Collection", "sub-head": "Create a Collection for Community {{ parent }}" + }, + "delete": { + "head": "Delete Collection", + "text": "Are you sure you want to delete collection \"{{ dso }}\"", + "confirm": "Confirm", + "cancel": "Cancel", + "notification": { + "success": "Successfully deleted collection", + "fail": "Collection could not be deleted" + } } }, "community": { @@ -60,11 +71,22 @@ } }, "edit": { - "head": "Edit Community" + "head": "Edit Community", + "delete": "Delete this community" }, "create": { "head": "Create a Community", "sub-head": "Create a Sub-Community for Community {{ parent }}" + }, + "delete": { + "head": "Delete Community", + "text": "Are you sure you want to delete community \"{{ dso }}\"", + "confirm": "Confirm", + "cancel": "Cancel", + "notification": { + "success": "Successfully deleted community", + "fail": "Community could not be deleted" + } } }, "item": { diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index ec53796e61..939844dfcd 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -7,6 +7,7 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component'; import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard'; +import { DeleteCollectionPageComponent } from './delete-collection-page/delete-community-page.component'; @NgModule({ imports: [ @@ -25,6 +26,15 @@ import { CreateCollectionPageGuard } from './create-collection-page/create-colle dso: CollectionPageResolver } }, + { + path: ':id/delete', + pathMatch: 'full', + component: DeleteCollectionPageComponent, + canActivate: [AuthenticatedGuard], + resolve: { + dso: CollectionPageResolver + } + }, { path: ':id', component: CollectionPageComponent, diff --git a/src/app/+collection-page/collection-page.module.ts b/src/app/+collection-page/collection-page.module.ts index 63a03f4299..9d14de489a 100644 --- a/src/app/+collection-page/collection-page.module.ts +++ b/src/app/+collection-page/collection-page.module.ts @@ -9,6 +9,7 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c import { CollectionFormComponent } from './collection-form/collection-form.component'; import { SearchPageModule } from '../+search-page/search-page.module'; import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component'; +import { DeleteCollectionPageComponent } from './delete-collection-page/delete-community-page.component'; @NgModule({ imports: [ @@ -21,6 +22,7 @@ import { EditCollectionPageComponent } from './edit-collection-page/edit-collect CollectionPageComponent, CreateCollectionPageComponent, EditCollectionPageComponent, + DeleteCollectionPageComponent, CollectionFormComponent ] }) diff --git a/src/app/+collection-page/delete-collection-page/delete-community-page.component.html b/src/app/+collection-page/delete-collection-page/delete-community-page.component.html new file mode 100644 index 0000000000..cfd09f2bbd --- /dev/null +++ b/src/app/+collection-page/delete-collection-page/delete-community-page.component.html @@ -0,0 +1,19 @@ +
+
+ +
+ +

{{ 'community.delete.text' | translate:{ dso: dso.name } }}

+ + +
+
+ +
+ +
diff --git a/src/app/+collection-page/delete-collection-page/delete-community-page.component.scss b/src/app/+collection-page/delete-collection-page/delete-community-page.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/+collection-page/delete-collection-page/delete-community-page.component.scss @@ -0,0 +1 @@ + diff --git a/src/app/+collection-page/delete-collection-page/delete-community-page.component.ts b/src/app/+collection-page/delete-collection-page/delete-community-page.component.ts new file mode 100644 index 0000000000..21d4dfd135 --- /dev/null +++ b/src/app/+collection-page/delete-collection-page/delete-community-page.component.ts @@ -0,0 +1,33 @@ +import { Component } from '@angular/core'; +import { Community } from '../../core/shared/community.model'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; +import { Collection } from '../../core/shared/collection.model'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * Component that represents the page where a user can edit an existing Community + */ +@Component({ + selector: 'ds-delete-collection', + styleUrls: ['./delete-collection-page.component.scss'], + templateUrl: './delete-collection-page.component.html' +}) +export class DeleteCollectionPageComponent extends DeleteComColPageComponent { + protected frontendURL = '/collections/'; + + public constructor( + protected dsoDataService: CollectionDataService, + protected router: Router, + protected route: ActivatedRoute, + protected notifications: NotificationsService, + protected translate: TranslateService + ) { + super(dsoDataService, router, route, notifications, translate); + } +} diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html index 1308af919f..129cd3059b 100644 --- a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html @@ -1,8 +1,11 @@
- + + + {{'collection.edit.delete' + | translate}}
-
diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts index 2ffb4925a0..9bbdbfb9a1 100644 --- a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.ts @@ -1,5 +1,4 @@ import { Component } from '@angular/core'; -import { RouteService } from '../../shared/services/route.service'; import { ActivatedRoute, Router } from '@angular/router'; import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component'; import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model'; @@ -19,10 +18,9 @@ export class EditCollectionPageComponent extends EditComColPageComponent +
+ +
+ +

{{ 'community.delete.text' | translate:{ dso: dso.name } }}

+ + +
+
+ +
+ +
diff --git a/src/app/+community-page/delete-community-page/delete-community-page.component.scss b/src/app/+community-page/delete-community-page/delete-community-page.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/+community-page/delete-community-page/delete-community-page.component.scss @@ -0,0 +1 @@ + diff --git a/src/app/+community-page/delete-community-page/delete-community-page.component.ts b/src/app/+community-page/delete-community-page/delete-community-page.component.ts new file mode 100644 index 0000000000..d76284660f --- /dev/null +++ b/src/app/+community-page/delete-community-page/delete-community-page.component.ts @@ -0,0 +1,30 @@ +import { Component } from '@angular/core'; +import { Community } from '../../core/shared/community.model'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; +import { DeleteComColPageComponent } from '../../shared/comcol-forms/delete-comcol-page/delete-comcol-page.component'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * Component that represents the page where a user can edit an existing Community + */ +@Component({ + selector: 'ds-delete-community', + styleUrls: ['./delete-community-page.component.scss'], + templateUrl: './delete-community-page.component.html' +}) +export class DeleteCommunityPageComponent extends DeleteComColPageComponent { + protected frontendURL = '/communities/'; + + public constructor( + protected dsoDataService: CommunityDataService, + protected router: Router, + protected route: ActivatedRoute, + protected notifications: NotificationsService, + protected translate: TranslateService + ) { + super(dsoDataService, router, route, notifications, translate); + } +} diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.html b/src/app/+community-page/edit-community-page/edit-community-page.component.html index 98649243cc..a015a312d3 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.html +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.html @@ -1,8 +1,12 @@
-
-
- +
+
+ + + {{'community.edit.delete' + | translate}} +
-
-
diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.ts index 8db5eab204..31111f779a 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.ts +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.ts @@ -19,10 +19,9 @@ export class EditCommunityPageComponent extends EditComColPageComponent { function initMockCommunityDataService(): CommunityDataService { return jasmine.createSpyObj('responseCache', { getEndpoint: hot('--a-', { a: communitiesEndpoint }), - getFindByIDHref: cold('b-', { b: communityEndpoint }) + getIDHref: cold('b-', { b: communityEndpoint }) }); } diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 0616e9c2ed..8a1ea51bb3 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -42,7 +42,7 @@ export abstract class ComColDataService this.cds.getFindByIDHref(endpoint, options.scopeID)), + mergeMap((endpoint: string) => this.cds.getIDHref(endpoint, options.scopeID)), filter((href: string) => isNotEmpty(href)), take(1), tap((href: string) => { diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 47be86c296..069af4937e 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -18,7 +18,7 @@ import { URLCombiner } from '../url-combiner/url-combiner'; import { PaginatedList } from './paginated-list'; import { RemoteData } from './remote-data'; import { - CreateRequest, + CreateRequest, DeleteByIDRequest, FindAllOptions, FindAllRequest, FindByIDRequest, @@ -33,7 +33,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { HttpClient } from '@angular/common/http'; import { configureRequest, - filterSuccessfulResponses, getResourceLinksFromResponse, + filterSuccessfulResponses, getFinishedRemoteData, getResourceLinksFromResponse, getResponseFromEntry } from '../shared/operators'; import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/response.models'; @@ -43,6 +43,7 @@ import { NormalizedObjectFactory } from '../cache/models/normalized-object-facto import { CacheableObject } from '../cache/object-cache.reducer'; import { DataBuildService } from '../cache/builders/data-build.service'; import { UpdateComparator } from './update-comparator'; +import { RequestEntry } from './request.reducer'; export abstract class DataService { protected abstract requestService: RequestService; @@ -97,13 +98,13 @@ export abstract class DataService(hrefObs) as Observable>>; } - getFindByIDHref(endpoint, resourceID): string { + getIDHref(endpoint, resourceID): string { return `${endpoint}/${resourceID}`; } findById(id: string): Observable> { const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getFindByIDHref(endpoint, id))); + map((endpoint: string) => this.getIDHref(endpoint, id))); hrefObs.pipe( find((href: string) => hasValue(href))) @@ -192,4 +193,24 @@ export abstract class DataService { + const requestId = this.requestService.generateRequestId(); + + const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( + map((endpoint: string) => this.getIDHref(endpoint, dso.uuid))); + + hrefObs.pipe( + find((href: string) => hasValue(href)), + map((href: string) => { + const request = new DeleteByIDRequest(requestId, href, dso.uuid); + this.requestService.configure(request); + }) + ).subscribe(); + + return this.requestService.getByUUID(requestId).pipe( + find((request: RequestEntry) => request.completed), + map((request: RequestEntry) => request.response.isSuccessful) + ); + } + } diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 9a069c4d61..15438d60b7 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -37,7 +37,7 @@ class DataServiceImpl extends DataService return this.halService.getEndpoint(linkPath); } - getFindByIDHref(endpoint, resourceID): string { + getIDHref(endpoint, resourceID): string { return endpoint.replace(/\{\?uuid\}/,`?uuid=${resourceID}`); } } diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 6c4c89e492..5f8539f144 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -227,6 +227,17 @@ export class CreateRequest extends PostRequest { } } +export class DeleteByIDRequest extends DeleteRequest { + constructor( + uuid: string, + href: string, + public resourceID: string + ) { + super(uuid, href); + } +} + + export class RequestError extends Error { statusText: string; } diff --git a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts new file mode 100644 index 0000000000..6fb97d3f37 --- /dev/null +++ b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts @@ -0,0 +1,71 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { RouteService } from '../../services/route.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RemoteData } from '../../../core/data/remote-data'; +import { isNotUndefined } from '../../empty.util'; +import { first, map } from 'rxjs/operators'; +import { getSucceededRemoteData } from '../../../core/shared/operators'; +import { DataService } from '../../../core/data/data.service'; +import { NormalizedDSpaceObject } from '../../../core/cache/models/normalized-dspace-object.model'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * Component representing the edit page for communities and collections + */ +@Component({ + selector: 'ds-delete-comcol', + template: '' +}) +export class DeleteComColPageComponent implements OnInit { + /** + * Frontend endpoint where for this type of DSP + */ + protected frontendURL: string; + /** + * The initial DSO object + */ + public dsoRD$: Observable>; + + public constructor( + protected dsoDataService: DataService, + protected router: Router, + protected route: ActivatedRoute, + protected notifications: NotificationsService, + protected translate: TranslateService + ) { + } + + ngOnInit(): void { + this.dsoRD$ = this.route.data.pipe(first(), map((data) => data.dso)); + } + + /** + * @param {TDomain} dso The DSO to delete + * Deletes an existing DSO and redirects to the home page afterwards + */ + onConfirm(dso: TDomain) { + this.dsoDataService.delete(dso) + .pipe(first()) + .subscribe((success: boolean) => { + if (success) { + const successMessage = this.translate.instant(dso.type + '.delete.notification.success'); + this.notifications.success(successMessage) + } else { + const errorMessage = this.translate.instant(dso.type + '.delete.notification.fail'); + this.notifications.error(errorMessage) + } + this.router.navigate(['/']); + }); + } + + /** + * @param {TDomain} dso The DSO for which the delete action was canceled + * When a delete is canceled, the user is redirected to the DSO's edit page + */ + onCancel(dso: TDomain) { + this.router.navigate([this.frontendURL + '/' + dso.uuid + '/edit']); + } +} diff --git a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.spec.ts b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.spec.ts index ae59e185a3..aa8d73cd2a 100644 --- a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.spec.ts +++ b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.spec.ts @@ -1,6 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CommunityDataService } from '../../../core/data/community-data.service'; -import { RouteService } from '../../services/route.service'; import { ActivatedRoute, Router } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; @@ -20,13 +19,11 @@ describe('EditComColPageComponent', () => { let fixture: ComponentFixture>; let communityDataService: CommunityDataService; let dsoDataService: CommunityDataService; - let routeService: RouteService; let router: Router; let community; let newCommunity; let communityDataServiceStub; - let routeServiceStub; let routerStub; let routeStub; @@ -59,9 +56,6 @@ describe('EditComColPageComponent', () => { }; - routeServiceStub = { - getQueryParameterValue: (param) => observableOf(community.uuid) - }; routerStub = { navigate: (commands) => commands }; @@ -78,7 +72,6 @@ describe('EditComColPageComponent', () => { imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], providers: [ { provide: DataService, useValue: communityDataServiceStub }, - { provide: RouteService, useValue: routeServiceStub }, { provide: Router, useValue: routerStub }, { provide: ActivatedRoute, useValue: routeStub }, ], @@ -92,7 +85,6 @@ describe('EditComColPageComponent', () => { fixture.detectChanges(); dsoDataService = (comp as any).dsoDataService; communityDataService = (comp as any).communityDataService; - routeService = (comp as any).routeService; router = (comp as any).router; }); diff --git a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts index 8f03280fc0..42a77b66f9 100644 --- a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts +++ b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts @@ -1,6 +1,5 @@ import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; -import { RouteService } from '../../services/route.service'; import { ActivatedRoute, Router } from '@angular/router'; import { RemoteData } from '../../../core/data/remote-data'; import { isNotUndefined } from '../../empty.util'; @@ -29,7 +28,6 @@ export class EditComColPageComponent, - protected routeService: RouteService, protected router: Router, protected route: ActivatedRoute ) { @@ -41,7 +39,7 @@ export class EditComColPageComponent Date: Fri, 4 Jan 2019 16:55:16 +0100 Subject: [PATCH 46/57] 54472: fixed small test issue --- .../integration/integration-response-parsing.service.spec.ts | 2 +- .../comcol-forms/edit-comcol-page/edit-comcol-page.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/integration/integration-response-parsing.service.spec.ts b/src/app/core/integration/integration-response-parsing.service.spec.ts index 38741da4e2..03f350b868 100644 --- a/src/app/core/integration/integration-response-parsing.service.spec.ts +++ b/src/app/core/integration/integration-response-parsing.service.spec.ts @@ -143,7 +143,7 @@ describe('IntegrationResponseParsingService', () => { }, statusCode: '200' }; - const pageinfo = Object.assign(new PageInfo(), { elementsPerPage: 5, totalElements: 5, totalPages: 1, currentPage: 1 }); + const pageinfo = Object.assign(new PageInfo(), { elementsPerPage: 5, totalElements: 5, totalPages: 1, currentPage: 1, self: 'https://rest.api/integration/authorities/type/entries'}); const definitions = new PaginatedList(pageinfo,[ Object.assign({}, new AuthorityValueModel(), { type: 'authority', diff --git a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts index 8f03280fc0..5ccbcfcc66 100644 --- a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts +++ b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts @@ -41,7 +41,7 @@ export class EditComColPageComponent Date: Mon, 7 Jan 2019 09:17:22 +0100 Subject: [PATCH 47/57] 54472: fixed small test issue --- ...tegration-response-parsing.service.spec.ts | 102 ++++++++++-------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/src/app/core/integration/integration-response-parsing.service.spec.ts b/src/app/core/integration/integration-response-parsing.service.spec.ts index 03f350b868..baa4343724 100644 --- a/src/app/core/integration/integration-response-parsing.service.spec.ts +++ b/src/app/core/integration/integration-response-parsing.service.spec.ts @@ -23,15 +23,57 @@ describe('IntegrationResponseParsingService', () => { const uuid = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; const integrationEndpoint = 'https://rest.api/integration/authorities'; const entriesEndpoint = `${integrationEndpoint}/${name}/entries?query=${query}&metadata=${metadata}&uuid=${uuid}`; + let validRequest; - beforeEach(() => { - service = new IntegrationResponseParsingService(EnvConfig, objectCacheService); - }); + let validResponse; - describe('parse', () => { - const validRequest = new IntegrationRequest('69f375b5-19f4-4453-8c7a-7dc5c55aafbb', entriesEndpoint); + let invalidResponse1; + let invalidResponse2; + let pageInfo; + let definitions; - const validResponse = { + function initVars() { + pageInfo = Object.assign(new PageInfo(), { elementsPerPage: 5, totalElements: 5, totalPages: 1, currentPage: 1, self: 'https://rest.api/integration/authorities/type/entries'}); + definitions = new PaginatedList(pageInfo,[ + Object.assign({}, new AuthorityValueModel(), { + type: 'authority', + display: 'One', + id: 'One', + otherInformation: undefined, + value: 'One' + }), + Object.assign({}, new AuthorityValueModel(), { + type: 'authority', + display: 'Two', + id: 'Two', + otherInformation: undefined, + value: 'Two' + }), + Object.assign({}, new AuthorityValueModel(), { + type: 'authority', + display: 'Three', + id: 'Three', + otherInformation: undefined, + value: 'Three' + }), + Object.assign({}, new AuthorityValueModel(), { + type: 'authority', + display: 'Four', + id: 'Four', + otherInformation: undefined, + value: 'Four' + }), + Object.assign({}, new AuthorityValueModel(), { + type: 'authority', + display: 'Five', + id: 'Five', + otherInformation: undefined, + value: 'Five' + }) + ]); + validRequest = new IntegrationRequest('69f375b5-19f4-4453-8c7a-7dc5c55aafbb', entriesEndpoint); + + validResponse = { payload: { page: { number: 0, @@ -86,12 +128,12 @@ describe('IntegrationResponseParsingService', () => { statusCode: '200' }; - const invalidResponse1 = { + invalidResponse1 = { payload: {}, statusCode: '200' }; - const invalidResponse2 = { + invalidResponse2 = { payload: { page: { number: 0, @@ -143,45 +185,13 @@ describe('IntegrationResponseParsingService', () => { }, statusCode: '200' }; - const pageinfo = Object.assign(new PageInfo(), { elementsPerPage: 5, totalElements: 5, totalPages: 1, currentPage: 1, self: 'https://rest.api/integration/authorities/type/entries'}); - const definitions = new PaginatedList(pageinfo,[ - Object.assign({}, new AuthorityValueModel(), { - type: 'authority', - display: 'One', - id: 'One', - otherInformation: undefined, - value: 'One' - }), - Object.assign({}, new AuthorityValueModel(), { - type: 'authority', - display: 'Two', - id: 'Two', - otherInformation: undefined, - value: 'Two' - }), - Object.assign({}, new AuthorityValueModel(), { - type: 'authority', - display: 'Three', - id: 'Three', - otherInformation: undefined, - value: 'Three' - }), - Object.assign({}, new AuthorityValueModel(), { - type: 'authority', - display: 'Four', - id: 'Four', - otherInformation: undefined, - value: 'Four' - }), - Object.assign({}, new AuthorityValueModel(), { - type: 'authority', - display: 'Five', - id: 'Five', - otherInformation: undefined, - value: 'Five' - }) - ]); + } + beforeEach(() => { + initVars(); + service = new IntegrationResponseParsingService(EnvConfig, objectCacheService); + }); + describe('parse', () => { it('should return a IntegrationSuccessResponse if data contains a valid endpoint response', () => { const response = service.parse(validRequest, validResponse); expect(response.constructor).toBe(IntegrationSuccessResponse); From 4ba6094da59026a1e26ea24473c2148a9f2d3e51 Mon Sep 17 00:00:00 2001 From: lotte Date: Mon, 7 Jan 2019 12:01:09 +0100 Subject: [PATCH 48/57] Added tests/type doc --- .../collection-page-routing.module.ts | 2 +- .../collection-page.module.ts | 2 +- ... => delete-collection-page.component.html} | 0 ... => delete-collection-page.component.scss} | 0 ...ts => delete-collection-page.component.ts} | 2 +- .../delete-community-page.component.ts | 2 +- .../create-comcol-page.component.ts | 2 +- .../delete-comcol-page.component.spec.ts | 154 ++++++++++++++++++ .../delete-comcol-page.component.ts | 6 +- .../edit-comcol-page.component.spec.ts | 9 - .../edit-comcol-page.component.ts | 2 +- 11 files changed, 163 insertions(+), 18 deletions(-) rename src/app/+collection-page/delete-collection-page/{delete-community-page.component.html => delete-collection-page.component.html} (100%) rename src/app/+collection-page/delete-collection-page/{delete-community-page.component.scss => delete-collection-page.component.scss} (100%) rename src/app/+collection-page/delete-collection-page/{delete-community-page.component.ts => delete-collection-page.component.ts} (94%) create mode 100644 src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index 939844dfcd..ddcf36a0cc 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -7,7 +7,7 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component'; import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard'; -import { DeleteCollectionPageComponent } from './delete-collection-page/delete-community-page.component'; +import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; @NgModule({ imports: [ diff --git a/src/app/+collection-page/collection-page.module.ts b/src/app/+collection-page/collection-page.module.ts index 9d14de489a..8424cc02a4 100644 --- a/src/app/+collection-page/collection-page.module.ts +++ b/src/app/+collection-page/collection-page.module.ts @@ -9,7 +9,7 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c import { CollectionFormComponent } from './collection-form/collection-form.component'; import { SearchPageModule } from '../+search-page/search-page.module'; import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component'; -import { DeleteCollectionPageComponent } from './delete-collection-page/delete-community-page.component'; +import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component'; @NgModule({ imports: [ diff --git a/src/app/+collection-page/delete-collection-page/delete-community-page.component.html b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.html similarity index 100% rename from src/app/+collection-page/delete-collection-page/delete-community-page.component.html rename to src/app/+collection-page/delete-collection-page/delete-collection-page.component.html diff --git a/src/app/+collection-page/delete-collection-page/delete-community-page.component.scss b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.scss similarity index 100% rename from src/app/+collection-page/delete-collection-page/delete-community-page.component.scss rename to src/app/+collection-page/delete-collection-page/delete-collection-page.component.scss diff --git a/src/app/+collection-page/delete-collection-page/delete-community-page.component.ts b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.ts similarity index 94% rename from src/app/+collection-page/delete-collection-page/delete-community-page.component.ts rename to src/app/+collection-page/delete-collection-page/delete-collection-page.component.ts index 21d4dfd135..80abb83694 100644 --- a/src/app/+collection-page/delete-collection-page/delete-community-page.component.ts +++ b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.ts @@ -11,7 +11,7 @@ import { Collection } from '../../core/shared/collection.model'; import { TranslateService } from '@ngx-translate/core'; /** - * Component that represents the page where a user can edit an existing Community + * Component that represents the page where a user can delete an existing Collection */ @Component({ selector: 'ds-delete-collection', diff --git a/src/app/+community-page/delete-community-page/delete-community-page.component.ts b/src/app/+community-page/delete-community-page/delete-community-page.component.ts index d76284660f..01741a7577 100644 --- a/src/app/+community-page/delete-community-page/delete-community-page.component.ts +++ b/src/app/+community-page/delete-community-page/delete-community-page.component.ts @@ -8,7 +8,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { TranslateService } from '@ngx-translate/core'; /** - * Component that represents the page where a user can edit an existing Community + * Component that represents the page where a user can delete an existing Community */ @Component({ selector: 'ds-delete-community', 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 f8bed5b814..fc7ee3ee70 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 @@ -21,7 +21,7 @@ import { NormalizedDSpaceObject } from '../../../core/cache/models/normalized-ds }) export class CreateComColPageComponent implements OnInit { /** - * Frontend endpoint where for this type of DSP + * Frontend endpoint for this type of DSO */ protected frontendURL: string; diff --git a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts new file mode 100644 index 0000000000..0243cce33b --- /dev/null +++ b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts @@ -0,0 +1,154 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommunityDataService } from '../../../core/data/community-data.service'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; +import { Community } from '../../../core/shared/community.model'; +import { SharedModule } from '../../shared.module'; +import { CommonModule } from '@angular/common'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { NormalizedDSpaceObject } from '../../../core/cache/models/normalized-dspace-object.model'; +import { DataService } from '../../../core/data/data.service'; +import { DeleteComColPageComponent } from './delete-comcol-page.component'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../testing/notifications-service-stub'; + +describe('DeleteComColPageComponent', () => { + let comp: DeleteComColPageComponent; + let fixture: ComponentFixture>; + let dsoDataService: CommunityDataService; + let router: Router; + + let community; + let newCommunity; + let routerStub; + let routeStub; + let notificationsService; + const validUUID = 'valid-uuid'; + const invalidUUID = 'invalid-uuid'; + + function initializeVars() { + community = Object.assign(new Community(), { + uuid: 'a20da287-e174-466a-9926-f66b9300d347', + metadata: [{ + key: 'dc.title', + value: 'test community' + }] + }); + + newCommunity = Object.assign(new Community(), { + uuid: '1ff59938-a69a-4e62-b9a4-718569c55d48', + metadata: [{ + key: 'dc.title', + value: 'new community' + }] + }); + + dsoDataService = jasmine.createSpyObj( + 'dsoDataService', + { + delete: observableOf(true) + }); + + routerStub = { + navigate: (commands) => commands + }; + + routeStub = { + data: observableOf(community) + }; + + } + + beforeEach(async(() => { + initializeVars(); + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + providers: [ + { provide: DataService, useValue: dsoDataService }, + { provide: Router, useValue: routerStub }, + { provide: ActivatedRoute, useValue: routeStub }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteComColPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + notificationsService = (comp as any).notifications; + router = (comp as any).router; + }); + + describe('onConfirm', () => { + let data1; + let data2; + beforeEach(() => { + data1 = Object.assign(new Community(), { + uuid: validUUID, + metadata: [{ + key: 'dc.title', + value: 'test' + }] + }); + + data2 = Object.assign(new Community(), { + uuid: invalidUUID, + metadata: [{ + key: 'dc.title', + value: 'test' + }] + }); + }); + + it('should show an error notification on failure', () => { + (dsoDataService.delete as any).and.returnValue(observableOf(false)); + spyOn(notificationsService, 'error'); + spyOn(router, 'navigate'); + comp.onConfirm(data2); + fixture.detectChanges(); + expect(notificationsService.error).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should show a success notification on success and navigate', () => { + spyOn(notificationsService, 'success'); + spyOn(router, 'navigate'); + comp.onConfirm(data1); + fixture.detectChanges(); + expect(notificationsService.success).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); + }); + + it('should call delete on the data service', () => { + comp.onConfirm(data1); + fixture.detectChanges(); + expect(dsoDataService.delete).toHaveBeenCalledWith(data1); + }); + }); + + describe('onCancel', () => { + let data1; + beforeEach(() => { + data1 = Object.assign(new Community(), { + uuid: validUUID, + metadata: [{ + key: 'dc.title', + value: 'test' + }] + }); + }); + + it('should redirect to the edit page', () => { + const redirectURL = 'communities/edit/' + validUUID; + spyOn(router, 'navigate'); + comp.onCancel(data1); + fixture.detectChanges(); + expect(router.navigate).toHaveBeenCalledWith([redirectURL]); + }); + }); +}); diff --git a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts index 6fb97d3f37..6e3a826e87 100644 --- a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts +++ b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts @@ -13,7 +13,7 @@ import { NotificationsService } from '../../notifications/notifications.service' import { TranslateService } from '@ngx-translate/core'; /** - * Component representing the edit page for communities and collections + * Component representing the delete page for communities and collections */ @Component({ selector: 'ds-delete-comcol', @@ -21,7 +21,7 @@ import { TranslateService } from '@ngx-translate/core'; }) export class DeleteComColPageComponent implements OnInit { /** - * Frontend endpoint where for this type of DSP + * Frontend endpoint for this type of DSO */ protected frontendURL: string; /** @@ -44,7 +44,7 @@ export class DeleteComColPageComponent { let comp: EditComColPageComponent; let fixture: ComponentFixture>; - let communityDataService: CommunityDataService; let dsoDataService: CommunityDataService; let router: Router; @@ -45,13 +44,6 @@ describe('EditComColPageComponent', () => { }); communityDataServiceStub = { - findById: (uuid) => observableOf(new RemoteData(false, false, true, null, Object.assign(new Community(), { - uuid: uuid, - metadata: [{ - key: 'dc.title', - value: community.name - }] - }))), update: (com, uuid?) => observableOf(new RemoteData(false, false, true, undefined, newCommunity)) }; @@ -84,7 +76,6 @@ describe('EditComColPageComponent', () => { comp = fixture.componentInstance; fixture.detectChanges(); dsoDataService = (comp as any).dsoDataService; - communityDataService = (comp as any).communityDataService; router = (comp as any).router; }); diff --git a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts index 42a77b66f9..b669fcea54 100644 --- a/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts +++ b/src/app/shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.ts @@ -18,7 +18,7 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model'; }) export class EditComColPageComponent implements OnInit { /** - * Frontend endpoint where for this type of DSP + * Frontend endpoint for this type of DSO */ protected frontendURL: string; /** From 0be76d49d97831d80303e2f92f3581dd228aae8a Mon Sep 17 00:00:00 2001 From: lotte Date: Mon, 7 Jan 2019 12:42:24 +0100 Subject: [PATCH 49/57] 58522: delete tests and docs --- .../delete-comcol-page/delete-comcol-page.component.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts index 0243cce33b..81ec9c47a0 100644 --- a/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts +++ b/src/app/shared/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts @@ -28,7 +28,7 @@ describe('DeleteComColPageComponent', () => { let notificationsService; const validUUID = 'valid-uuid'; const invalidUUID = 'invalid-uuid'; - + const frontendURL = '/testType'; function initializeVars() { community = Object.assign(new Community(), { uuid: 'a20da287-e174-466a-9926-f66b9300d347', @@ -81,6 +81,7 @@ describe('DeleteComColPageComponent', () => { comp = fixture.componentInstance; fixture.detectChanges(); notificationsService = (comp as any).notifications; + (comp as any).frontendURL = frontendURL; router = (comp as any).router; }); @@ -144,7 +145,7 @@ describe('DeleteComColPageComponent', () => { }); it('should redirect to the edit page', () => { - const redirectURL = 'communities/edit/' + validUUID; + const redirectURL = frontendURL + '/' + validUUID + '/edit'; spyOn(router, 'navigate'); comp.onCancel(data1); fixture.detectChanges(); From 8e543ab0366660f92f050723d8cc745c6e886cf3 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 10 Jan 2019 10:14:50 +0100 Subject: [PATCH 50/57] 58522: changed styling of button --- .../edit-collection-page/edit-collection-page.component.html | 2 +- .../edit-community-page/edit-community-page.component.html | 2 +- src/app/core/data/request.models.ts | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html index 129cd3059b..c389c681ce 100644 --- a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.html @@ -3,7 +3,7 @@
- {{'collection.edit.delete' | translate}}
diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.html b/src/app/+community-page/edit-community-page/edit-community-page.component.html index a015a312d3..cedb771c14 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.html +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.html @@ -4,7 +4,7 @@ - {{'community.edit.delete' | translate}}
diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 5f8539f144..6f0ff5b605 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -237,7 +237,6 @@ export class DeleteByIDRequest extends DeleteRequest { } } - export class RequestError extends Error { statusText: string; } From b0da712915b15ef3b9643012eefceafa905f97af Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 10 Jan 2019 13:01:57 +0100 Subject: [PATCH 51/57] added trivial tests --- .../create-collection-page.component.spec.ts | 46 +++++++++++++++++++ .../edit-collection-page.component.spec.ts | 42 +++++++++++++++++ .../create-community-page.component.spec.ts | 42 +++++++++++++++++ .../edit-community-page.component.spec.ts | 42 +++++++++++++++++ src/app/core/cache/object-cache.reducer.ts | 2 +- 5 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts create mode 100644 src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts create mode 100644 src/app/+community-page/create-community-page/create-community-page.component.spec.ts create mode 100644 src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts 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 new file mode 100644 index 0000000000..29350a83e0 --- /dev/null +++ b/src/app/+collection-page/create-collection-page/create-collection-page.component.spec.ts @@ -0,0 +1,46 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +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 { SharedModule } from '../../shared/shared.module'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { of as observableOf } from 'rxjs'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { CreateCollectionPageComponent } from './create-collection-page.component'; + +describe('CreateCollectionPageComponent', () => { + let comp: CreateCollectionPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [CreateCollectionPageComponent], + providers: [ + { provide: CollectionDataService, useValue: {} }, + { + provide: CommunityDataService, + useValue: { findById: () => observableOf({ payload: { name: 'test' } }) } + }, + { provide: RouteService, useValue: { getQueryParameterValue: () => observableOf('1234') } }, + { provide: Router, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateCollectionPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('frontendURL', () => { + it('should have the right frontendURL set', () => { + expect((comp as any).frontendURL).toEqual('/collections/'); + }) + }); +}); diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts new file mode 100644 index 0000000000..6b01f13574 --- /dev/null +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts @@ -0,0 +1,42 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +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 { EditCollectionPageComponent } from './edit-collection-page.component'; +import { RouteService } from '../../shared/services/route.service'; +import { SharedModule } from '../../shared/shared.module'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { of as observableOf } from 'rxjs'; + +describe('EditCollectionPageComponent', () => { + let comp: EditCollectionPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [EditCollectionPageComponent], + providers: [ + { provide: CollectionDataService, useValue: {} }, + { provide: RouteService, useValue: {} }, + { provide: Router, useValue: {} }, + { provide: ActivatedRoute, useValue: { data: observableOf({dso: undefined}) } }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditCollectionPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('frontendURL', () => { + it('should have the right frontendURL set', () => { + expect((comp as any).frontendURL).toEqual('/collections/'); + }) + }); +}); 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 new file mode 100644 index 0000000000..dba15dbe88 --- /dev/null +++ b/src/app/+community-page/create-community-page/create-community-page.component.spec.ts @@ -0,0 +1,42 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Router } from '@angular/router'; +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 { SharedModule } from '../../shared/shared.module'; +import { CollectionDataService } from '../../core/data/collection-data.service'; +import { of as observableOf } from 'rxjs'; +import { CommunityDataService } from '../../core/data/community-data.service'; +import { CreateCommunityPageComponent } from './create-community-page.component'; + +describe('CreateCommunityPageComponent', () => { + let comp: CreateCommunityPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [CreateCommunityPageComponent], + providers: [ + { provide: CommunityDataService, useValue: { findById: () => observableOf({}) } }, + { provide: RouteService, useValue: { getQueryParameterValue: () => observableOf('1234') } }, + { provide: Router, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CreateCommunityPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('frontendURL', () => { + it('should have the right frontendURL set', () => { + expect((comp as any).frontendURL).toEqual('/communities/'); + }) + }); +}); diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts new file mode 100644 index 0000000000..764cffe8b5 --- /dev/null +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts @@ -0,0 +1,42 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +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 { SharedModule } from '../../shared/shared.module'; +import { of as observableOf } from 'rxjs'; +import { EditCommunityPageComponent } from './edit-community-page.component'; +import { CommunityDataService } from '../../core/data/community-data.service'; + +describe('EditCommunityPageComponent', () => { + let comp: EditCommunityPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [EditCommunityPageComponent], + providers: [ + { provide: CommunityDataService, useValue: {} }, + { provide: RouteService, useValue: {} }, + { provide: Router, useValue: {} }, + { provide: ActivatedRoute, useValue: { data: observableOf({dso: undefined}) } }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditCommunityPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('frontendURL', () => { + it('should have the right frontendURL set', () => { + expect((comp as any).frontendURL).toEqual('/communities/'); + }) + }); +}); diff --git a/src/app/core/cache/object-cache.reducer.ts b/src/app/core/cache/object-cache.reducer.ts index 867f31e1bb..991ae5466e 100644 --- a/src/app/core/cache/object-cache.reducer.ts +++ b/src/app/core/cache/object-cache.reducer.ts @@ -22,7 +22,7 @@ export interface Patch { operations: Operation[]; } -/**conca +/** * An interface to represent objects that can be cached * * A cacheable object should have a self link From f51ac8086d5eb68f66bcf9bb07659976b681c134 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 10 Jan 2019 13:16:27 +0100 Subject: [PATCH 52/57] remove exportToZip config property that was added accidentally --- config/environment.default.js | 1 - src/config/cache-config.interface.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/config/environment.default.js b/config/environment.default.js index 527e12936e..3c1144fc6f 100644 --- a/config/environment.default.js +++ b/config/environment.default.js @@ -20,7 +20,6 @@ module.exports = { // NOTE: how long should objects be cached for by default msToLive: { default: 15 * 60 * 1000, // 15 minutes - exportToZip: 5 * 1000 // 5 seconds }, // msToLive: 1000, // 15 minutes control: 'max-age=60', // revalidate browser diff --git a/src/config/cache-config.interface.ts b/src/config/cache-config.interface.ts index a52eca60e2..ef2d19e76e 100644 --- a/src/config/cache-config.interface.ts +++ b/src/config/cache-config.interface.ts @@ -4,7 +4,6 @@ import { AutoSyncConfig } from './auto-sync-config.interface'; export interface CacheConfig extends Config { msToLive: { default: number; - exportToZip: number; }, control: string, autoSync: AutoSyncConfig From a9f62659b95f669883e6d80d302f1c9a8244b394 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 10 Jan 2019 14:12:47 +0100 Subject: [PATCH 53/57] added tests --- .../delete-collection-page.component.spec.ts | 41 ++++++++++++++++++ .../edit-collection-page.component.spec.ts | 7 +--- .../delete-community-page.component.spec.ts | 42 +++++++++++++++++++ .../edit-community-page.component.spec.ts | 7 +--- .../edit-community-page.component.ts | 1 - 5 files changed, 87 insertions(+), 11 deletions(-) create mode 100644 src/app/+collection-page/delete-collection-page/delete-collection-page.component.spec.ts create mode 100644 src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts diff --git a/src/app/+collection-page/delete-collection-page/delete-collection-page.component.spec.ts b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.spec.ts new file mode 100644 index 0000000000..d64c1d1915 --- /dev/null +++ b/src/app/+collection-page/delete-collection-page/delete-collection-page.component.spec.ts @@ -0,0 +1,41 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +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 { SharedModule } from '../../shared/shared.module'; +import { of as observableOf } from 'rxjs'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { DeleteCollectionPageComponent } from './delete-collection-page.component'; +import { CollectionDataService } from '../../core/data/collection-data.service'; + +describe('DeleteCollectionPageComponent', () => { + let comp: DeleteCollectionPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [DeleteCollectionPageComponent], + providers: [ + { provide: CollectionDataService, useValue: {} }, + { provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } }, + { provide: NotificationsService, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteCollectionPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('frontendURL', () => { + it('should have the right frontendURL set', () => { + expect((comp as any).frontendURL).toEqual('/collections/'); + }) + }); +}); diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts index 6b01f13574..193cb293e4 100644 --- a/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.component.spec.ts @@ -1,11 +1,10 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; 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 { EditCollectionPageComponent } from './edit-collection-page.component'; -import { RouteService } from '../../shared/services/route.service'; import { SharedModule } from '../../shared/shared.module'; import { CollectionDataService } from '../../core/data/collection-data.service'; import { of as observableOf } from 'rxjs'; @@ -20,9 +19,7 @@ describe('EditCollectionPageComponent', () => { declarations: [EditCollectionPageComponent], providers: [ { provide: CollectionDataService, useValue: {} }, - { provide: RouteService, useValue: {} }, - { provide: Router, useValue: {} }, - { provide: ActivatedRoute, useValue: { data: observableOf({dso: undefined}) } }, + { provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); 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 new file mode 100644 index 0000000000..f18c4fb1f1 --- /dev/null +++ b/src/app/+community-page/delete-community-page/delete-community-page.component.spec.ts @@ -0,0 +1,42 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivatedRoute, Router } from '@angular/router'; +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 { SharedModule } from '../../shared/shared.module'; +import { of as observableOf } from 'rxjs'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { DeleteCommunityPageComponent } from './delete-community-page.component'; +import { CommunityDataService } from '../../core/data/community-data.service'; + +describe('DeleteCommunityPageComponent', () => { + let comp: DeleteCommunityPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule], + declarations: [DeleteCommunityPageComponent], + providers: [ + { provide: CommunityDataService, useValue: {} }, + { provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } }, + { provide: NotificationsService, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteCommunityPageComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('frontendURL', () => { + it('should have the right frontendURL set', () => { + expect((comp as any).frontendURL).toEqual('/communities/'); + }) + }); +}); diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts index 764cffe8b5..54f2133ce7 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.spec.ts @@ -1,10 +1,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; 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 { SharedModule } from '../../shared/shared.module'; import { of as observableOf } from 'rxjs'; import { EditCommunityPageComponent } from './edit-community-page.component'; @@ -20,9 +19,7 @@ describe('EditCommunityPageComponent', () => { declarations: [EditCommunityPageComponent], providers: [ { provide: CommunityDataService, useValue: {} }, - { provide: RouteService, useValue: {} }, - { provide: Router, useValue: {} }, - { provide: ActivatedRoute, useValue: { data: observableOf({dso: undefined}) } }, + { provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/+community-page/edit-community-page/edit-community-page.component.ts b/src/app/+community-page/edit-community-page/edit-community-page.component.ts index 31111f779a..68f092e915 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.component.ts +++ b/src/app/+community-page/edit-community-page/edit-community-page.component.ts @@ -1,7 +1,6 @@ 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 { ActivatedRoute, Router } from '@angular/router'; import { NormalizedCommunity } from '../../core/cache/models/normalized-community.model'; import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component'; From e387f9446f0a7ff8b399729ca2f3bad708984996 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 17 Jan 2019 13:53:34 +0100 Subject: [PATCH 54/57] remove unused 'first' imports --- src/app/+community-page/community-page.component.ts | 2 +- .../search-facet-filter/search-facet-filter.component.ts | 2 +- .../search-filters/search-filter/search-filter.component.ts | 2 +- src/app/app.component.ts | 2 +- src/app/core/auth/auth.effects.ts | 2 +- src/app/core/auth/server-auth.service.ts | 2 +- src/app/core/cache/object-cache.service.ts | 2 +- src/app/core/cache/server-sync-buffer.effects.ts | 2 +- src/app/core/shared/operators.ts | 2 +- src/app/shared/host-window.service.ts | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index bfaac33c1c..0483143230 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -1,4 +1,4 @@ -import { mergeMap, filter, map, first, tap } from 'rxjs/operators'; +import { mergeMap, filter, map } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index faaf3b9fb5..fd5a75e7d1 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -6,7 +6,7 @@ import { Subject, Subscription } from 'rxjs'; -import { switchMap, distinctUntilChanged, first, map, take } from 'rxjs/operators'; +import { switchMap, distinctUntilChanged, map, take } from 'rxjs/operators'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts index ec239e3628..dcc01f2b46 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts @@ -1,5 +1,5 @@ -import { first, take } from 'rxjs/operators'; +import { take } from 'rxjs/operators'; import { Component, Input, OnInit } from '@angular/core'; import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; import { SearchFilterService } from './search-filter.service'; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 98e0d614ae..30a8f01251 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { filter, first, map, take } from 'rxjs/operators'; +import { filter, map, take } from 'rxjs/operators'; import { AfterViewInit, ChangeDetectionStrategy, diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts index 56a5411ef2..1e68802af8 100644 --- a/src/app/core/auth/auth.effects.ts +++ b/src/app/core/auth/auth.effects.ts @@ -1,6 +1,6 @@ import { of as observableOf, Observable } from 'rxjs'; -import { filter, debounceTime, switchMap, take, tap, catchError, map, first } from 'rxjs/operators'; +import { filter, debounceTime, switchMap, take, tap, catchError, map } from 'rxjs/operators'; import { Injectable } from '@angular/core'; // import @ngrx diff --git a/src/app/core/auth/server-auth.service.ts b/src/app/core/auth/server-auth.service.ts index e11800233e..868d444c26 100644 --- a/src/app/core/auth/server-auth.service.ts +++ b/src/app/core/auth/server-auth.service.ts @@ -1,4 +1,4 @@ -import { first, map, switchMap, take } from 'rxjs/operators'; +import { map, switchMap, take } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts index 40f41be14d..af30646f53 100644 --- a/src/app/core/cache/object-cache.service.ts +++ b/src/app/core/cache/object-cache.service.ts @@ -1,6 +1,6 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { distinctUntilChanged, filter, first, map, mergeMap, take, } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, mergeMap, take, } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { MemoizedSelector, select, Store } from '@ngrx/store'; import { IndexName } from '../index/index.reducer'; diff --git a/src/app/core/cache/server-sync-buffer.effects.ts b/src/app/core/cache/server-sync-buffer.effects.ts index d0a194705b..0d7392e555 100644 --- a/src/app/core/cache/server-sync-buffer.effects.ts +++ b/src/app/core/cache/server-sync-buffer.effects.ts @@ -1,4 +1,4 @@ -import { delay, exhaustMap, first, map, switchMap, take, tap } from 'rxjs/operators'; +import { delay, exhaustMap, map, switchMap, take } from 'rxjs/operators'; import { Inject, Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index 09cb86e010..64f4ffa570 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -1,5 +1,5 @@ import { Observable } from 'rxjs'; -import { filter, find, first, flatMap, map, tap } from 'rxjs/operators'; +import { filter, find, flatMap, map, tap } from 'rxjs/operators'; import { hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util'; import { DSOSuccessResponse, RestResponse } from '../cache/response.models'; import { RemoteData } from '../data/remote-data'; diff --git a/src/app/shared/host-window.service.ts b/src/app/shared/host-window.service.ts index 840b134996..146c616e38 100644 --- a/src/app/shared/host-window.service.ts +++ b/src/app/shared/host-window.service.ts @@ -1,6 +1,6 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { filter, distinctUntilChanged, map, first } from 'rxjs/operators'; +import { filter, distinctUntilChanged, map } from 'rxjs/operators'; import { HostWindowState } from './host-window.reducer'; import { Injectable } from '@angular/core'; import { createSelector, select, Store } from '@ngrx/store'; From 586a4ac35b002778e3d25753f9960b4fa1284c52 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 17 Jan 2019 14:59:48 +0100 Subject: [PATCH 55/57] add docs, rename classes for clarity --- ....ts => normalized-object-build.service.ts} | 32 +++++++++++++------ src/app/core/cache/object-cache.reducer.ts | 10 ++++++ src/app/core/core.module.ts | 8 ++--- .../data/base-response-parsing.service.ts | 2 +- src/app/core/data/change-analyzer.ts | 20 ++++++++++++ src/app/core/data/collection-data.service.ts | 8 ++--- src/app/core/data/comcol-data.service.spec.ts | 10 +++--- src/app/core/data/community-data.service.ts | 8 ++--- src/app/core/data/data.service.spec.ts | 16 +++++----- src/app/core/data/data.service.ts | 19 ++++++++--- .../core/data/dso-change-analyzer.service.ts | 26 +++++++++++++++ src/app/core/data/dso-update-comparator.ts | 12 ------- .../data/dspace-object-data.service.spec.ts | 4 +-- .../core/data/dspace-object-data.service.ts | 12 +++---- src/app/core/data/item-data.service.spec.ts | 4 +-- src/app/core/data/item-data.service.ts | 8 ++--- src/app/core/data/request.reducer.ts | 12 ++++++- src/app/core/data/update-comparator.ts | 6 ---- .../dspace-rest-v2/dspace-rest-v2.service.ts | 8 +++++ .../core/metadata/metadata.service.spec.ts | 8 ++--- .../comcol-form/comcol-form.component.ts | 3 ++ src/app/shared/testing/utils.ts | 8 +++++ src/config/auto-sync-config.interface.ts | 19 +++++++++++ 23 files changed, 186 insertions(+), 77 deletions(-) rename src/app/core/cache/builders/{data-build.service.ts => normalized-object-build.service.ts} (63%) create mode 100644 src/app/core/data/change-analyzer.ts create mode 100644 src/app/core/data/dso-change-analyzer.service.ts delete mode 100644 src/app/core/data/dso-update-comparator.ts delete mode 100644 src/app/core/data/update-comparator.ts diff --git a/src/app/core/cache/builders/data-build.service.ts b/src/app/core/cache/builders/normalized-object-build.service.ts similarity index 63% rename from src/app/core/cache/builders/data-build.service.ts rename to src/app/core/cache/builders/normalized-object-build.service.ts index 8ba3ebee0c..9d97ccda75 100644 --- a/src/app/core/cache/builders/data-build.service.ts +++ b/src/app/core/cache/builders/normalized-object-build.service.ts @@ -3,24 +3,38 @@ import { NormalizedObject } from '../models/normalized-object.model'; import { CacheableObject } from '../object-cache.reducer'; import { getRelationships } from './build-decorators'; import { NormalizedObjectFactory } from '../models/normalized-object-factory'; -import { map, take } from 'rxjs/operators'; import { hasValue, isNotEmpty } from '../../../shared/empty.util'; -import { PaginatedList } from '../../data/paginated-list'; -export function isRestDataObject(halObj: any) { +/** + * Return true if halObj has a value for `_links.self` + * + * @param {any} halObj The object to test + */ +export function isRestDataObject(halObj: any): boolean { return isNotEmpty(halObj._links) && hasValue(halObj._links.self); } -export function isRestPaginatedList(halObj: any) { +/** + * Return true if halObj has a value for `page` and `_embedded` + * + * @param {any} halObj The object to test + */ +export function isRestPaginatedList(halObj: any): boolean { return hasValue(halObj.page) && hasValue(halObj._embedded); } -export function isPaginatedList(halObj: any) { - return hasValue(halObj.page) && hasValue(halObj.pageInfo); -} - +/** + * A service to turn domain models in to their normalized + * counterparts. + */ @Injectable() -export class DataBuildService { +export class NormalizedObjectBuildService { + + /** + * Returns the normalized model that corresponds to the given domain model + * + * @param {TDomain} domainModel a domain model + */ normalize(domainModel: TDomain): TNormalized { const normalizedConstructor = NormalizedObjectFactory.getConstructor(domainModel.type); const relationships = getRelationships(normalizedConstructor) || []; diff --git a/src/app/core/cache/object-cache.reducer.ts b/src/app/core/cache/object-cache.reducer.ts index 991ae5466e..982c77341e 100644 --- a/src/app/core/cache/object-cache.reducer.ts +++ b/src/app/core/cache/object-cache.reducer.ts @@ -17,8 +17,18 @@ export enum DirtyType { Deleted = 'Deleted' } +/** + * An interface to represent a JsonPatch + */ export interface Patch { + /** + * The identifier for this Patch + */ uuid?: string; + + /** + * the list of operations this Patch is composed of + */ operations: Operation[]; } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index b1904782bd..0868773550 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -65,8 +65,8 @@ import { BrowseItemsResponseParsingService } from './data/browse-items-response- import { DSpaceObjectDataService } from './data/dspace-object-data.service'; import { CSSVariableService } from '../shared/sass-helper/sass-helper.service'; import { MenuService } from '../shared/menu/menu.service'; -import { DataBuildService } from './cache/builders/data-build.service'; -import { DSOUpdateComparator } from './data/dso-update-comparator'; +import { NormalizedObjectBuildService } from './cache/builders/normalized-object-build.service'; +import { DSOChangeAnalyzer } from './data/dso-change-analyzer.service'; const IMPORTS = [ CommonModule, @@ -103,7 +103,7 @@ const PROVIDERS = [ ObjectCacheService, PaginationComponentOptions, RegistryService, - DataBuildService, + NormalizedObjectBuildService, RemoteDataBuildService, RequestService, EndpointMapResponseParsingService, @@ -131,7 +131,7 @@ const PROVIDERS = [ UploaderService, UUIDService, DSpaceObjectDataService, - DSOUpdateComparator, + DSOChangeAnalyzer, CSSVariableService, MenuService, // register AuthInterceptor as HttpInterceptor diff --git a/src/app/core/data/base-response-parsing.service.ts b/src/app/core/data/base-response-parsing.service.ts index d5c1c58296..925caa495c 100644 --- a/src/app/core/data/base-response-parsing.service.ts +++ b/src/app/core/data/base-response-parsing.service.ts @@ -8,7 +8,7 @@ import { GenericConstructor } from '../shared/generic-constructor'; import { PaginatedList } from './paginated-list'; import { ResourceType } from '../shared/resource-type'; import { RESTURLCombiner } from '../url-combiner/rest-url-combiner'; -import { isRestDataObject, isRestPaginatedList } from '../cache/builders/data-build.service'; +import { isRestDataObject, isRestPaginatedList } from '../cache/builders/normalized-object-build.service'; /* tslint:disable:max-classes-per-file */ export abstract class BaseResponseParsingService { diff --git a/src/app/core/data/change-analyzer.ts b/src/app/core/data/change-analyzer.ts new file mode 100644 index 0000000000..caf9e38c7c --- /dev/null +++ b/src/app/core/data/change-analyzer.ts @@ -0,0 +1,20 @@ +import { NormalizedObject } from '../cache/models/normalized-object.model'; +import { Operation } from 'fast-json-patch/lib/core'; + +/** + * An interface to determine what differs between two + * NormalizedObjects + */ +export interface ChangeAnalyzer { + + /** + * Compare two objects and return their differences as a + * JsonPatch Operation Array + * + * @param {NormalizedObject} object1 + * The first object to compare + * @param {NormalizedObject} object2 + * The second object to compare + */ + diff(object1: TNormalized, object2: TNormalized): Operation[]; +} diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index b08b1005b7..e8a682ba0e 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -11,8 +11,8 @@ import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; -import { DataBuildService } from '../cache/builders/data-build.service'; -import { DSOUpdateComparator } from './dso-update-comparator'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; @Injectable() export class CollectionDataService extends ComColDataService { @@ -21,14 +21,14 @@ export class CollectionDataService extends ComColDataService, protected cds: CommunityDataService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: DSOUpdateComparator + protected comparator: DSOChangeAnalyzer ) { super(); } diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index ca6c8dd855..1b520d83ce 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -16,8 +16,8 @@ import { RequestEntry } from './request.reducer'; import { of as observableOf } from 'rxjs'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; -import { DataBuildService } from '../cache/builders/data-build.service'; -import { DSOUpdateComparator } from './dso-update-comparator'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; const LINK_NAME = 'test'; @@ -30,7 +30,7 @@ class TestService extends ComColDataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, - protected dataBuildService: DataBuildService, + protected dataBuildService: NormalizedObjectBuildService, protected store: Store, protected EnvConfig: GlobalConfig, protected cds: CommunityDataService, @@ -38,7 +38,7 @@ class TestService extends ComColDataService { protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: DSOUpdateComparator, + protected comparator: DSOChangeAnalyzer, protected linkPath: string ) { super(); @@ -61,7 +61,7 @@ describe('ComColDataService', () => { const notificationsService = {} as NotificationsService; const http = {} as HttpClient; const comparator = {} as any; - const dataBuildService = {} as DataBuildService; + const dataBuildService = {} as NormalizedObjectBuildService; const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; const options = Object.assign(new FindAllOptions(), { diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 63fbe3a21a..d09a0b9757 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -17,8 +17,8 @@ import { Observable } from 'rxjs'; import { PaginatedList } from './paginated-list'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; -import { DataBuildService } from '../cache/builders/data-build.service'; -import { DSOUpdateComparator } from './dso-update-comparator'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; @Injectable() export class CommunityDataService extends ComColDataService { @@ -29,13 +29,13 @@ export class CommunityDataService extends ComColDataService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: DSOUpdateComparator + protected comparator: DSOChangeAnalyzer ) { super(); } diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index 9a690e3c4b..9b39f0b68e 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -11,9 +11,9 @@ import { SortDirection, SortOptions } from '../cache/models/sort-options.model'; import { ObjectCacheService } from '../cache/object-cache.service'; import { Operation } from '../../../../node_modules/fast-json-patch'; import { DSpaceObject } from '../shared/dspace-object.model'; -import { UpdateComparator } from './update-comparator'; +import { ChangeAnalyzer } from './change-analyzer'; import { HttpClient } from '@angular/common/http'; -import { DataBuildService } from '../cache/builders/data-build.service'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { compare } from 'fast-json-patch'; @@ -27,14 +27,14 @@ class TestService extends DataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, - protected dataBuildService: DataBuildService, + protected dataBuildService: NormalizedObjectBuildService, protected store: Store, protected linkPath: string, protected halService: HALEndpointService, protected objectCache: ObjectCacheService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: UpdateComparator + protected comparator: ChangeAnalyzer ) { super(); } @@ -44,8 +44,8 @@ class TestService extends DataService { } } -class DummyComparator implements UpdateComparator { - compare(object1: NormalizedTestObject, object2: NormalizedTestObject): Operation[] { +class DummyChangeAnalyzer implements ChangeAnalyzer { + diff(object1: NormalizedTestObject, object2: NormalizedTestObject): Operation[] { return compare((object1 as any).metadata, (object2 as any).metadata); } @@ -58,10 +58,10 @@ describe('DataService', () => { const rdbService = {} as RemoteDataBuildService; const notificationsService = {} as NotificationsService; const http = {} as HttpClient; - const comparator = new DummyComparator() as any; + const comparator = new DummyChangeAnalyzer() as any; const dataBuildService = { normalize: (object) => object - } as DataBuildService; + } as NormalizedObjectBuildService; const objectCache = { addPatch: () => { /* empty */ diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 47be86c296..045e82fbb6 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -41,20 +41,20 @@ import { NotificationOptions } from '../../shared/notifications/models/notificat import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer'; import { NormalizedObjectFactory } from '../cache/models/normalized-object-factory'; import { CacheableObject } from '../cache/object-cache.reducer'; -import { DataBuildService } from '../cache/builders/data-build.service'; -import { UpdateComparator } from './update-comparator'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { ChangeAnalyzer } from './change-analyzer'; export abstract class DataService { protected abstract requestService: RequestService; protected abstract rdbService: RemoteDataBuildService; - protected abstract dataBuildService: DataBuildService; + protected abstract dataBuildService: NormalizedObjectBuildService; protected abstract store: Store; protected abstract linkPath: string; protected abstract halService: HALEndpointService; protected abstract objectCache: ObjectCacheService; protected abstract notificationsService: NotificationsService; protected abstract http: HttpClient; - protected abstract comparator: UpdateComparator; + protected abstract comparator: ChangeAnalyzer; public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable @@ -138,7 +138,7 @@ export abstract class DataService { const newVersion = this.dataBuildService.normalize(object); - const operations = this.comparator.compare(oldVersion, newVersion); + const operations = this.comparator.diff(oldVersion, newVersion); if (isNotEmpty(operations)) { this.objectCache.addPatch(object.self, operations); } @@ -148,6 +148,15 @@ export abstract class DataService> { const requestId = this.requestService.generateRequestId(); const endpoint$ = this.halService.getEndpoint(this.linkPath).pipe( diff --git a/src/app/core/data/dso-change-analyzer.service.ts b/src/app/core/data/dso-change-analyzer.service.ts new file mode 100644 index 0000000000..a47359e5c0 --- /dev/null +++ b/src/app/core/data/dso-change-analyzer.service.ts @@ -0,0 +1,26 @@ +import { Operation } from 'fast-json-patch/lib/core'; +import { compare } from 'fast-json-patch'; +import { ChangeAnalyzer } from './change-analyzer'; +import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model'; +import { Injectable } from '@angular/core'; + +/** + * A class to determine what differs between two + * DSpaceObjects + */ +@Injectable() +export class DSOChangeAnalyzer implements ChangeAnalyzer { + + /** + * Compare the metadata of two DSpaceObjects and return the differences as + * a JsonPatch Operation Array + * + * @param {NormalizedDSpaceObject} object1 + * The first object to compare + * @param {NormalizedDSpaceObject} object2 + * The second object to compare + */ + diff(object1: NormalizedDSpaceObject, object2: NormalizedDSpaceObject): Operation[] { + return compare(object1.metadata, object2.metadata).map((operation: Operation) => Object.assign({}, operation, { path: '/metadata' + operation.path })); + } +} diff --git a/src/app/core/data/dso-update-comparator.ts b/src/app/core/data/dso-update-comparator.ts deleted file mode 100644 index 245fbfaef0..0000000000 --- a/src/app/core/data/dso-update-comparator.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Operation } from 'fast-json-patch/lib/core'; -import { compare } from 'fast-json-patch'; -import { UpdateComparator } from './update-comparator'; -import { NormalizedDSpaceObject } from '../cache/models/normalized-dspace-object.model'; -import { Injectable } from '@angular/core'; - -@Injectable() -export class DSOUpdateComparator implements UpdateComparator { - compare(object1: NormalizedDSpaceObject, object2: NormalizedDSpaceObject): Operation[] { - return compare(object1.metadata, object2.metadata).map((operation: Operation) => Object.assign({}, operation, { path: '/metadata' + operation.path })); - } -} diff --git a/src/app/core/data/dspace-object-data.service.spec.ts b/src/app/core/data/dspace-object-data.service.spec.ts index 2d478b8f73..7047db6065 100644 --- a/src/app/core/data/dspace-object-data.service.spec.ts +++ b/src/app/core/data/dspace-object-data.service.spec.ts @@ -9,7 +9,7 @@ import { DSpaceObjectDataService } from './dspace-object-data.service'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; -import { DataBuildService } from '../cache/builders/data-build.service'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; describe('DSpaceObjectDataService', () => { let scheduler: TestScheduler; @@ -46,7 +46,7 @@ describe('DSpaceObjectDataService', () => { const notificationsService = {} as NotificationsService; const http = {} as HttpClient; const comparator = {} as any; - const dataBuildService = {} as DataBuildService; + const dataBuildService = {} as NormalizedObjectBuildService; service = new DSpaceObjectDataService( requestService, diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index 9a069c4d61..d485fd0bc4 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -13,8 +13,8 @@ import { FindAllOptions } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; -import { DataBuildService } from '../cache/builders/data-build.service'; -import { DSOUpdateComparator } from './dso-update-comparator'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; /* tslint:disable:max-classes-per-file */ class DataServiceImpl extends DataService { @@ -23,13 +23,13 @@ class DataServiceImpl extends DataService constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, - protected dataBuildService: DataBuildService, + protected dataBuildService: NormalizedObjectBuildService, protected store: Store, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: DSOUpdateComparator) { + protected comparator: DSOChangeAnalyzer) { super(); } @@ -50,12 +50,12 @@ export class DSpaceObjectDataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, - protected dataBuildService: DataBuildService, + protected dataBuildService: NormalizedObjectBuildService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: DSOUpdateComparator) { + protected comparator: DSOChangeAnalyzer) { this.dataService = new DataServiceImpl(requestService, rdbService, dataBuildService, null, objectCache, halService, notificationsService, http, comparator); } diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts index 1be361cb9d..0cac6f901d 100644 --- a/src/app/core/data/item-data.service.spec.ts +++ b/src/app/core/data/item-data.service.spec.ts @@ -11,7 +11,7 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { FindAllOptions } from './request.models'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; -import { DataBuildService } from '../cache/builders/data-build.service'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; describe('ItemDataService', () => { let scheduler: TestScheduler; @@ -40,7 +40,7 @@ describe('ItemDataService', () => { const notificationsService = {} as NotificationsService; const http = {} as HttpClient; const comparator = {} as any; - const dataBuildService = {} as DataBuildService; + const dataBuildService = {} as NormalizedObjectBuildService; function initMockBrowseService(isSuccessful: boolean) { const obs = isSuccessful ? diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 2fb2c017dc..32cb00c1a9 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -18,8 +18,8 @@ import { FindAllOptions } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; -import { DataBuildService } from '../cache/builders/data-build.service'; -import { DSOUpdateComparator } from './dso-update-comparator'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; @Injectable() export class ItemDataService extends DataService { @@ -28,14 +28,14 @@ export class ItemDataService extends DataService { constructor( protected requestService: RequestService, protected rdbService: RemoteDataBuildService, - protected dataBuildService: DataBuildService, + protected dataBuildService: NormalizedObjectBuildService, protected store: Store, private bs: BrowseService, protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: DSOUpdateComparator) { + protected comparator: DSOChangeAnalyzer) { super(); } diff --git a/src/app/core/data/request.reducer.ts b/src/app/core/data/request.reducer.ts index a680de2d6b..27b5a4188a 100644 --- a/src/app/core/data/request.reducer.ts +++ b/src/app/core/data/request.reducer.ts @@ -86,7 +86,17 @@ function completeRequest(state: RequestState, action: RequestCompleteAction): Re }); } -function resetResponseTimestamps(state: RequestState, action: ResetResponseTimestampsAction) { +/** + * Reset the timeAdded property of all responses + * + * @param state + * the current state + * @param action + * a RequestCompleteAction + * @return RequestState + * the new state, with the timeAdded property reset + */ +function resetResponseTimestamps(state: RequestState, action: ResetResponseTimestampsAction): RequestState { const newState = Object.create(null); Object.keys(state).forEach((key) => { newState[key] = Object.assign({}, state[key], diff --git a/src/app/core/data/update-comparator.ts b/src/app/core/data/update-comparator.ts deleted file mode 100644 index f064d7f3f2..0000000000 --- a/src/app/core/data/update-comparator.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { NormalizedObject } from '../cache/models/normalized-object.model'; -import { Operation } from 'fast-json-patch/lib/core'; - -export interface UpdateComparator { - compare(object1: TNormalized, object2: TNormalized): Operation[]; -} diff --git a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts index 59038228bb..20d6b1dfb3 100644 --- a/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts +++ b/src/app/core/dspace-rest-v2/dspace-rest-v2.service.ts @@ -79,6 +79,14 @@ export class DSpaceRESTv2Service { })); } + /** + * Create a FormData object from a DSpaceObject + * + * @param {DSpaceObject} dso + * the DSpaceObject + * @return {FormData} + * the result + */ buildFormData(dso: DSpaceObject): FormData { const form: FormData = new FormData(); form.append('name', dso.name); diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index ef1d69b8b5..90c811db7a 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -35,8 +35,8 @@ import { AuthService } from '../auth/auth.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { EmptyError } from 'rxjs/internal-compatibility'; -import { DataBuildService } from '../cache/builders/data-build.service'; -import { DSOUpdateComparator } from '../data/dso-update-comparator'; +import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; +import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service'; /* tslint:disable:max-classes-per-file */ @Component({ @@ -119,8 +119,8 @@ describe('MetadataService', () => { { provide: AuthService, useValue: {} }, { provide: NotificationsService, useValue: {} }, { provide: HttpClient, useValue: {} }, - { provide: DataBuildService, useValue: {} }, - { provide: DSOUpdateComparator, useValue: {} }, + { provide: NormalizedObjectBuildService, useValue: {} }, + { provide: DSOChangeAnalyzer, useValue: {} }, Meta, Title, ItemDataService, diff --git a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts index 19050e2bc2..17710fd1c6 100644 --- a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts +++ b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts @@ -11,6 +11,9 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { isNotEmpty } from '../../empty.util'; import { ResourceType } from '../../../core/shared/resource-type'; +/** + * A form for creating and editing Communities or Collections + */ @Component({ selector: 'ds-comcol-form', styleUrls: ['./comcol-form.component.scss'], diff --git a/src/app/shared/testing/utils.ts b/src/app/shared/testing/utils.ts index cd17a1b1f5..770a554439 100644 --- a/src/app/shared/testing/utils.ts +++ b/src/app/shared/testing/utils.ts @@ -31,6 +31,14 @@ export const createTestComponent = (html: string, type: { new(...args: any[]) return fixture as ComponentFixture; }; +/** + * Allows you to spy on a read only property + * + * @param obj + * The object to spy on + * @param prop + * The property to spy on + */ export function spyOnOperator(obj: any, prop: string): any { const oldProp = obj[prop]; Object.defineProperty(obj, prop, { diff --git a/src/config/auto-sync-config.interface.ts b/src/config/auto-sync-config.interface.ts index 5285916b12..90e7ebee80 100644 --- a/src/config/auto-sync-config.interface.ts +++ b/src/config/auto-sync-config.interface.ts @@ -1,11 +1,30 @@ import { RestRequestMethod } from '../app/core/data/rest-request-method'; +/** + * The number of seconds between automatic syncs to the + * server for requests using a certain HTTP Method + */ type TimePerMethod = { [method in RestRequestMethod]: number; }; +/** + * The config that determines how the automatic syncing + * of changed data to the server works + */ export interface AutoSyncConfig { + /** + * The number of seconds between automatic syncs to the server + */ defaultTime: number; + + /** + * HTTP Method specific overrides of defaultTime + */ timePerMethod: TimePerMethod; + + /** + * The max number of requests in the buffer before a sync to the server + */ maxBufferSize: number; }; From c4004eaaa2d3f4f4a530fb6449844fab00922d6e Mon Sep 17 00:00:00 2001 From: lotte Date: Tue, 22 Jan 2019 09:46:55 +0100 Subject: [PATCH 56/57] fixes for conflicts with master --- .../item-delete/item-delete.component.spec.ts | 38 +++++++++---------- .../item-delete/item-delete.component.ts | 10 ++--- .../item-private.component.spec.ts | 2 +- .../item-private/item-private.component.ts | 12 +++--- .../item-public/item-public.component.spec.ts | 36 +++++++++--------- .../item-public/item-public.component.ts | 12 +++--- .../item-reinstate.component.spec.ts | 36 +++++++++--------- .../item-reinstate.component.ts | 12 +++--- .../item-withdraw.component.spec.ts | 36 +++++++++--------- .../item-withdraw/item-withdraw.component.ts | 12 +++--- ...tract-simple-item-action.component.spec.ts | 38 +++++++++---------- .../abstract-simple-item-action.component.ts | 2 +- src/app/core/auth/auth-request.service.ts | 2 - src/app/core/data/item-data.service.spec.ts | 7 ++++ 14 files changed, 130 insertions(+), 125 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts index 50c494128a..c03ae7c3fe 100644 --- a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts @@ -1,22 +1,22 @@ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {Item} from '../../../core/shared/item.model'; -import {RouterStub} from '../../../shared/testing/router-stub'; -import {of as observableOf} from 'rxjs'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {RemoteData} from '../../../core/data/remote-data'; -import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub'; -import {CommonModule} from '@angular/common'; -import {FormsModule} from '@angular/forms'; -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 {ItemDataService} from '../../../core/data/item-data.service'; -import {NotificationsService} from '../../../shared/notifications/notifications.service'; -import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; -import {By} from '@angular/platform-browser'; -import {ItemDeleteComponent} from './item-delete.component'; -import {getItemEditPath} from '../../item-page-routing.module'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Item } from '../../../core/shared/item.model'; +import { RouterStub } from '../../../shared/testing/router-stub'; +import { of as observableOf } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +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 { ItemDataService } from '../../../core/data/item-data.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { ItemDeleteComponent } from './item-delete.component'; +import { getItemEditPath } from '../../item-page-routing.module'; +import { RestResponse } from '../../../core/cache/response.models'; let comp: ItemDeleteComponent; let fixture: ComponentFixture; diff --git a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts index 68c5738f7d..95f25c67bc 100644 --- a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts +++ b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts @@ -1,8 +1,8 @@ -import {Component} from '@angular/core'; -import {first} from 'rxjs/operators'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component'; -import {getItemEditPath} from '../../item-page-routing.module'; +import { Component } from '@angular/core'; +import { first } from 'rxjs/operators'; +import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; +import { getItemEditPath } from '../../item-page-routing.module'; +import { RestResponse } from '../../../core/cache/response.models'; @Component({ selector: 'ds-item-delete', diff --git a/src/app/+item-page/edit-item-page/item-private/item-private.component.spec.ts b/src/app/+item-page/edit-item-page/item-private/item-private.component.spec.ts index 5b99ced743..9f9447704b 100644 --- a/src/app/+item-page/edit-item-page/item-private/item-private.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-private/item-private.component.spec.ts @@ -2,7 +2,6 @@ import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {Item} from '../../../core/shared/item.model'; import {RouterStub} from '../../../shared/testing/router-stub'; import {of as observableOf} from 'rxjs'; -import {RestResponse} from '../../../core/cache/response-cache.models'; import {RemoteData} from '../../../core/data/remote-data'; import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub'; import {CommonModule} from '@angular/common'; @@ -16,6 +15,7 @@ import {NotificationsService} from '../../../shared/notifications/notifications. import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; import {By} from '@angular/platform-browser'; import {ItemPrivateComponent} from './item-private.component'; +import { RestResponse } from '../../../core/cache/response.models'; let comp: ItemPrivateComponent; let fixture: ComponentFixture; diff --git a/src/app/+item-page/edit-item-page/item-private/item-private.component.ts b/src/app/+item-page/edit-item-page/item-private/item-private.component.ts index f1e7600c18..d949e4fa6e 100644 --- a/src/app/+item-page/edit-item-page/item-private/item-private.component.ts +++ b/src/app/+item-page/edit-item-page/item-private/item-private.component.ts @@ -1,9 +1,9 @@ -import {Component} from '@angular/core'; -import {first} from 'rxjs/operators'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component'; -import {RemoteData} from '../../../core/data/remote-data'; -import {Item} from '../../../core/shared/item.model'; +import { Component } from '@angular/core'; +import { first } from 'rxjs/operators'; +import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Item } from '../../../core/shared/item.model'; +import { RestResponse } from '../../../core/cache/response.models'; @Component({ selector: 'ds-item-private', diff --git a/src/app/+item-page/edit-item-page/item-public/item-public.component.spec.ts b/src/app/+item-page/edit-item-page/item-public/item-public.component.spec.ts index 182d3ffabe..97c81681d0 100644 --- a/src/app/+item-page/edit-item-page/item-public/item-public.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-public/item-public.component.spec.ts @@ -1,21 +1,21 @@ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {Item} from '../../../core/shared/item.model'; -import {RouterStub} from '../../../shared/testing/router-stub'; -import {of as observableOf} from 'rxjs'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {RemoteData} from '../../../core/data/remote-data'; -import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub'; -import {CommonModule} from '@angular/common'; -import {FormsModule} from '@angular/forms'; -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 {ItemDataService} from '../../../core/data/item-data.service'; -import {NotificationsService} from '../../../shared/notifications/notifications.service'; -import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; -import {By} from '@angular/platform-browser'; -import {ItemPublicComponent} from './item-public.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Item } from '../../../core/shared/item.model'; +import { RouterStub } from '../../../shared/testing/router-stub'; +import { of as observableOf } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +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 { ItemDataService } from '../../../core/data/item-data.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { ItemPublicComponent } from './item-public.component'; +import { RestResponse } from '../../../core/cache/response.models'; let comp: ItemPublicComponent; let fixture: ComponentFixture; diff --git a/src/app/+item-page/edit-item-page/item-public/item-public.component.ts b/src/app/+item-page/edit-item-page/item-public/item-public.component.ts index d65d0f171d..272cf9a96f 100644 --- a/src/app/+item-page/edit-item-page/item-public/item-public.component.ts +++ b/src/app/+item-page/edit-item-page/item-public/item-public.component.ts @@ -1,9 +1,9 @@ -import {Component} from '@angular/core'; -import {first} from 'rxjs/operators'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component'; -import {RemoteData} from '../../../core/data/remote-data'; -import {Item} from '../../../core/shared/item.model'; +import { Component } from '@angular/core'; +import { first } from 'rxjs/operators'; +import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Item } from '../../../core/shared/item.model'; +import { RestResponse } from '../../../core/cache/response.models'; @Component({ selector: 'ds-item-public', diff --git a/src/app/+item-page/edit-item-page/item-reinstate/item-reinstate.component.spec.ts b/src/app/+item-page/edit-item-page/item-reinstate/item-reinstate.component.spec.ts index dbea7f3e69..e89eda736f 100644 --- a/src/app/+item-page/edit-item-page/item-reinstate/item-reinstate.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-reinstate/item-reinstate.component.spec.ts @@ -1,21 +1,21 @@ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {Item} from '../../../core/shared/item.model'; -import {RouterStub} from '../../../shared/testing/router-stub'; -import {of as observableOf} from 'rxjs'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {RemoteData} from '../../../core/data/remote-data'; -import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub'; -import {CommonModule} from '@angular/common'; -import {FormsModule} from '@angular/forms'; -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 {ItemDataService} from '../../../core/data/item-data.service'; -import {NotificationsService} from '../../../shared/notifications/notifications.service'; -import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; -import {By} from '@angular/platform-browser'; -import {ItemReinstateComponent} from './item-reinstate.component'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Item } from '../../../core/shared/item.model'; +import { RouterStub } from '../../../shared/testing/router-stub'; +import { of as observableOf } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +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 { ItemDataService } from '../../../core/data/item-data.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { ItemReinstateComponent } from './item-reinstate.component'; +import { RestResponse } from '../../../core/cache/response.models'; let comp: ItemReinstateComponent; let fixture: ComponentFixture; diff --git a/src/app/+item-page/edit-item-page/item-reinstate/item-reinstate.component.ts b/src/app/+item-page/edit-item-page/item-reinstate/item-reinstate.component.ts index 5c710b0a81..9c0e1c8d05 100644 --- a/src/app/+item-page/edit-item-page/item-reinstate/item-reinstate.component.ts +++ b/src/app/+item-page/edit-item-page/item-reinstate/item-reinstate.component.ts @@ -1,9 +1,9 @@ -import {Component} from '@angular/core'; -import {first} from 'rxjs/operators'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component'; -import {RemoteData} from '../../../core/data/remote-data'; -import {Item} from '../../../core/shared/item.model'; +import { Component } from '@angular/core'; +import { first } from 'rxjs/operators'; +import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Item } from '../../../core/shared/item.model'; +import { RestResponse } from '../../../core/cache/response.models'; @Component({ selector: 'ds-item-reinstate', diff --git a/src/app/+item-page/edit-item-page/item-withdraw/item-withdraw.component.spec.ts b/src/app/+item-page/edit-item-page/item-withdraw/item-withdraw.component.spec.ts index e1de52a506..9305459c12 100644 --- a/src/app/+item-page/edit-item-page/item-withdraw/item-withdraw.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-withdraw/item-withdraw.component.spec.ts @@ -1,21 +1,21 @@ -import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import {Item} from '../../../core/shared/item.model'; -import {RouterStub} from '../../../shared/testing/router-stub'; -import {of as observableOf} from 'rxjs'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {RemoteData} from '../../../core/data/remote-data'; -import {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub'; -import {CommonModule} from '@angular/common'; -import {FormsModule} from '@angular/forms'; -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 {ItemDataService} from '../../../core/data/item-data.service'; -import {NotificationsService} from '../../../shared/notifications/notifications.service'; -import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; -import {ItemWithdrawComponent} from './item-withdraw.component'; -import {By} from '@angular/platform-browser'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Item } from '../../../core/shared/item.model'; +import { RouterStub } from '../../../shared/testing/router-stub'; +import { of as observableOf } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +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 { ItemDataService } from '../../../core/data/item-data.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { ItemWithdrawComponent } from './item-withdraw.component'; +import { By } from '@angular/platform-browser'; +import { RestResponse } from '../../../core/cache/response.models'; let comp: ItemWithdrawComponent; let fixture: ComponentFixture; diff --git a/src/app/+item-page/edit-item-page/item-withdraw/item-withdraw.component.ts b/src/app/+item-page/edit-item-page/item-withdraw/item-withdraw.component.ts index 6924124efc..1fed1756a4 100644 --- a/src/app/+item-page/edit-item-page/item-withdraw/item-withdraw.component.ts +++ b/src/app/+item-page/edit-item-page/item-withdraw/item-withdraw.component.ts @@ -1,9 +1,9 @@ -import {Component} from '@angular/core'; -import {first} from 'rxjs/operators'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {AbstractSimpleItemActionComponent} from '../simple-item-action/abstract-simple-item-action.component'; -import {RemoteData} from '../../../core/data/remote-data'; -import {Item} from '../../../core/shared/item.model'; +import { Component } from '@angular/core'; +import { first } from 'rxjs/operators'; +import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Item } from '../../../core/shared/item.model'; +import { RestResponse } from '../../../core/cache/response.models'; @Component({ selector: 'ds-item-withdraw', diff --git a/src/app/+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.spec.ts b/src/app/+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.spec.ts index 2da671517c..1c4cae552e 100644 --- a/src/app/+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.spec.ts +++ b/src/app/+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.spec.ts @@ -1,22 +1,22 @@ -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 {NotificationsServiceStub} from '../../../shared/testing/notifications-service-stub'; -import {NotificationsService} from '../../../shared/notifications/notifications.service'; -import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; -import {FormsModule} from '@angular/forms'; -import {ItemDataService} from '../../../core/data/item-data.service'; -import {RemoteData} from '../../../core/data/remote-data'; -import {AbstractSimpleItemActionComponent} from './abstract-simple-item-action.component'; -import {By} from '@angular/platform-browser'; -import {RestResponse} from '../../../core/cache/response-cache.models'; -import {of as observableOf} from 'rxjs'; -import {getItemEditPath} from '../../item-page-routing.module'; +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 { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { ItemDataService } from '../../../core/data/item-data.service'; +import { RemoteData } from '../../../core/data/remote-data'; +import { AbstractSimpleItemActionComponent } from './abstract-simple-item-action.component'; +import { By } from '@angular/platform-browser'; +import { of as observableOf } from 'rxjs'; +import { getItemEditPath } from '../../item-page-routing.module'; +import { RestResponse } from '../../../core/cache/response.models'; /** * Test component that implements the AbstractSimpleItemActionComponent used to test the diff --git a/src/app/+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.ts b/src/app/+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.ts index 743b52921f..7773dbb573 100644 --- a/src/app/+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.ts +++ b/src/app/+item-page/edit-item-page/simple-item-action/abstract-simple-item-action.component.ts @@ -8,9 +8,9 @@ import {RemoteData} from '../../../core/data/remote-data'; import {Observable} from 'rxjs'; import {getSucceededRemoteData} from '../../../core/shared/operators'; import {first, map} from 'rxjs/operators'; -import {RestResponse} from '../../../core/cache/response-cache.models'; import {findSuccessfulAccordingTo} from '../edit-item-operators'; import {getItemEditPath} from '../../item-page-routing.module'; +import { RestResponse } from '../../../core/cache/response.models'; /** * Component to render and handle simple item edit actions such as withdrawal and reinstatement. diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index f957d807c1..6d782cbbe2 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -25,8 +25,6 @@ export class AuthRequestService { protected fetchRequest(request: RestRequest): Observable { return this.requestService.getByUUID(request.uuid).pipe( getResponseFromEntry(), - // TODO to review when https://github.com/DSpace/dspace-angular/issues/217 will be fixed - // tap(() => this.responseCache.remove(request.href)), mergeMap((response) => { if (response.isSuccessful && isNotEmpty(response)) { return observableOf((response as AuthStatusResponse).response); diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts index 93ff3b051b..6cf7e503d3 100644 --- a/src/app/core/data/item-data.service.spec.ts +++ b/src/app/core/data/item-data.service.spec.ts @@ -14,6 +14,8 @@ import { RestResponse } from '../cache/response.models'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { HttpClient } from '@angular/common/http'; +import { RequestEntry } from './request.reducer'; +import { of as observableOf } from 'rxjs'; describe('ItemDataService', () => { let scheduler: TestScheduler; @@ -25,6 +27,11 @@ describe('ItemDataService', () => { }, configure(request: RestRequest) { // Do nothing + }, + getByHref(requestHref: string) { + const responseCacheEntry = new RequestEntry(); + responseCacheEntry.response = new RestResponse(true, '200'); + return observableOf(responseCacheEntry); } } as RequestService; const rdbService = {} as RemoteDataBuildService; From b8252a1a8eac709778fc3c21f558e39783460c47 Mon Sep 17 00:00:00 2001 From: lotte Date: Thu, 24 Jan 2019 14:13:22 +0100 Subject: [PATCH 57/57] Fixed feedback --- .../item-delete/item-delete.component.spec.ts | 34 +++++++------------ .../item-delete/item-delete.component.ts | 11 +++--- src/app/core/data/data.service.ts | 10 ++++++ src/app/core/data/item-data.service.spec.ts | 21 ------------ src/app/core/data/item-data.service.ts | 34 ++----------------- src/app/core/data/request.models.ts | 3 ++ 6 files changed, 34 insertions(+), 79 deletions(-) diff --git a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts index c03ae7c3fe..6d435c8de8 100644 --- a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts +++ b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts @@ -27,8 +27,6 @@ let routerStub; let mockItemDataService: ItemDataService; let routeStub; let notificationsServiceStub; -let successfulRestResponse; -let failRestResponse; describe('ItemDeleteComponent', () => { beforeEach(async(() => { @@ -46,14 +44,12 @@ describe('ItemDeleteComponent', () => { }); mockItemDataService = jasmine.createSpyObj('mockItemDataService', { - delete: observableOf(new RestResponse(true, '200')) + delete: observableOf(true) }); routeStub = { data: observableOf({ - item: new RemoteData(false, false, true, null, { - id: 'fake-id' - }) + item: new RemoteData(false, false, true, null, mockItem) }) }; @@ -63,10 +59,10 @@ describe('ItemDeleteComponent', () => { imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()], declarations: [ItemDeleteComponent], providers: [ - {provide: ActivatedRoute, useValue: routeStub}, - {provide: Router, useValue: routerStub}, - {provide: ItemDataService, useValue: mockItemDataService}, - {provide: NotificationsService, useValue: notificationsServiceStub}, + { provide: ActivatedRoute, useValue: routeStub }, + { provide: Router, useValue: routerStub }, + { provide: ItemDataService, useValue: mockItemDataService }, + { provide: NotificationsService, useValue: notificationsServiceStub }, ], schemas: [ CUSTOM_ELEMENTS_SCHEMA ] @@ -74,9 +70,6 @@ describe('ItemDeleteComponent', () => { })); beforeEach(() => { - successfulRestResponse = new RestResponse(true, '200'); - failRestResponse = new RestResponse(false, '500'); - fixture = TestBed.createComponent(ItemDeleteComponent); comp = fixture.componentInstance; fixture.detectChanges(); @@ -95,22 +88,21 @@ describe('ItemDeleteComponent', () => { describe('performAction', () => { it('should call delete function from the ItemDataService', () => { - spyOn(comp, 'processRestResponse'); + spyOn(comp, 'notify'); comp.performAction(); - - expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id); - expect(comp.processRestResponse).toHaveBeenCalled(); + expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem); + expect(comp.notify).toHaveBeenCalled(); }); }); - describe('processRestResponse', () => { + describe('notify', () => { it('should navigate to the homepage on successful deletion of the item', () => { - comp.processRestResponse(successfulRestResponse); + comp.notify(true); expect(routerStub.navigate).toHaveBeenCalledWith(['']); }); }); - describe('processRestResponse', () => { + describe('notify', () => { it('should navigate to the item edit page on failed deletion of the item', () => { - comp.processRestResponse(failRestResponse); + comp.notify(false); expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditPath('fake-id')]); }); }); diff --git a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts index 95f25c67bc..2700b45475 100644 --- a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts +++ b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts @@ -19,20 +19,19 @@ export class ItemDeleteComponent extends AbstractSimpleItemActionComponent { * Perform the delete action to the item */ performAction() { - this.itemDataService.delete(this.item.id).pipe(first()).subscribe( - (response: RestResponse) => { - this.processRestResponse(response); + this.itemDataService.delete(this.item).pipe(first()).subscribe( + (succeeded: boolean) => { + this.notify(succeeded); } ); } /** - * Process the RestResponse retrieved from the server. * When the item is successfully delete, navigate to the homepage, otherwise navigate back to the item edit page * @param response */ - processRestResponse(response: RestResponse) { - if (response.isSuccessful) { + notify(succeeded: boolean) { + if (succeeded) { this.notificationsService.success(this.translateService.get('item.edit.' + this.messageKey + '.success')); this.router.navigate(['']); } else { diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index f4d7bcdf4f..440013c31c 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -95,6 +95,11 @@ export abstract class DataService(hrefObs) as Observable>>; } + /** + * Create the HREF for a specific object based on its identifier + * @param endpoint The base endpoint for the type of object + * @param resourceID The identifier for the object + */ getIDHref(endpoint, resourceID): string { return `${endpoint}/${resourceID}`; } @@ -199,6 +204,11 @@ export abstract class DataService { const requestId = this.requestService.generateRequestId(); diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts index 6cf7e503d3..02c70791b5 100644 --- a/src/app/core/data/item-data.service.spec.ts +++ b/src/app/core/data/item-data.service.spec.ts @@ -162,25 +162,4 @@ describe('ItemDataService', () => { }); }); - describe('getItemDeleteEndpoint', () => { - beforeEach(() => { - scheduler = getTestScheduler(); - service = initTestService(); - }); - - it('should return the endpoint to make an item private or public', () => { - const result = service.getItemDeleteEndpoint(scopeID); - const expected = cold('a', {a: ScopedItemEndpoint}); - - expect(result).toBeObservable(expected); - }); - - it('should delete the item', () => { - const expected = new RestResponse(true, '200'); - const result = service.delete(scopeID); - result.subscribe((v) => expect(v).toEqual(expected)); - - }); - }); - }); diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index c67b49f70d..bd3c42a67c 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -14,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 { DeleteRequest, FindAllOptions, PatchRequest, RestRequest } from './request.models'; +import { FindAllOptions, PatchRequest, RestRequest } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; @@ -64,7 +64,7 @@ export class ItemDataService extends DataService { */ public getItemWithdrawEndpoint(itemId: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getFindByIDHref(endpoint, itemId)) + map((endpoint: string) => this.getIDHref(endpoint, itemId)) ); } @@ -74,17 +74,7 @@ export class ItemDataService extends DataService { */ public getItemDiscoverableEndpoint(itemId: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getFindByIDHref(endpoint, itemId)) - ); - } - - /** - * Get the endpoint to delete the item - * @param itemId - */ - public getItemDeleteEndpoint(itemId: string): Observable { - return this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getFindByIDHref(endpoint, itemId)) + map((endpoint: string) => this.getIDHref(endpoint, itemId)) ); } @@ -129,22 +119,4 @@ export class ItemDataService extends DataService { map((requestEntry: RequestEntry) => requestEntry.response) ); } - - /** - * Delete the item - * @param itemId - */ - public delete(itemId: string) { - return this.getItemDeleteEndpoint(itemId).pipe( - distinctUntilChanged(), - map((endpointURL: string) => - new DeleteRequest(this.requestService.generateRequestId(), endpointURL) - ), - configureRequest(this.requestService), - map((request: RestRequest) => request.href), - getRequestFromRequestHref(this.requestService), - map((requestEntry: RequestEntry) => requestEntry.response) - ); - } - } diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 6f0ff5b605..e4920058ac 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -227,6 +227,9 @@ export class CreateRequest extends PostRequest { } } +/** + * Request to delete an object based on its identifier + */ export class DeleteByIDRequest extends DeleteRequest { constructor( uuid: string,