From 46a0ea10f40539463abb05fbcbbe1aa8da62f444 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Fri, 25 Mar 2022 18:28:26 +0530 Subject: [PATCH 01/75] [CST-5535] WIP --- src/app/health-page/health.module.ts | 12 +++++++++ src/app/health-page/health.routing.module.ts | 25 ++++++++++++++++++ .../health-page/health/health.component.html | 1 + .../health-page/health/health.component.scss | 0 .../health/health.component.spec.ts | 25 ++++++++++++++++++ .../health-page/health/health.component.ts | 26 +++++++++++++++++++ 6 files changed, 89 insertions(+) create mode 100644 src/app/health-page/health.module.ts create mode 100644 src/app/health-page/health.routing.module.ts create mode 100644 src/app/health-page/health/health.component.html create mode 100644 src/app/health-page/health/health.component.scss create mode 100644 src/app/health-page/health/health.component.spec.ts create mode 100644 src/app/health-page/health/health.component.ts diff --git a/src/app/health-page/health.module.ts b/src/app/health-page/health.module.ts new file mode 100644 index 0000000000..46a6642168 --- /dev/null +++ b/src/app/health-page/health.module.ts @@ -0,0 +1,12 @@ +import { NgModule } from '@angular/core'; +import { HealthComponent } from './health/health.component'; + + +@NgModule({ + declarations: [ + HealthComponent + ] + }) + export class HealthModule { + + } \ No newline at end of file diff --git a/src/app/health-page/health.routing.module.ts b/src/app/health-page/health.routing.module.ts new file mode 100644 index 0000000000..70ed2a0e43 --- /dev/null +++ b/src/app/health-page/health.routing.module.ts @@ -0,0 +1,25 @@ +import { RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; +import { HealthComponent } from './health/health.component'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + canActivate: [AuthenticatedGuard], + children: [ + { + path: '', + component: HealthComponent, + }, + ] + }, + + ]) + ] +}) +export class HealthPageRoutingModule { + +} diff --git a/src/app/health-page/health/health.component.html b/src/app/health-page/health/health.component.html new file mode 100644 index 0000000000..f96dc0a28f --- /dev/null +++ b/src/app/health-page/health/health.component.html @@ -0,0 +1 @@ +

health works!

diff --git a/src/app/health-page/health/health.component.scss b/src/app/health-page/health/health.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/health-page/health/health.component.spec.ts b/src/app/health-page/health/health.component.spec.ts new file mode 100644 index 0000000000..9423481b32 --- /dev/null +++ b/src/app/health-page/health/health.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HealthComponent } from './health.component'; + +describe('HealthComponent', () => { + let component: HealthComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ HealthComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/health-page/health/health.component.ts b/src/app/health-page/health/health.component.ts new file mode 100644 index 0000000000..46ff73cc09 --- /dev/null +++ b/src/app/health-page/health/health.component.ts @@ -0,0 +1,26 @@ +import { Component, OnInit } from '@angular/core'; +import { DspaceRestService } from '../../core/dspace-rest/dspace-rest.service'; +import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; + +@Component({ + selector: 'ds-health', + templateUrl: './health.component.html', + styleUrls: ['./health.component.scss'] +}) +export class HealthComponent implements OnInit { + + constructor(protected halService: HALEndpointService, + protected restService: DspaceRestService) { + } + + ngOnInit(): void { + this.halService.getRootHref(); + console.log('this.halService.getRootHref()',); + this.restService.get(this.halService.getRootHref() + '/actuator' + '/health').subscribe((data)=>{ + console.log(data); + + }) + + } + +} From 108f6e60f932e459982e2c8d54b9497d6424c8f5 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Fri, 25 Mar 2022 18:37:25 +0530 Subject: [PATCH 02/75] [CST-5535] WIP --- server.ts | 14 ++++++++++++++ .../admin/admin-sidebar/admin-sidebar.component.ts | 12 ++++++++++++ src/app/app-routing.module.ts | 5 +++++ src/assets/i18n/en.json5 | 2 ++ 4 files changed, 33 insertions(+) diff --git a/server.ts b/server.ts index da3b877bc1..57ab3cb69f 100644 --- a/server.ts +++ b/server.ts @@ -157,6 +157,20 @@ export function app() { */ server.use('/iiif', express.static(IIIF_VIEWER, {index:false})); + /** + * Checking server status + */ + server.get('/app/health', async (req,res) => { + try { + const serverStatus = await https.get(`${environment.rest.baseUrl}/actuator/health`); + res.send(serverStatus); + } catch (error) { + res.send({ + error: error.message + }); + } + }); + // Register the ngApp callback function to handle incoming requests server.get('*', ngApp); diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index dc9d2a817f..431a8785da 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -308,6 +308,18 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { icon: 'terminal', index: 10 }, + { + id: 'health', + active: false, + visible: isSiteAdmin, + model: { + type: MenuItemType.LINK, + text: 'menu.section.health', + link: '/health' + } as LinkMenuItemModel, + icon: 'heartbeat', + index: 11 + }, ]; menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { shouldPersistOnRouteChange: true diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 88f7791b1b..6456547355 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -208,6 +208,11 @@ import { ServerCheckGuard } from './core/server-check/server-check.guard'; loadChildren: () => import('./statistics-page/statistics-page-routing.module') .then((m) => m.StatisticsPageRoutingModule) }, + { + path: 'health', + loadChildren: () => import('./health-page/health.routing.module') + .then((m) => m.HealthPageRoutingModule) + }, { path: ACCESS_CONTROL_MODULE_PATH, loadChildren: () => import('./access-control/access-control.module').then((m) => m.AccessControlModule), diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index f33a195cfe..ba0508bdb5 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2543,6 +2543,8 @@ "menu.section.processes": "Processes", + "menu.section.health": "Health", + "menu.section.registries": "Registries", From 392d0e366d3209ef8e50071b5b8a0bba2c6e35bb Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Tue, 5 Apr 2022 18:46:58 +0530 Subject: [PATCH 03/75] [CST-5535] test cases added. --- package.json | 1 + src/app/app-routing.module.ts | 4 +- src/app/health-page/health-data.service.ts | 32 ++++ src/app/health-page/health.module.ts | 15 +- src/app/health-page/health.routing.module.ts | 3 + .../health-page/health/health.component.html | 40 ++++- .../health-page/health/health.component.scss | 8 + .../health/health.component.spec.ts | 141 +++++++++++++++++- .../health-page/health/health.component.ts | 100 +++++++++++-- src/assets/i18n/en.json5 | 6 + yarn.lock | 5 + 11 files changed, 334 insertions(+), 21 deletions(-) create mode 100644 src/app/health-page/health-data.service.ts diff --git a/package.json b/package.json index 99ab1d2e07..95f659e9eb 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@angular/core": "~11.2.14", "@angular/forms": "~11.2.14", "@angular/localize": "11.2.14", + "@angular/material": "9.2.0", "@angular/platform-browser": "~11.2.14", "@angular/platform-browser-dynamic": "~11.2.14", "@angular/platform-server": "~11.2.14", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 6456547355..80310774c8 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -210,8 +210,8 @@ import { ServerCheckGuard } from './core/server-check/server-check.guard'; }, { path: 'health', - loadChildren: () => import('./health-page/health.routing.module') - .then((m) => m.HealthPageRoutingModule) + loadChildren: () => import('./health-page/health.module') + .then((m) => m.HealthModule) }, { path: ACCESS_CONTROL_MODULE_PATH, diff --git a/src/app/health-page/health-data.service.ts b/src/app/health-page/health-data.service.ts new file mode 100644 index 0000000000..bd905006aa --- /dev/null +++ b/src/app/health-page/health-data.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { DspaceRestService } from '../core/dspace-rest/dspace-rest.service'; +import { RawRestResponse } from '../core/dspace-rest/raw-rest-response.model'; +import { HALEndpointService } from '../core/shared/hal-endpoint.service'; + +@Injectable({ + providedIn: 'root' +}) +export class HealthDataService { + constructor(protected halService: HALEndpointService, + protected restService: DspaceRestService) { + } + /** + * @returns health data + */ + getHealth(): Observable { + return this.halService.getEndpoint('/actuator').pipe( + map((restURL: string) => restURL + '/health'), + switchMap((endpoint: string) => this.restService.get(endpoint))); + } + + /** + * @returns information of server + */ + getInfo(): Observable { + return this.halService.getEndpoint('/actuator').pipe( + map((restURL: string) => restURL + '/info'), + switchMap((endpoint: string) => this.restService.get(endpoint))); + } +} diff --git a/src/app/health-page/health.module.ts b/src/app/health-page/health.module.ts index 46a6642168..8731b77f77 100644 --- a/src/app/health-page/health.module.ts +++ b/src/app/health-page/health.module.ts @@ -1,12 +1,23 @@ +import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { HealthPageRoutingModule } from './health.routing.module'; import { HealthComponent } from './health/health.component'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; @NgModule({ + imports: [ + CommonModule, + HealthPageRoutingModule, + MatExpansionModule, + NgbModule, + TranslateModule + ], declarations: [ HealthComponent ] }) export class HealthModule { - - } \ No newline at end of file + } diff --git a/src/app/health-page/health.routing.module.ts b/src/app/health-page/health.routing.module.ts index 70ed2a0e43..a8d94d9d1f 100644 --- a/src/app/health-page/health.routing.module.ts +++ b/src/app/health-page/health.routing.module.ts @@ -2,12 +2,15 @@ import { RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { HealthComponent } from './health/health.component'; +import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; @NgModule({ imports: [ RouterModule.forChild([ { path: '', + resolve: { breadcrumb: I18nBreadcrumbResolver }, + data: { breadcrumbKey: 'health' }, canActivate: [AuthenticatedGuard], children: [ { diff --git a/src/app/health-page/health/health.component.html b/src/app/health-page/health/health.component.html index f96dc0a28f..05f77225fb 100644 --- a/src/app/health-page/health/health.component.html +++ b/src/app/health-page/health/health.component.html @@ -1 +1,39 @@ -

health works!

+
+ +
+
+ + + + diff --git a/src/app/health-page/health/health.component.scss b/src/app/health-page/health/health.component.scss index e69de29bb2..c085111079 100644 --- a/src/app/health-page/health/health.component.scss +++ b/src/app/health-page/health/health.component.scss @@ -0,0 +1,8 @@ +.mat-expansion-panel-header { + padding-left: 0px; +} + +.circle-red { + color:red; + align-items: center; +} \ No newline at end of file diff --git a/src/app/health-page/health/health.component.spec.ts b/src/app/health-page/health/health.component.spec.ts index 9423481b32..845360f059 100644 --- a/src/app/health-page/health/health.component.spec.ts +++ b/src/app/health-page/health/health.component.spec.ts @@ -1,14 +1,138 @@ +import { CommonModule } from '@angular/common'; import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { MatExpansionModule } from '@angular/material/expansion'; +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { of } from 'rxjs'; +import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; +import { HealthDataService } from '../health-data.service'; +import { HealthPageRoutingModule } from '../health.routing.module'; import { HealthComponent } from './health.component'; -describe('HealthComponent', () => { + function getHealth() { + return of({ + 'payload':{ + 'status':'UP_WITH_ISSUES', + 'components':{ + 'db':{ + 'status':'UP', + 'components':{ + 'dataSource':{ + 'status':'UP', + 'details':{ + 'database':'PostgreSQL', + 'result':1, + 'validationQuery':'SELECT 1' + } + }, + 'dspaceDataSource':{ + 'status':'UP', + 'details':{ + 'database':'PostgreSQL', + 'result':1, + 'validationQuery':'SELECT 1' + } + } + } + }, + 'geoIp':{ + 'status':'UP_WITH_ISSUES', + 'details':{ + 'reason':'The GeoLite Database file is missing (/var/lib/GeoIP/GeoLite2-City.mmdb)! Solr Statistics cannot generate location based reports! Please see the DSpace installation instructions for instructions to install this file.' + } + }, + 'solrOaiCore':{ + 'status':'UP', + 'details':{ + 'status':0, + 'detectedPathType':'particular core' + } + }, + 'solrSearchCore':{ + 'status':'UP', + 'details':{ + 'status':0, + 'detectedPathType':'particular core' + } + }, + 'solrStatisticsCore':{ + 'status':'UP', + 'details':{ + 'status':0, + 'detectedPathType':'particular core' + } + } + } + }, + 'statusCode':200, + 'statusText':'OK' + }); + } + + function getInfo() { + return of({ + 'payload':{ + 'app':{ + 'name':'DSpace at My University', + 'version':'7.3', + 'dir':'/Users/pratikrajkotiya/Documents/Project/FrontEnd/dspace-cris-install', + 'url':'http://localhost:8080/server', + 'db':'jdbc:postgresql://localhost:5432/4science', + 'solr':{ + 'server':'http://localhost:8983/solr', + 'prefix':'' + }, + 'mail':{ + 'server':'smtp.example.com', + 'from-address':'dspace-noreply@myu.edu', + 'feedback-recipient':'dspace-help@myu.edu', + 'mail-admin':'dspace-help@myu.edu', + 'mail-helpdesk':'dspace-help@myu.edu', + 'alert-recipient':'dspace-help@myu.edu' + }, + 'cors':{ + 'allowed-origins':'http://localhost:4000' + }, + 'ui':{ + 'url':'http://localhost:4000' + } + } + }, + 'statusCode':200, + 'statusText':'OK' + }); + } + +function getMockHealthDataService() { + return jasmine.createSpyObj('healthDataService', { + getHealth: getHealth(), + getInfo: getInfo() + }); +} + +fdescribe('HealthComponent', () => { let component: HealthComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ HealthComponent ] + imports: [ + NgbNavModule, + CommonModule, + HealthPageRoutingModule, + MatExpansionModule, + BrowserAnimationsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [ HealthComponent ], + providers:[{ provide: HealthDataService, useValue: getMockHealthDataService() }] }) .compileComponents(); }); @@ -22,4 +146,15 @@ describe('HealthComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should render health tab.', () => { + const healthTab = fixture.debugElement.query(By.css('#health')); + expect(healthTab).toBeTruthy(); + }); + + it('should render info tab.', () => { + const infoTab = fixture.debugElement.query(By.css('#info')); + expect(infoTab).toBeFalsy(); + }); + }); diff --git a/src/app/health-page/health/health.component.ts b/src/app/health-page/health/health.component.ts index 46ff73cc09..89aa6611f6 100644 --- a/src/app/health-page/health/health.component.ts +++ b/src/app/health-page/health/health.component.ts @@ -1,26 +1,100 @@ import { Component, OnInit } from '@angular/core'; -import { DspaceRestService } from '../../core/dspace-rest/dspace-rest.service'; -import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; +import { HealthDataService } from '../health-data.service'; + +enum HealthStatus { + UP = 'UP', + UP_WITH_ISSUES = 'UP_WITH_ISSUES', + DOWN = 'DOWN' +} @Component({ selector: 'ds-health', templateUrl: './health.component.html', styleUrls: ['./health.component.scss'] }) export class HealthComponent implements OnInit { - - constructor(protected halService: HALEndpointService, - protected restService: DspaceRestService) { - } + healthArr: string[]; + serverInfoArr: string[]; + healthGlobalStatus: string; + activeId ='Health'; + constructor(private healthDataService: HealthDataService) { } ngOnInit(): void { - this.halService.getRootHref(); - console.log('this.halService.getRootHref()',); - this.restService.get(this.halService.getRootHref() + '/actuator' + '/health').subscribe((data)=>{ - console.log(data); - - }) - + this.healthDataService.getHealth().subscribe((data) => { + this.healthArr = this.getHealth(data.payload.components); + this.healthGlobalStatus = data.payload.status; + }); + + this.healthDataService.getInfo().subscribe((data) => { + this.serverInfoArr = this.getInfo(data.payload, null, []); + }); + } + + /** + * @param obj represents a info + * @param key represents a nested key of info + * @param arr represents a key value pair or only key + * @returns {{arr}} of key value pair or only key + */ + getInfo(obj, key, arr) { + if (typeof obj === 'object' && key !== null) { + arr.push({style: {'font-weight': 'bold' ,'font-size.px': key === 'app' ? '30' : '20' }, value: key}); + } + if (typeof obj !== 'object') { + arr.push({style: {'font-size.px': '15'}, value: `${key} = ${obj}`}); + return obj; + } + // tslint:disable-next-line: forin + for (const objKey in obj) { + this.getInfo(obj[objKey], objKey, arr); + } + return arr; + } + + /** + * @param subCompObj represent nested sub component + * @param superCompkey represents a key of super component + * @returns linear components array + */ + getHealthSubComponents(subCompObj, superCompkey) { + const subCompArr = []; + // tslint:disable-next-line: forin + for (const key in subCompObj) { + subCompArr.push({ ...subCompObj[key], components: superCompkey + '.' + key }); + } + return subCompArr; + } + + /** + * @param componentsObj represent health data + * @returns linear components array + */ + getHealth(componentsObj) { + let componentsArr = []; + for (const key in componentsObj) { + if (componentsObj[key].hasOwnProperty('components')) { + componentsArr.push({ ...componentsObj[key], components: key }); + // tslint:disable-next-line: no-string-literal + componentsArr = [...componentsArr, ...this.getHealthSubComponents(componentsObj[key]['components'], key)]; + } else { + componentsArr.push({ ...componentsObj[key], components: key }); + } + } + return componentsArr; + } + + /** + * @param status of perticular block + * @returns {{ string }} class respective status + */ + getHealthIconClass(status: string): string { + if (status === HealthStatus.UP) { + return 'fa fa-check-circle text-success ml-2 mt-1'; + } else if (status === HealthStatus.UP_WITH_ISSUES) { + return 'fa fa-exclamation-triangle text-warning ml-2 mt-1'; + } else { + return 'fa fa-times-circle circle-red ml-2 mt-1'; + } } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index ba0508bdb5..5326c2f84e 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2909,7 +2909,13 @@ "profile.title": "Update Profile", + "health.breadcrumbs": "Health", + "health-page.health" : "Health", + + "health-page.info" : "Info", + + "health-page.status" : "Status", "project.listelement.badge": "Research Project", diff --git a/yarn.lock b/yarn.lock index 420ff76478..593dd53d36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -397,6 +397,11 @@ glob "7.1.2" yargs "^16.2.0" +"@angular/material@9.2.0": + version "9.2.0" + resolved "https://registry.yarnpkg.com/@angular/material/-/material-9.2.0.tgz#1b6f0a2e115f93885d7fc2dc4b258d8c9cf6821f" + integrity sha512-KKzEIVh6/m56m+Ao8p4PK0SyEr0574l3VP2swj1qPag3u+FYgemmXCGTaChrKdDsez+zeTCPXImBGXzE6NQ80Q== + "@angular/platform-browser-dynamic@~11.2.14": version "11.2.14" resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-11.2.14.tgz#3c7fff1a1daacba5390acf033d28c377ec281166" From d7c3a20f2a29244a0059dacd1a275256587e3b45 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya <“pratik.rajkotiya@4science.com”> Date: Tue, 5 Apr 2022 18:52:40 +0530 Subject: [PATCH 04/75] [CST-5535] remove fdescibe. --- src/app/health-page/health/health.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/health-page/health/health.component.spec.ts b/src/app/health-page/health/health.component.spec.ts index 845360f059..4515aef2cb 100644 --- a/src/app/health-page/health/health.component.spec.ts +++ b/src/app/health-page/health/health.component.spec.ts @@ -112,7 +112,7 @@ function getMockHealthDataService() { }); } -fdescribe('HealthComponent', () => { +describe('HealthComponent', () => { let component: HealthComponent; let fixture: ComponentFixture; From 5add939a1d5989f59f0eb35d96e83472f6e0c5f5 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Thu, 21 Apr 2022 12:33:38 +0200 Subject: [PATCH 05/75] [CST-5270] Insertion of submission section with mock & section layout --- .../models/sherpa-policies-details.model.ts | 60 ++++++ ...spaceitem-section-sherpa-policies.model.ts | 22 ++ .../models/workspaceitem-sections.model.ts | 2 + .../objects/submission-objects.effects.ts | 111 ++++++++-- src/app/submission/sections/sections-type.ts | 1 + .../content-accordion.component.html | 30 +++ .../content-accordion.component.scss | 33 +++ .../content-accordion.component.spec.ts | 25 +++ .../content-accordion.component.ts | 15 ++ .../section-sherpa-policies.component.html | 45 ++++ .../section-sherpa-policies.component.scss | 31 +++ .../section-sherpa-policies.component.spec.ts | 204 ++++++++++++++++++ .../section-sherpa-policies.component.ts | 79 +++++++ .../section-sherpa-policies.service.ts | 42 ++++ src/app/submission/submission.module.ts | 10 +- 15 files changed, 694 insertions(+), 16 deletions(-) create mode 100644 src/app/core/submission/models/sherpa-policies-details.model.ts create mode 100644 src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts create mode 100644 src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html create mode 100644 src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.scss create mode 100644 src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts create mode 100644 src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts create mode 100644 src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html create mode 100644 src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss create mode 100644 src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts create mode 100644 src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts create mode 100644 src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts diff --git a/src/app/core/submission/models/sherpa-policies-details.model.ts b/src/app/core/submission/models/sherpa-policies-details.model.ts new file mode 100644 index 0000000000..84f3735403 --- /dev/null +++ b/src/app/core/submission/models/sherpa-policies-details.model.ts @@ -0,0 +1,60 @@ +/** + * An interface to represent an access condition. + */ +export class SherpaPoliciesDetailsObject { + + /** + * The sherpa policies uri + */ + uri: string; + + /** + * The sherpa policies details + */ + journals: Journals; +} + + +export interface Journals { + titles: string[]; + url: string; + issns: string[]; + romeoPub: string; + zetoPub: string; + inDOAJ: boolean; + publisher: Publisher; + policies: Policies; + urls: string[]; + openAccessProhibited: boolean; +} + +export interface Publisher { + name: string; + relationshipType: string; + country: string; + uri: string; + identifier: string; + paidAccessDescription: string; + paidAccessUrl: string; +} + +export interface Policies { + openAccessPermitted: boolean; + uri: string; + internalMoniker: string; + permittedVersions: PermittedVersions; +} + +export interface PermittedVersions { + articleVersion: string; + conditions: string[]; + prerequisites: string[]; + locations: string[]; + licenses: string[]; + embargo: Embargo; +} + +export interface Embargo { + units: any; + amount: any; +} diff --git a/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts b/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts new file mode 100644 index 0000000000..d7666befe7 --- /dev/null +++ b/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts @@ -0,0 +1,22 @@ +import { SherpaPoliciesDetailsObject } from './sherpa-policies-details.model'; + +/** + * An interface to represent the submission's item accesses condition. + */ +export interface WorkspaceitemSectionSherpaPoliciesObject { + + /** + * The access condition id + */ + id: string; + + /** + * The sherpa policies retrievalTime + */ + retrievalTime: string; + + /** + * The sherpa policies details + */ + details: SherpaPoliciesDetailsObject; +} diff --git a/src/app/core/submission/models/workspaceitem-sections.model.ts b/src/app/core/submission/models/workspaceitem-sections.model.ts index 084da3f088..1112d740ed 100644 --- a/src/app/core/submission/models/workspaceitem-sections.model.ts +++ b/src/app/core/submission/models/workspaceitem-sections.model.ts @@ -3,6 +3,7 @@ import { WorkspaceitemSectionFormObject } from './workspaceitem-section-form.mod import { WorkspaceitemSectionLicenseObject } from './workspaceitem-section-license.model'; import { WorkspaceitemSectionUploadObject } from './workspaceitem-section-upload.model'; import { WorkspaceitemSectionCcLicenseObject } from './workspaceitem-section-cc-license.model'; +import { WorkspaceitemSectionSherpaPoliciesObject } from './workspaceitem-section-sherpa-policies.model'; /** * An interface to represent submission's section object. @@ -21,4 +22,5 @@ export type WorkspaceitemSectionDataType | WorkspaceitemSectionLicenseObject | WorkspaceitemSectionCcLicenseObject | WorkspaceitemSectionAccessesObject + | WorkspaceitemSectionSherpaPoliciesObject | string; diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index e8de418436..f1750cb30f 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -1,3 +1,4 @@ +import { WorkspaceitemSectionSherpaPoliciesObject } from './../../core/submission/models/workspaceitem-section-sherpa-policies.model'; import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; @@ -51,6 +52,7 @@ import { SubmissionObjectDataService } from '../../core/submission/submission-ob import { followLink } from '../../shared/utils/follow-link-config.model'; import parseSectionErrorPaths, { SectionErrorPath } from '../utils/parseSectionErrorPaths'; import { FormState } from '../../shared/form/form.reducer'; +import { SUBMISSION_SECTION_TYPE } from 'src/app/core/config/models/config-type'; @Injectable() export class SubmissionObjectEffects { @@ -63,7 +65,7 @@ export class SubmissionObjectEffects { map((action: InitSubmissionFormAction) => { const definition = action.payload.submissionDefinition; const mappedActions = []; - definition.sections.page.forEach((sectionDefinition: any) => { + definition.sections.page.forEach((sectionDefinition: any, index) => { const selfLink = sectionDefinition._links.self.href || sectionDefinition._links.self; const sectionId = selfLink.substr(selfLink.lastIndexOf('/') + 1); const config = sectionDefinition._links.config ? (sectionDefinition._links.config.href || sectionDefinition._links.config) : ''; @@ -75,6 +77,7 @@ export class SubmissionObjectEffects { sectionData = action.payload.item.metadata; } const sectionErrors = isNotEmpty(action.payload.errors) ? (action.payload.errors[sectionId] || null) : null; + mappedActions.push( new InitSectionAction( action.payload.submissionId, @@ -89,7 +92,87 @@ export class SubmissionObjectEffects { sectionErrors ) ); + + if (index === definition.sections.page.length - 1) { + mappedActions.push( + new InitSectionAction( + action.payload.submissionId, + 'sherpaPolicies', + 'submit.progressbar.sherpaPolicies', + 'submit.progressbar.sherpaPolicies', + true, + SectionsType.SherpaPolicies, + { main: null, other: 'READONLY' }, + true, + { + 'id': 'sherpaPolicies', + 'retrievalTime': '2022-01-11T09:43:53Z', + 'details': { + 'uri': 'https://www.nature.com/natsynth/', + 'journals': { + 'titles': [ + 'Nature Synthesis' + ], + 'url': 'http://europepmc.org/', + 'issns': [ + '2731-0582', + '2731-0583', + '2731-0584', + ], + 'romeoPub': 'Self archiving and license to publish', + 'zetoPub': 'Self archiving and license to publish', + 'inDOAJ': true, + 'publisher': { + 'name': 'Europe PMC', + 'relationshipType': 'Stest', + 'country': 'gb', + 'uri': 'https://v2.sherpa.ac.uk/id/publication/40863', + 'identifier': '123123123', + 'paidAccessDescription': 'test test sss', + 'paidAccessUrl': 'https://www.nature.com/nature-portfolio/editorial-policies/preprints-and-conference-proceedings' + }, + 'policies': { + 'openAccessPermitted': true, + 'uri': 'https://v2.sherpa.ac.uk/id/publisher_policy/3286', + 'internalMoniker': 'Default Policy', + 'permittedVersions': { + 'articleVersion': 'submitted', + 'conditions': [ + 'Must link to publisher version', + 'Published source must be acknowledged and DOI cited', + 'Post-prints are subject to Springer Nature re-use terms', + 'Non-commercial use only' + ], + 'prerequisites': [], + 'locations': [ + 'authors_homepage', + 'funder_designated_location', + 'institutional_repository', + 'preprint_repository' + ], + 'licenses': [], + 'embargo': { + 'units': 'months', + 'amount': 6 + } + } + }, + 'urls': [ + 'https://www.nature.com/neuro/editorial-policies/self-archiving-and-license-to-publish', + 'https://www.nature.com/nature-portfolio/editorial-policies/preprints-and-conference-proceedings', + 'https://www.springernature.com/gp/open-research/policies/accepted-manuscript-terms' + ], + 'openAccessProhibited': true + } + } + } as WorkspaceitemSectionSherpaPoliciesObject, + null + ) + ); + } + }); + console.log(mappedActions); return { action: action, definition: definition, mappedActions: mappedActions }; }), mergeMap((result) => { @@ -125,8 +208,8 @@ export class SubmissionObjectEffects { this.submissionService.getSubmissionObjectLinkName(), action.payload.submissionId, 'sections').pipe( - map((response: SubmissionObject[]) => new SaveSubmissionFormSuccessAction(action.payload.submissionId, response, action.payload.isManual)), - catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); + map((response: SubmissionObject[]) => new SaveSubmissionFormSuccessAction(action.payload.submissionId, response, action.payload.isManual)), + catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); })); /** @@ -139,8 +222,8 @@ export class SubmissionObjectEffects { this.submissionService.getSubmissionObjectLinkName(), action.payload.submissionId, 'sections').pipe( - map((response: SubmissionObject[]) => new SaveForLaterSubmissionFormSuccessAction(action.payload.submissionId, response)), - catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); + map((response: SubmissionObject[]) => new SaveForLaterSubmissionFormSuccessAction(action.payload.submissionId, response)), + catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); })); /** @@ -179,8 +262,8 @@ export class SubmissionObjectEffects { action.payload.submissionId, 'sections', action.payload.sectionId).pipe( - map((response: SubmissionObject[]) => new SaveSubmissionSectionFormSuccessAction(action.payload.submissionId, response)), - catchError(() => observableOf(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId)))); + map((response: SubmissionObject[]) => new SaveSubmissionSectionFormSuccessAction(action.payload.submissionId, response)), + catchError(() => observableOf(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId)))); })); /** @@ -317,13 +400,13 @@ export class SubmissionObjectEffects { tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.discard_error_notice')))); constructor(private actions$: Actions, - private notificationsService: NotificationsService, - private operationsService: SubmissionJsonPatchOperationsService, - private sectionService: SectionsService, - private store$: Store, - private submissionService: SubmissionService, - private submissionObjectService: SubmissionObjectDataService, - private translate: TranslateService) { + private notificationsService: NotificationsService, + private operationsService: SubmissionJsonPatchOperationsService, + private sectionService: SectionsService, + private store$: Store, + private submissionService: SubmissionService, + private submissionObjectService: SubmissionObjectDataService, + private translate: TranslateService) { } /** diff --git a/src/app/submission/sections/sections-type.ts b/src/app/submission/sections/sections-type.ts index d13aef1da1..f998ef4554 100644 --- a/src/app/submission/sections/sections-type.ts +++ b/src/app/submission/sections/sections-type.ts @@ -6,4 +6,5 @@ export enum SectionsType { CcLicense = 'cclicense', collection = 'collection', AccessesCondition = 'accessCondition', + SherpaPolicies = 'sherpaPolicies', } diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html new file mode 100644 index 0000000000..8aa4a7ef9f --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html @@ -0,0 +1,30 @@ + + + + Publication information +
+ + +
+
+ +
+
+
+

Title

+
+
+
+
+
+
+

ISSNs

+
+
+
+
+
+ +
+
+
\ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.scss b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.scss new file mode 100644 index 0000000000..73d0f259cc --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.scss @@ -0,0 +1,33 @@ +:host ::ng-deep { + .card { + border: none; + margin-bottom: 20px; + border-bottom: 1px solid rgba(0, 0, 0, 0.125); + + .card-header { + border: none; + background-color: transparent; + border-top: 1px solid rgba(0, 0, 0, 0.125); + + button { + text-align: left; + padding: 0px; + width: 100%; + color: #000; + font-weight: normal; + + .fas { + background: #fff; + color: #000; + margin-right: 10px; + height: 1.25em; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + } + } + } + } + +} \ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts new file mode 100644 index 0000000000..3b4d18a633 --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ContentAccordionComponent } from './content-accordion.component'; + +describe('ContentAccordionComponent', () => { + let component: ContentAccordionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ContentAccordionComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ContentAccordionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts new file mode 100644 index 0000000000..09e1d174d1 --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'ds-content-accordion', + templateUrl: './content-accordion.component.html', + styleUrls: ['./content-accordion.component.scss'] +}) +export class ContentAccordionComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html new file mode 100644 index 0000000000..a8014f0d6e --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -0,0 +1,45 @@ + + + +
+ + +
+ Publication information +
+ +
+
+
+

Title

+
+
+

{{title}}

+
+
+
+
+

ISSNs

+
+
+

{{issn}}

+
+
+
+ +
+
+ + +
+ + +
+ Publisher Policy +
+ + + + +
+
\ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss new file mode 100644 index 0000000000..8c1bfc2580 --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss @@ -0,0 +1,31 @@ +:host ::ng-deep { + .card { + border: none; + margin-bottom: 20px; + border-bottom: 1px solid rgba(0, 0, 0, 0.125); + + .card-header { + border: none; + background-color: transparent; + border-top: 1px solid rgba(0, 0, 0, 0.125); + + button { + text-align: left; + padding: 0px; + width: auto; + font-weight: bold; + + .fas { + background: #207698; + color: #fff; + margin-right: 10px; + height: 1.25em; + display: flex; + align-items: center; + justify-content: center; + } + } + } + } + +} \ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts new file mode 100644 index 0000000000..5509cab4bb --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts @@ -0,0 +1,204 @@ +import { FormService } from '../../../shared/form/form.service'; +import { ComponentFixture, inject, TestBed } from '@angular/core/testing'; + +import { SubmissionSectionAccessesComponent } from './section-accesses.component'; +import { SectionsService } from '../sections.service'; +import { SectionsServiceStub } from '../../../shared/testing/sections-service.stub'; + +import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; +import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock'; +import { SubmissionAccessesConfigService } from '../../../core/config/submission-accesses-config.service'; +import { + getSubmissionAccessesConfigNotChangeDiscoverableService, + getSubmissionAccessesConfigService +} from '../../../shared/mocks/section-accesses-config.service.mock'; +import { SectionAccessesService } from './section-accesses.service'; +import { SectionFormOperationsService } from '../form/section-form-operations.service'; +import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; +import { getSectionAccessesService } from '../../../shared/mocks/section-accesses.service.mock'; +import { getMockFormOperationsService } from '../../../shared/mocks/form-operations-service.mock'; +import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock'; +import { SubmissionJsonPatchOperationsServiceStub } from '../../../shared/testing/submission-json-patch-operations-service.stub'; +import { BrowserModule } from '@angular/platform-browser'; + +import { of as observableOf } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { FormComponent } from '../../../shared/form/form.component'; +import { + DynamicCheckboxModel, + DynamicDatePickerModel, + DynamicFormArrayModel, + DynamicSelectModel +} from '@ng-dynamic-forms/core'; +import { AppState } from '../../../app.reducer'; +import { getMockFormService } from '../../../shared/mocks/form-service.mock'; +import { mockAccessesFormData } from '../../../shared/mocks/submission.mock'; +import { accessConditionChangeEvent, checkboxChangeEvent } from '../../../shared/testing/form-event.stub'; + +describe('SubmissionSectionAccessesComponent', () => { + let component: SubmissionSectionAccessesComponent; + let fixture: ComponentFixture; + + const sectionsServiceStub = new SectionsServiceStub(); + // const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex); + + const builderService: FormBuilderService = getMockFormBuilderService(); + const submissionAccessesConfigService = getSubmissionAccessesConfigService(); + const sectionAccessesService = getSectionAccessesService(); + const sectionFormOperationsService = getMockFormOperationsService(); + const operationsBuilder = jasmine.createSpyObj('operationsBuilder', { + add: undefined, + remove: undefined, + replace: undefined, + }); + + let formService: any; + + const storeStub = jasmine.createSpyObj('store', ['dispatch']); + + const sectionData = { + header: 'submit.progressbar.accessCondition', + config: 'http://localhost:8080/server/api/config/submissionaccessoptions/AccessConditionDefaultConfiguration', + mandatory: true, + sectionType: 'accessCondition', + collapsed: false, + enabled: true, + data: { + discoverable: true, + accessConditions: [] + }, + errorsToShow: [], + serverValidationErrors: [], + isLoading: false, + isValid: true + }; + + describe('First with canChangeDiscoverable true', () => { + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + BrowserModule, + TranslateModule.forRoot() + ], + declarations: [SubmissionSectionAccessesComponent, FormComponent], + providers: [ + { provide: SectionsService, useValue: sectionsServiceStub }, + { provide: FormBuilderService, useValue: builderService }, + { provide: SubmissionAccessesConfigService, useValue: submissionAccessesConfigService }, + { provide: SectionAccessesService, useValue: sectionAccessesService }, + { provide: SectionFormOperationsService, useValue: sectionFormOperationsService }, + { provide: JsonPatchOperationsBuilder, useValue: operationsBuilder }, + { provide: TranslateService, useValue: getMockTranslateService() }, + { provide: FormService, useValue: getMockFormService() }, + { provide: Store, useValue: storeStub }, + { provide: SubmissionJsonPatchOperationsService, useValue: SubmissionJsonPatchOperationsServiceStub }, + { provide: 'sectionDataProvider', useValue: sectionData }, + { provide: 'submissionIdProvider', useValue: '1508' }, + ] + }) + .compileComponents(); + }); + + beforeEach(inject([Store], (store: Store) => { + fixture = TestBed.createComponent(SubmissionSectionAccessesComponent); + component = fixture.componentInstance; + formService = TestBed.inject(FormService); + formService.validateAllFormFields.and.callFake(() => null); + formService.isValid.and.returnValue(observableOf(true)); + formService.getFormData.and.returnValue(observableOf(mockAccessesFormData)); + fixture.detectChanges(); + })); + + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have created formModel', () => { + expect(component.formModel).toBeTruthy(); + }); + + it('should have formModel length should be 2', () => { + expect(component.formModel.length).toEqual(2); + }); + + it('formModel should have 1 model type checkbox and 1 model type array', () => { + expect(component.formModel[0] instanceof DynamicCheckboxModel).toBeTrue(); + expect(component.formModel[1] instanceof DynamicFormArrayModel).toBeTrue(); + }); + + it('formModel type array should have formgroup with 1 input and 2 datepickers', () => { + const formModel: any = component.formModel[1]; + const formGroup = formModel.groupFactory()[0].group; + expect(formGroup[0] instanceof DynamicSelectModel).toBeTrue(); + expect(formGroup[1] instanceof DynamicDatePickerModel).toBeTrue(); + expect(formGroup[2] instanceof DynamicDatePickerModel).toBeTrue(); + }); + + it('when checkbox changed it should call operationsBuilder replace function', () => { + component.onChange(checkboxChangeEvent); + fixture.detectChanges(); + + expect(operationsBuilder.replace).toHaveBeenCalled(); + }); + + it('when dropdown select changed it should call operationsBuilder add function', () => { + component.onChange(accessConditionChangeEvent); + fixture.detectChanges(); + expect(operationsBuilder.add).toHaveBeenCalled(); + }); + }); + + describe('when canDescoverable is false', () => { + + + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + BrowserModule, + TranslateModule.forRoot() + ], + declarations: [SubmissionSectionAccessesComponent, FormComponent], + providers: [ + { provide: SectionsService, useValue: sectionsServiceStub }, + { provide: FormBuilderService, useValue: builderService }, + { provide: SubmissionAccessesConfigService, useValue: getSubmissionAccessesConfigNotChangeDiscoverableService() }, + { provide: SectionAccessesService, useValue: sectionAccessesService }, + { provide: SectionFormOperationsService, useValue: sectionFormOperationsService }, + { provide: JsonPatchOperationsBuilder, useValue: operationsBuilder }, + { provide: TranslateService, useValue: getMockTranslateService() }, + { provide: FormService, useValue: getMockFormService() }, + { provide: Store, useValue: storeStub }, + { provide: SubmissionJsonPatchOperationsService, useValue: SubmissionJsonPatchOperationsServiceStub }, + { provide: 'sectionDataProvider', useValue: sectionData }, + { provide: 'submissionIdProvider', useValue: '1508' }, + ] + }) + .compileComponents(); + }); + + beforeEach(inject([Store], (store: Store) => { + fixture = TestBed.createComponent(SubmissionSectionAccessesComponent); + component = fixture.componentInstance; + formService = TestBed.inject(FormService); + formService.validateAllFormFields.and.callFake(() => null); + formService.isValid.and.returnValue(observableOf(true)); + formService.getFormData.and.returnValue(observableOf(mockAccessesFormData)); + fixture.detectChanges(); + })); + + + it('should have formModel length should be 1', () => { + expect(component.formModel.length).toEqual(1); + }); + + it('formModel should have only 1 model type array', () => { + expect(component.formModel[0] instanceof DynamicFormArrayModel).toBeTrue(); + }); + + }); +}); diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts new file mode 100644 index 0000000000..5704d1abbb --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts @@ -0,0 +1,79 @@ +import { WorkspaceitemSectionSherpaPoliciesObject } from './../../../core/submission/models/workspaceitem-section-sherpa-policies.model'; +import { SectionSherpaPoliciesService } from './section-sherpa-policies.service'; +import { Component, Inject, ViewChild } from '@angular/core'; +import { FormControl } from '@angular/forms'; + +import { filter, map, mergeMap, take } from 'rxjs/operators'; +import { combineLatest, Observable, of, Subscription } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; + +import { renderSectionFor } from '../sections-decorator'; +import { SectionsType } from '../sections-type'; +import { SectionDataObject } from '../models/section-data.model'; +import { SectionsService } from '../sections.service'; +import { SectionModelComponent } from '../models/section.model'; +import { NgbAccordionConfig } from '@ng-bootstrap/ng-bootstrap'; + +/** + * This component represents a section for managing item's access conditions. + */ +@Component({ + selector: 'ds-section-sherpa-policies', + templateUrl: './section-sherpa-policies.component.html', + styleUrls: ['./section-sherpa-policies.component.scss'] +}) +@renderSectionFor(SectionsType.SherpaPolicies) +export class SubmissionSectionSherpaPoliciesComponent extends SectionModelComponent { + + /** + * The accesses section data + * @type {WorkspaceitemSectionAccessesObject} + */ + public sherpaPoliciesData: WorkspaceitemSectionSherpaPoliciesObject; + + + /** + * Initialize instance variables + * + * @param {SectionsService} sectionService + * @param {SectionDataObject} injectedSectionData + * @param {SectionSherpaPoliciesService} sectionSherpaPoliciesService + * @param {string} injectedSubmissionId + */ + constructor( + protected sectionService: SectionsService, + private sectionSherpaPoliciesService: SectionSherpaPoliciesService, + @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, + @Inject('submissionIdProvider') public injectedSubmissionId: string) { + super(undefined, injectedSectionData, injectedSubmissionId); + } + + /** + * Unsubscribe from all subscriptions + */ + // tslint:disable-next-line:no-empty + onSectionDestroy() { + + } + + /** + * Initialize all instance variables and retrieve collection default access conditions + */ + protected onSectionInit(): void { + this.sectionSherpaPoliciesService.getSherpaPoliciesData(this.submissionId, this.sectionData.id).subscribe((sherpaPolicies: WorkspaceitemSectionSherpaPoliciesObject) => { + console.log(sherpaPolicies); + this.sherpaPoliciesData = sherpaPolicies; + }); + } + + /** + * Get section status + * + * @return Observable + * the section status + */ + protected getSectionStatus(): Observable { + return of(true); + } + +} diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts new file mode 100644 index 0000000000..c81caca41e --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts @@ -0,0 +1,42 @@ +import { WorkspaceitemSectionSherpaPoliciesObject } from './../../../core/submission/models/workspaceitem-section-sherpa-policies.model'; +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs'; +import { distinctUntilChanged, filter } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; + +import { SubmissionState } from '../../submission.reducers'; +import { isNotUndefined } from '../../../shared/empty.util'; +import { submissionSectionDataFromIdSelector } from '../../selectors'; + +/** + * A service that provides methods to handle submission item's accesses condition state. + */ +@Injectable() +export class SectionSherpaPoliciesService { + + /** + * Initialize service variables + * + * @param {Store} store + */ + constructor(private store: Store) { } + + + /** + * Return item's accesses condition state. + * + * @param submissionId + * The submission id + * @param sectionId + * The section id + * @returns {Observable} + * Emits bitstream's metadata + */ + public getSherpaPoliciesData(submissionId: string, sectionId: string): Observable { + + return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe( + filter((state) => isNotUndefined(state)), + distinctUntilChanged()); + } +} diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index 939d1bff29..f1cedc8953 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -1,3 +1,4 @@ +import { SectionSherpaPoliciesService } from './sections/sherpa-policies/section-sherpa-policies.service'; import { NgModule } from '@angular/core'; import { CoreModule } from '../core/core.module'; import { SharedModule } from '../shared/shared.module'; @@ -42,6 +43,8 @@ import { NgbAccordionModule, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; import { SubmissionSectionAccessesComponent } from './sections/accesses/section-accesses.component'; import { SubmissionAccessesConfigService } from '../core/config/submission-accesses-config.service'; import { SectionAccessesService } from './sections/accesses/section-accesses.service'; +import { SubmissionSectionSherpaPoliciesComponent } from './sections/sherpa-policies/section-sherpa-policies.component'; +import { ContentAccordionComponent } from './sections/sherpa-policies/content-accordion/content-accordion.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -50,7 +53,8 @@ const ENTRY_COMPONENTS = [ SubmissionSectionLicenseComponent, SubmissionSectionCcLicensesComponent, SubmissionSectionAccessesComponent, - SubmissionSectionUploadFileEditComponent + SubmissionSectionUploadFileEditComponent, + SubmissionSectionSherpaPoliciesComponent, ]; const DECLARATIONS = [ @@ -75,6 +79,7 @@ const DECLARATIONS = [ SubmissionImportExternalSearchbarComponent, SubmissionImportExternalPreviewComponent, SubmissionImportExternalCollectionComponent, + ContentAccordionComponent, ]; @NgModule({ @@ -97,7 +102,8 @@ const DECLARATIONS = [ SectionsService, SubmissionUploadsConfigService, SubmissionAccessesConfigService, - SectionAccessesService + SectionAccessesService, + SectionSherpaPoliciesService ] }) From da2cba5827d22be7bb8df4a53d14fd488bc0eb80 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Thu, 21 Apr 2022 18:48:19 +0200 Subject: [PATCH 06/75] [CST-5270] Added some information from mock json to the layout --- .../content-accordion.component.html | 48 ++++++++++-- .../content-accordion.component.scss | 2 +- .../content-accordion.component.ts | 10 +-- .../section-sherpa-policies.component.html | 78 ++++++++++++++++++- 4 files changed, 122 insertions(+), 16 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html index 8aa4a7ef9f..2a453ec408 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html @@ -1,26 +1,60 @@ - - + + - Publication information + {{id.replace('-',' ') | titlecase}}
- - + +
-

Title

+

Article Version

+ {{data.articleVersion}}
-

ISSNs

+

Conditions

+

{{condition}}

+
+
+
+
+

Prerequisites

+
+
+

{{prerequisite}}

+
+
+
+
+

Location

+
+
+

{{location}}

+
+
+
+
+

License

+
+
+

{{license}}

+
+
+
+
+

Embargo

+
+
+

{{data.embargo.amount}} {{data.embargo.units}}

diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.scss b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.scss index 73d0f259cc..b18e7d3781 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.scss +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.scss @@ -14,7 +14,7 @@ padding: 0px; width: 100%; color: #000; - font-weight: normal; + font-weight: 600; .fas { background: #fff; diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts index 09e1d174d1..95f85e0732 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts @@ -1,15 +1,13 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input } from '@angular/core'; @Component({ selector: 'ds-content-accordion', templateUrl: './content-accordion.component.html', styleUrls: ['./content-accordion.component.scss'] }) -export class ContentAccordionComponent implements OnInit { +export class ContentAccordionComponent { - constructor() { } - - ngOnInit(): void { - } + @Input() id: string; + @Input() data: any; } diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index a8014f0d6e..dbb33ee806 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -25,6 +25,50 @@

{{issn}}

+ + +
+
+

romeoPub

+
+
+

+ {{sherpaPoliciesData.details.journals.romeoPub}} +

+
+
+
+
+

zetoPub

+
+
+

+ {{sherpaPoliciesData.details.journals.zetoPub}} +

+
+
@@ -38,8 +82,38 @@ Publisher Policy - - +
+
+
+

+ Open Access pathways permitted by this journal's policy are listed below by article version. + Click on a pathway for a more detailed view +

+
+
+ + + +
+
+

+ For more information, please see the following links: +

+ +
+
+ + +
+ + + +
\ No newline at end of file From d390920ccafdb201a1daf2b0a8209be6f40a2fc6 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Fri, 22 Apr 2022 19:09:40 +0200 Subject: [PATCH 07/75] [CST-5270] Code refactoring, made changes from the rest contract and implemented i18n --- .../models/sherpa-policies-details.model.ts | 49 ++++-- ...spaceitem-section-sherpa-policies.model.ts | 2 +- .../objects/submission-objects.effects.ts | 150 ++++++++++------- .../content-accordion.component.html | 57 ++++--- .../content-accordion.component.ts | 4 +- .../metadata-information.component.html | 39 +++++ .../metadata-information.component.scss | 0 .../metadata-information.component.spec.ts | 25 +++ .../metadata-information.component.ts | 13 ++ .../publication-information.component.html | 70 ++++++++ .../publication-information.component.scss | 0 .../publication-information.component.spec.ts | 25 +++ .../publication-information.component.ts | 13 ++ .../publisher-policy.component.html | 26 +++ .../publisher-policy.component.scss | 0 .../publisher-policy.component.spec.ts | 25 +++ .../publisher-policy.component.ts | 13 ++ .../section-sherpa-policies.component.html | 151 +++++------------- .../section-sherpa-policies.component.ts | 18 ++- src/app/submission/submission.module.ts | 6 + src/assets/i18n/en.json5 | 53 ++++++ 21 files changed, 522 insertions(+), 217 deletions(-) create mode 100644 src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.html create mode 100644 src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.scss create mode 100644 src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts create mode 100644 src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts create mode 100644 src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.html create mode 100644 src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.scss create mode 100644 src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts create mode 100644 src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts create mode 100644 src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html create mode 100644 src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.scss create mode 100644 src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts create mode 100644 src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts diff --git a/src/app/core/submission/models/sherpa-policies-details.model.ts b/src/app/core/submission/models/sherpa-policies-details.model.ts index 84f3735403..af4d4a5890 100644 --- a/src/app/core/submission/models/sherpa-policies-details.model.ts +++ b/src/app/core/submission/models/sherpa-policies-details.model.ts @@ -4,18 +4,39 @@ export class SherpaPoliciesDetailsObject { /** - * The sherpa policies uri + * The sherpa policies error */ - uri: string; + error: boolean; /** - * The sherpa policies details + * The sherpa policies journal details */ - journals: Journals; + journals: Journal[]; + + /** + * The sherpa policies message + */ + message: string; + + /** + * The sherpa policies metadata + */ + metadata: Metadata; + } -export interface Journals { +export interface Metadata { + id: number; + uri: string; + dateCreated: string; + dateModified: string; + inDOAJ: boolean; + publiclyVisible: boolean; +} + + +export interface Journal { titles: string[]; url: string; issns: string[]; @@ -23,9 +44,8 @@ export interface Journals { zetoPub: string; inDOAJ: boolean; publisher: Publisher; - policies: Policies; - urls: string[]; - openAccessProhibited: boolean; + publishers: Publisher[]; + policies: Policy[]; } export interface Publisher { @@ -36,17 +56,26 @@ export interface Publisher { identifier: string; paidAccessDescription: string; paidAccessUrl: string; + publicationCount: number; } -export interface Policies { +export interface Policy { + id: number; openAccessPermitted: boolean; uri: string; internalMoniker: string; - permittedVersions: PermittedVersions; + permittedVersions: PermittedVersions[]; + urls: any; + publicationCount: number; + preArchiving: string; + postArchiving: string; + pubArchiving: string; + openAccessProhibited: boolean; } export interface PermittedVersions { articleVersion: string; + option: number; conditions: string[]; prerequisites: string[]; locations: string[]; diff --git a/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts b/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts index d7666befe7..cbef185090 100644 --- a/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts @@ -18,5 +18,5 @@ export interface WorkspaceitemSectionSherpaPoliciesObject { /** * The sherpa policies details */ - details: SherpaPoliciesDetailsObject; + sherpaResponse: SherpaPoliciesDetailsObject[]; } diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index f1750cb30f..e91c2caf14 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -106,65 +106,101 @@ export class SubmissionObjectEffects { true, { 'id': 'sherpaPolicies', - 'retrievalTime': '2022-01-11T09:43:53Z', - 'details': { - 'uri': 'https://www.nature.com/natsynth/', - 'journals': { - 'titles': [ - 'Nature Synthesis' - ], - 'url': 'http://europepmc.org/', - 'issns': [ - '2731-0582', - '2731-0583', - '2731-0584', - ], - 'romeoPub': 'Self archiving and license to publish', - 'zetoPub': 'Self archiving and license to publish', - 'inDOAJ': true, - 'publisher': { - 'name': 'Europe PMC', - 'relationshipType': 'Stest', - 'country': 'gb', - 'uri': 'https://v2.sherpa.ac.uk/id/publication/40863', - 'identifier': '123123123', - 'paidAccessDescription': 'test test sss', - 'paidAccessUrl': 'https://www.nature.com/nature-portfolio/editorial-policies/preprints-and-conference-proceedings' + 'retrievalTime': '2022-04-20T09:44:39.870+00:00', + 'sherpaResponse': [ + { + 'error': false, + 'message': null, + 'metadata': { + 'id': 23803, + 'uri': 'http://v2.sherpa.ac.uk/id/publication/23803', + 'dateCreated': '2012-11-20 14:51:52', + 'dateModified': '2020-03-06 11:25:54', + 'inDOAJ': false, + 'publiclyVisible': true }, - 'policies': { - 'openAccessPermitted': true, - 'uri': 'https://v2.sherpa.ac.uk/id/publisher_policy/3286', - 'internalMoniker': 'Default Policy', - 'permittedVersions': { - 'articleVersion': 'submitted', - 'conditions': [ - 'Must link to publisher version', - 'Published source must be acknowledged and DOI cited', - 'Post-prints are subject to Springer Nature re-use terms', - 'Non-commercial use only' - ], - 'prerequisites': [], - 'locations': [ - 'authors_homepage', - 'funder_designated_location', - 'institutional_repository', - 'preprint_repository' - ], - 'licenses': [], - 'embargo': { - 'units': 'months', - 'amount': 6 - } - } - }, - 'urls': [ - 'https://www.nature.com/neuro/editorial-policies/self-archiving-and-license-to-publish', - 'https://www.nature.com/nature-portfolio/editorial-policies/preprints-and-conference-proceedings', - 'https://www.springernature.com/gp/open-research/policies/accepted-manuscript-terms' - ], - 'openAccessProhibited': true + 'journals': [{ + 'titles': ['The Lancet', 'Lancet'], + 'url': 'http://www.thelancet.com/journals/lancet/issue/current', + 'issns': ['0140-6736', '1474-547X'], + 'romeoPub': 'Elsevier: The Lancet', + 'zetoPub': 'Elsevier: The Lancet', + 'publisher': { + 'name': 'Elsevier', + 'relationshipType': null, + 'country': null, + 'uri': 'http://www.elsevier.com/', + 'identifier': null, + 'publicationCount': 0, + 'paidAccessDescription': 'Open access', + 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' + }, + 'publishers': [{ + 'name': 'Elsevier', + 'relationshipType': null, + 'country': null, + 'uri': 'http://www.elsevier.com/', + 'identifier': null, + 'publicationCount': 0, + 'paidAccessDescription': 'Open access', + 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' + }], + 'policies': [{ + 'id': 0, + 'openAccessPermitted': false, + 'uri': null, + 'internalMoniker': 'Lancet', + 'permittedVersions': [{ + 'articleVersion': 'submitted', + 'option': 1, + 'conditions': ['Upon publication publisher copyright and source must be acknowledged', 'Upon publication must link to publisher version'], + 'prerequisites': [], + 'locations': ['Author\'s Homepage', 'Preprint Repository'], + 'licenses': [], + 'embargo': null + }, { + 'articleVersion': 'accepted', + 'option': 1, + 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], + 'prerequisites': [], + 'locations': ['Author\'s Homepage', 'Institutional Website'], + 'licenses': ['CC BY-NC-ND'], + 'embargo': null + }, { + 'articleVersion': 'accepted', + 'option': 2, + 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], + 'prerequisites': ['If Required by Funder'], + 'locations': ['Non-Commercial Repository'], + 'licenses': ['CC BY-NC-ND'], + 'embargo': null + }, { + 'articleVersion': 'accepted', + 'option': 3, + 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], + 'prerequisites': [], + 'locations': ['Non-Commercial Repository'], + 'licenses': [], + 'embargo': null + }], + 'urls': { + 'http://download.thelancet.com/flatcontentassets/authors/lancet-information-for-authors.pdf': 'Guidelines for Authors', + 'http://www.thelancet.com/journals/lancet/article/PIIS0140-6736%2813%2960720-5/fulltext': 'The Lancet journals welcome a new open access policy', + 'http://www.thelancet.com/lancet-information-for-authors/after-publication': 'What happens after publication?', + 'http://www.thelancet.com/lancet/information-for-authors/disclosure-of-results': 'Disclosure of results before publication', + 'https://www.elsevier.com/__data/assets/pdf_file/0005/78476/external-embargo-list.pdf': 'Journal Embargo Period List', + 'https://www.elsevier.com/__data/assets/pdf_file/0011/78473/UK-Embargo-Periods.pdf': 'Journal Embargo List for UK Authors' + }, + 'openAccessProhibited': false, + 'publicationCount': 0, + 'preArchiving': 'can', + 'postArchiving': 'can', + 'pubArchiving': 'cannot' + }], + 'inDOAJ': false + }] } - } + ] } as WorkspaceitemSectionSherpaPoliciesObject, null ) diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html index 2a453ec408..bec999603d 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html @@ -1,64 +1,63 @@ - - + + - {{id.replace('-',' ') | titlecase}} + + {{version.articleVersion | titlecase}} {{ 'submission.sections.sherpa.publisher.policy.version' | + translate }} +
- - + +
-

Article Version

+

{{ 'submission.sections.sherpa.publisher.policy.embargo' | translate }}

-
- {{data.articleVersion}} +
+

{{version.embargo.amount}} + {{version.embargo.units}}

+ +

{{ 'submission.sections.sherpa.publisher.policy.noembargo' | translate }}

+
+
-

Conditions

+

{{ 'submission.sections.sherpa.publisher.policy.license' | translate }}

-
-

{{condition}}

+
+

{{license}}

-

Prerequisites

+

{{ 'submission.sections.sherpa.publisher.policy.prerequisites' | translate }}

-
-

{{prerequisite}}

+
+

{{prerequisite}}

-

Location

+

{{ 'submission.sections.sherpa.publisher.policy.location' | translate }}

-
-

{{location}}

+
+

{{location}}

-

License

+

{{ 'submission.sections.sherpa.publisher.policy.conditions' | translate }}

-
-

{{license}}

-
-
-
-
-

Embargo

-
-
-

{{data.embargo.amount}} {{data.embargo.units}}

+
+

{{condition}}

- \ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts index 95f85e0732..12b48d10f4 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts @@ -1,3 +1,4 @@ +import { PermittedVersions } from './../../../../core/submission/models/sherpa-policies-details.model'; import { Component, Input } from '@angular/core'; @Component({ @@ -7,7 +8,6 @@ import { Component, Input } from '@angular/core'; }) export class ContentAccordionComponent { - @Input() id: string; - @Input() data: any; + @Input() version: PermittedVersions; } diff --git a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.html b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.html new file mode 100644 index 0000000000..bfbfe5f2fd --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.html @@ -0,0 +1,39 @@ +
+
+
+

{{ 'submission.sections.sherpa.record.information.id' | translate }}

+
+
+

{{metadata.id}} +

+
+
+
+
+

{{ 'submission.sections.sherpa.record.information.date.created' | translate }}

+
+
+

{{metadata.dateCreated | date: 'd MMMM Y H:mm:ss zzzz' }} +

+
+
+
+
+

{{ 'submission.sections.sherpa.record.information.date.modified' | translate }}

+
+
+

{{metadata.dateModified| date: 'd MMMM Y H:mm:ss zzzz' }} +

+
+
+
+
+

{{ 'submission.sections.sherpa.record.information.uri' | translate }}

+
+ +
+
\ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.scss b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts new file mode 100644 index 0000000000..c1257fc802 --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MetadataInformationComponent } from './metadata-information.component'; + +describe('MetadataInformationComponent', () => { + let component: MetadataInformationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MetadataInformationComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MetadataInformationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts new file mode 100644 index 0000000000..334aa43593 --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; +import { Metadata } from './../../../../core/submission/models/sherpa-policies-details.model'; + +@Component({ + selector: 'ds-metadata-information', + templateUrl: './metadata-information.component.html', + styleUrls: ['./metadata-information.component.scss'] +}) +export class MetadataInformationComponent { + + @Input() metadata: Metadata; + +} diff --git a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.html b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.html new file mode 100644 index 0000000000..2e6da459ae --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.html @@ -0,0 +1,70 @@ +
+
+
+

{{'submission.sections.sherpa.publication.information.title' + | translate}}

+
+
+

{{title}} +

+
+
+
+
+

{{'submission.sections.sherpa.publication.information.issns' + | translate}}

+
+
+

{{issn}} +

+
+
+
+
+

{{'submission.sections.sherpa.publication.information.url' + | translate}}

+
+ +
+
+
+

{{'submission.sections.sherpa.publication.information.publishers' + | translate}}

+
+ +
+
+
+

{{'submission.sections.sherpa.publication.information.romeoPub' + | translate}}

+
+
+

+ {{journal.romeoPub}} +

+
+
+
+
+

{{'submission.sections.sherpa.publication.information.zetoPub' + | translate}}

+
+
+

+ {{journal.zetoPub}} +

+
+
+
\ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.scss b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts new file mode 100644 index 0000000000..3f264df21f --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PublicationInformationComponent } from './publication-information.component'; + +describe('PublicationInformationComponent', () => { + let component: PublicationInformationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PublicationInformationComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PublicationInformationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts new file mode 100644 index 0000000000..a6cd6cc3da --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts @@ -0,0 +1,13 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Journal } from './../../../../core/submission/models/sherpa-policies-details.model'; + +@Component({ + selector: 'ds-publication-information', + templateUrl: './publication-information.component.html', + styleUrls: ['./publication-information.component.scss'] +}) +export class PublicationInformationComponent { + + @Input() journal: Journal; + +} diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html new file mode 100644 index 0000000000..3bc1cb5084 --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html @@ -0,0 +1,26 @@ +
+
+
+

+ {{'submission.sections.sherpa.publisher.policy.openaccess' | translate}} +

+
+
+ + + +
+
+

+ {{'submission.sections.sherpa.publisher.policy.more.information' | translate}} +

+ +
+
+ + +
\ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.scss b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts new file mode 100644 index 0000000000..1821a97795 --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PublisherPolicyComponent } from './publisher-policy.component'; + +describe('PublisherPolicyComponent', () => { + let component: PublisherPolicyComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PublisherPolicyComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PublisherPolicyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts new file mode 100644 index 0000000000..e526080d0d --- /dev/null +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts @@ -0,0 +1,13 @@ +import { Policy } from './../../../../core/submission/models/sherpa-policies-details.model'; +import { Component, Input, OnInit } from '@angular/core'; + +@Component({ + selector: 'ds-publisher-policy', + templateUrl: './publisher-policy.component.html', + styleUrls: ['./publisher-policy.component.scss'] +}) +export class PublisherPolicyComponent { + + @Input() policy: Policy; + +} diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index dbb33ee806..ade01a4312 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -1,119 +1,48 @@ - - + + + + +
+ + +
+ {{'submission.sections.sherpa.publication.information' + | translate}} +
+ + + +
+ + + +
+ + +
+ {{'submission.sections.sherpa.publisher.policy' + | translate}} +
+ + + +
+
+
+
- - + +
- Publication information + {{'submission.sections.sherpa.record.information' + | translate}}
-
-
-
-

Title

-
-
-

{{title}}

-
-
-
-
-

ISSNs

-
-
-

{{issn}}

-
-
- - -
-
-

romeoPub

-
-
-

- {{sherpaPoliciesData.details.journals.romeoPub}} -

-
-
-
-
-

zetoPub

-
-
-

- {{sherpaPoliciesData.details.journals.zetoPub}} -

-
-
-
- -
-
- - -
- - -
- Publisher Policy -
- -
-
-
-

- Open Access pathways permitted by this journal's policy are listed below by article version. - Click on a pathway for a more detailed view -

-
-
- - - -
-
-

- For more information, please see the following links: -

- -
-
- - -
- - - - +
\ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts index 5704d1abbb..662f066663 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts @@ -1,18 +1,14 @@ import { WorkspaceitemSectionSherpaPoliciesObject } from './../../../core/submission/models/workspaceitem-section-sherpa-policies.model'; import { SectionSherpaPoliciesService } from './section-sherpa-policies.service'; -import { Component, Inject, ViewChild } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, Inject, ViewChildren, QueryList } from '@angular/core'; -import { filter, map, mergeMap, take } from 'rxjs/operators'; -import { combineLatest, Observable, of, Subscription } from 'rxjs'; -import { TranslateService } from '@ngx-translate/core'; +import { Observable, of } from 'rxjs'; import { renderSectionFor } from '../sections-decorator'; import { SectionsType } from '../sections-type'; import { SectionDataObject } from '../models/section-data.model'; import { SectionsService } from '../sections.service'; import { SectionModelComponent } from '../models/section.model'; -import { NgbAccordionConfig } from '@ng-bootstrap/ng-bootstrap'; /** * This component represents a section for managing item's access conditions. @@ -25,6 +21,8 @@ import { NgbAccordionConfig } from '@ng-bootstrap/ng-bootstrap'; @renderSectionFor(SectionsType.SherpaPolicies) export class SubmissionSectionSherpaPoliciesComponent extends SectionModelComponent { + @ViewChildren('acc', { emitDistinctChangesOnly: true }) acc: QueryList; + /** * The accesses section data * @type {WorkspaceitemSectionAccessesObject} @@ -56,12 +54,18 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon } + ngAfterViewInit() { + this.acc.forEach(accordion => { + accordion.expandAll(); + }); + } + + /** * Initialize all instance variables and retrieve collection default access conditions */ protected onSectionInit(): void { this.sectionSherpaPoliciesService.getSherpaPoliciesData(this.submissionId, this.sectionData.id).subscribe((sherpaPolicies: WorkspaceitemSectionSherpaPoliciesObject) => { - console.log(sherpaPolicies); this.sherpaPoliciesData = sherpaPolicies; }); } diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index f1cedc8953..85b8067d0e 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -45,6 +45,9 @@ import { SubmissionAccessesConfigService } from '../core/config/submission-acces import { SectionAccessesService } from './sections/accesses/section-accesses.service'; import { SubmissionSectionSherpaPoliciesComponent } from './sections/sherpa-policies/section-sherpa-policies.component'; import { ContentAccordionComponent } from './sections/sherpa-policies/content-accordion/content-accordion.component'; +import { PublisherPolicyComponent } from './sections/sherpa-policies/publisher-policy/publisher-policy.component'; +import { PublicationInformationComponent } from './sections/sherpa-policies/publication-information/publication-information.component'; +import { MetadataInformationComponent } from './sections/sherpa-policies/metadata-information/metadata-information.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -80,6 +83,9 @@ const DECLARATIONS = [ SubmissionImportExternalPreviewComponent, SubmissionImportExternalCollectionComponent, ContentAccordionComponent, + PublisherPolicyComponent, + PublicationInformationComponent, + MetadataInformationComponent, ]; @NgModule({ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c3c68a6882..59079e34eb 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3987,6 +3987,59 @@ "submission.sections.accesses.form.until-placeholder": "Until", + "submission.sections.sherpa.publication.information": "Publication information", + + "submission.sections.sherpa.publication.information.title": "Title", + + "submission.sections.sherpa.publication.information.issns": "ISSNs", + + "submission.sections.sherpa.publication.information.url": "URL", + + "submission.sections.sherpa.publication.information.publishers": "Publisher", + + "submission.sections.sherpa.publication.information.romeoPub": "Romeo Pub", + + "submission.sections.sherpa.publication.information.zetoPub": "Zeto Pub", + + + "submission.sections.sherpa.publisher.policy": "Publisher Policy", + + "submission.sections.sherpa.publisher.policy.openaccess": "Open Access pathways permitted by this journal's policy are listed below by article version. Click on a pathway for a more detailed view", + + "submission.sections.sherpa.publisher.policy.more.information": "For more information, please see the following links:", + + "submission.sections.sherpa.publisher.policy.version": "Version", + + "submission.sections.sherpa.publisher.policy.embargo": "Embargo", + + "submission.sections.sherpa.publisher.policy.noembargo": "No Embargo", + + "submission.sections.sherpa.publisher.policy.license": "License", + + "submission.sections.sherpa.publisher.policy.prerequisites": "Prerequisites", + + "submission.sections.sherpa.publisher.policy.location": "Location", + + "submission.sections.sherpa.publisher.policy.conditions": "Conditions", + + + "submission.sections.sherpa.record.information": "Record Information", + + "submission.sections.sherpa.record.information.id": "ID", + + "submission.sections.sherpa.record.information.date.created": "Date Created", + + "submission.sections.sherpa.record.information.date.modified": "Last Modified", + + "submission.sections.sherpa.record.information.uri": "URI", + + + + + + + + "submission.submit.breadcrumbs": "New submission", "submission.submit.title": "New submission", From cb84bc758e026d0f42739622af7413358d6dd62b Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Tue, 26 Apr 2022 17:52:28 +0200 Subject: [PATCH 08/75] [CST-5270] Implemented new refresh button & code refactoring --- .../content-accordion.component.ts | 4 +- .../metadata-information.component.ts | 4 +- .../publication-information.component.ts | 4 +- .../publisher-policy.component.html | 3 -- .../publisher-policy.component.ts | 3 ++ .../section-sherpa-policies.component.html | 6 +++ .../section-sherpa-policies.component.scss | 5 ++ .../section-sherpa-policies.component.ts | 46 ++++++++++++++++--- .../section-sherpa-policies.service.ts | 4 +- src/assets/i18n/en.json5 | 1 + 10 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts index 12b48d10f4..378f08c8bc 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts @@ -7,7 +7,9 @@ import { Component, Input } from '@angular/core'; styleUrls: ['./content-accordion.component.scss'] }) export class ContentAccordionComponent { - + /** + * PermittedVersions to show information from + */ @Input() version: PermittedVersions; } diff --git a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts index 334aa43593..8b85237762 100644 --- a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts +++ b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts @@ -7,7 +7,9 @@ import { Metadata } from './../../../../core/submission/models/sherpa-policies-d styleUrls: ['./metadata-information.component.scss'] }) export class MetadataInformationComponent { - + /** + * Metadata to show information from + */ @Input() metadata: Metadata; } diff --git a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts index a6cd6cc3da..a5306406af 100644 --- a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts +++ b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts @@ -7,7 +7,9 @@ import { Journal } from './../../../../core/submission/models/sherpa-policies-de styleUrls: ['./publication-information.component.scss'] }) export class PublicationInformationComponent { - + /** + * Journal to show information from + */ @Input() journal: Journal; } diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html index 3bc1cb5084..4a03bae735 100644 --- a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html @@ -8,7 +8,6 @@
-

@@ -21,6 +20,4 @@

- -
\ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts index e526080d0d..dcbc115ddd 100644 --- a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts @@ -8,6 +8,9 @@ import { Component, Input, OnInit } from '@angular/core'; }) export class PublisherPolicyComponent { + /** + * Policy to show information from + */ @Input() policy: Policy; } diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index ade01a4312..6756793fd5 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -1,3 +1,9 @@ +
+ +
+ diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss index 8c1bfc2580..5a8e0e3c34 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss @@ -28,4 +28,9 @@ } } +} + +.refresh-container { + display: flex; + justify-content: right; } \ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts index 662f066663..f5eaaa773a 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts @@ -1,14 +1,18 @@ +import { JsonPatchOperationPathCombiner } from './../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { JsonPatchOperationsBuilder } from './../../../core/json-patch/builder/json-patch-operations-builder'; import { WorkspaceitemSectionSherpaPoliciesObject } from './../../../core/submission/models/workspaceitem-section-sherpa-policies.model'; import { SectionSherpaPoliciesService } from './section-sherpa-policies.service'; import { Component, Inject, ViewChildren, QueryList } from '@angular/core'; -import { Observable, of } from 'rxjs'; +import { Observable, of, Subscription } from 'rxjs'; import { renderSectionFor } from '../sections-decorator'; import { SectionsType } from '../sections-type'; import { SectionDataObject } from '../models/section-data.model'; import { SectionsService } from '../sections.service'; import { SectionModelComponent } from '../models/section.model'; +import { SubmissionService } from '../../submission.service'; +import { hasValue } from '../../../shared/empty.util'; /** * This component represents a section for managing item's access conditions. @@ -29,6 +33,17 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon */ public sherpaPoliciesData: WorkspaceitemSectionSherpaPoliciesObject; + /** + * The [[JsonPatchOperationPathCombiner]] object + * @type {JsonPatchOperationPathCombiner} + */ + protected pathCombiner: JsonPatchOperationPathCombiner; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + * @type {Array} + */ + protected subs: Subscription[] = []; /** * Initialize instance variables @@ -36,11 +51,15 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon * @param {SectionsService} sectionService * @param {SectionDataObject} injectedSectionData * @param {SectionSherpaPoliciesService} sectionSherpaPoliciesService + * @param {JsonPatchOperationsBuilder} operationsBuilder + * @param {SubmissionService} submissionService * @param {string} injectedSubmissionId */ constructor( protected sectionService: SectionsService, private sectionSherpaPoliciesService: SectionSherpaPoliciesService, + protected operationsBuilder: JsonPatchOperationsBuilder, + private submissionService: SubmissionService, @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, @Inject('submissionIdProvider') public injectedSubmissionId: string) { super(undefined, injectedSectionData, injectedSubmissionId); @@ -49,11 +68,15 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon /** * Unsubscribe from all subscriptions */ - // tslint:disable-next-line:no-empty onSectionDestroy() { - + this.subs + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); } + /** + * Expand all primary accordions + */ ngAfterViewInit() { this.acc.forEach(accordion => { accordion.expandAll(); @@ -65,9 +88,12 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon * Initialize all instance variables and retrieve collection default access conditions */ protected onSectionInit(): void { - this.sectionSherpaPoliciesService.getSherpaPoliciesData(this.submissionId, this.sectionData.id).subscribe((sherpaPolicies: WorkspaceitemSectionSherpaPoliciesObject) => { - this.sherpaPoliciesData = sherpaPolicies; - }); + this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); + this.subs.push( + this.sectionSherpaPoliciesService.getSherpaPoliciesData(this.submissionId, this.sectionData.id).subscribe((sherpaPolicies: WorkspaceitemSectionSherpaPoliciesObject) => { + this.sherpaPoliciesData = sherpaPolicies; + }) + ); } /** @@ -80,4 +106,12 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon return of(true); } + /** + * Refresh sherpa information + */ + refresh() { + this.operationsBuilder.remove(this.pathCombiner.getPath('retrievalTime')); + this.submissionService.dispatchSaveSection(this.submissionId, this.sectionData.id); + } + } diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts index c81caca41e..4ddf4509cb 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts @@ -10,7 +10,7 @@ import { isNotUndefined } from '../../../shared/empty.util'; import { submissionSectionDataFromIdSelector } from '../../selectors'; /** - * A service that provides methods to handle submission item's accesses condition state. + * A service that provides methods to handle submission item's sherpa policies state. */ @Injectable() export class SectionSherpaPoliciesService { @@ -24,7 +24,7 @@ export class SectionSherpaPoliciesService { /** - * Return item's accesses condition state. + * Return item's sherpa policies state. * * @param submissionId * The submission id diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 59079e34eb..3c2ad2505f 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4022,6 +4022,7 @@ "submission.sections.sherpa.publisher.policy.conditions": "Conditions", + "submission.sections.sherpa.publisher.policy.refresh": "Refresh", "submission.sections.sherpa.record.information": "Record Information", From 107199eb8e474848e0ff634f5c65687837a62e63 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Wed, 27 Apr 2022 13:40:33 +0200 Subject: [PATCH 09/75] [CST-5270] Finished unit testing --- .../section-sherpa-policies.service.mock.ts | 108 +++++++++++ .../content-accordion.component.spec.ts | 30 ++- .../metadata-information.component.spec.ts | 25 ++- .../publication-information.component.spec.ts | 26 ++- .../publisher-policy.component.spec.ts | 29 ++- .../section-sherpa-policies.component.spec.ts | 180 ++++++------------ 6 files changed, 263 insertions(+), 135 deletions(-) create mode 100644 src/app/shared/mocks/section-sherpa-policies.service.mock.ts diff --git a/src/app/shared/mocks/section-sherpa-policies.service.mock.ts b/src/app/shared/mocks/section-sherpa-policies.service.mock.ts new file mode 100644 index 0000000000..895e518d96 --- /dev/null +++ b/src/app/shared/mocks/section-sherpa-policies.service.mock.ts @@ -0,0 +1,108 @@ +import { WorkspaceitemSectionSherpaPoliciesObject } from './../../core/submission/models/workspaceitem-section-sherpa-policies.model'; +import { SubmissionFormsModel } from '../../core/config/models/config-submission-forms.model'; +import { of as observableOf } from 'rxjs'; + +export const dataRes = { + 'id': 'sherpaPolicies', + 'retrievalTime': '2022-04-20T09:44:39.870+00:00', + 'sherpaResponse': [ + { + 'error': false, + 'message': null, + 'metadata': { + 'id': 23803, + 'uri': 'http://v2.sherpa.ac.uk/id/publication/23803', + 'dateCreated': '2012-11-20 14:51:52', + 'dateModified': '2020-03-06 11:25:54', + 'inDOAJ': false, + 'publiclyVisible': true + }, + 'journals': [{ + 'titles': ['The Lancet', 'Lancet'], + 'url': 'http://www.thelancet.com/journals/lancet/issue/current', + 'issns': ['0140-6736', '1474-547X'], + 'romeoPub': 'Elsevier: The Lancet', + 'zetoPub': 'Elsevier: The Lancet', + 'publisher': { + 'name': 'Elsevier', + 'relationshipType': null, + 'country': null, + 'uri': 'http://www.elsevier.com/', + 'identifier': null, + 'publicationCount': 0, + 'paidAccessDescription': 'Open access', + 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' + }, + 'publishers': [{ + 'name': 'Elsevier', + 'relationshipType': null, + 'country': null, + 'uri': 'http://www.elsevier.com/', + 'identifier': null, + 'publicationCount': 0, + 'paidAccessDescription': 'Open access', + 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' + }], + 'policies': [{ + 'id': 0, + 'openAccessPermitted': false, + 'uri': null, + 'internalMoniker': 'Lancet', + 'permittedVersions': [{ + 'articleVersion': 'submitted', + 'option': 1, + 'conditions': ['Upon publication publisher copyright and source must be acknowledged', 'Upon publication must link to publisher version'], + 'prerequisites': [], + 'locations': ['Author\'s Homepage', 'Preprint Repository'], + 'licenses': [], + 'embargo': null + }, { + 'articleVersion': 'accepted', + 'option': 1, + 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], + 'prerequisites': [], + 'locations': ['Author\'s Homepage', 'Institutional Website'], + 'licenses': ['CC BY-NC-ND'], + 'embargo': null + }, { + 'articleVersion': 'accepted', + 'option': 2, + 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], + 'prerequisites': ['If Required by Funder'], + 'locations': ['Non-Commercial Repository'], + 'licenses': ['CC BY-NC-ND'], + 'embargo': null + }, { + 'articleVersion': 'accepted', + 'option': 3, + 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], + 'prerequisites': [], + 'locations': ['Non-Commercial Repository'], + 'licenses': [], + 'embargo': null + }], + 'urls': { + 'http://download.thelancet.com/flatcontentassets/authors/lancet-information-for-authors.pdf': 'Guidelines for Authors', + 'http://www.thelancet.com/journals/lancet/article/PIIS0140-6736%2813%2960720-5/fulltext': 'The Lancet journals welcome a new open access policy', + 'http://www.thelancet.com/lancet-information-for-authors/after-publication': 'What happens after publication?', + 'http://www.thelancet.com/lancet/information-for-authors/disclosure-of-results': 'Disclosure of results before publication', + 'https://www.elsevier.com/__data/assets/pdf_file/0005/78476/external-embargo-list.pdf': 'Journal Embargo Period List', + 'https://www.elsevier.com/__data/assets/pdf_file/0011/78473/UK-Embargo-Periods.pdf': 'Journal Embargo List for UK Authors' + }, + 'openAccessProhibited': false, + 'publicationCount': 0, + 'preArchiving': 'can', + 'postArchiving': 'can', + 'pubArchiving': 'cannot' + }], + 'inDOAJ': false + }] + } + ] +} as WorkspaceitemSectionSherpaPoliciesObject; + +export function getSherpaPoliciesData() { + return jasmine.createSpyObj('SectionAccessesService', { + getSherpaPoliciesData: observableOf(dataRes), + }); +} diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts index 3b4d18a633..ec1180a6c9 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts @@ -1,25 +1,51 @@ +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateLoaderMock } from './../../../../shared/testing/translate-loader.mock'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ContentAccordionComponent } from './content-accordion.component'; +import { dataRes } from './../../../../shared/mocks/section-sherpa-policies.service.mock'; +import { DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; describe('ContentAccordionComponent', () => { let component: ContentAccordionComponent; let fixture: ComponentFixture; + let de: DebugElement; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ContentAccordionComponent ] + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + NgbAccordionModule + ], + declarations: [ContentAccordionComponent] }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(ContentAccordionComponent); component = fixture.componentInstance; + de = fixture.debugElement; + component.version = dataRes.sherpaResponse[0].journals[0].policies[0].permittedVersions[0]; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should show accordion', () => { + expect(de.query(By.css('ngb-accordion'))).toBeTruthy(); + }); + + it('should show 5 rows', () => { + expect(de.queryAll(By.css('.row')).length).toEqual(5); + }); }); diff --git a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts index c1257fc802..b329fb1821 100644 --- a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts @@ -1,25 +1,46 @@ +import { TranslateLoaderMock } from './../../../../shared/testing/translate-loader.mock'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MetadataInformationComponent } from './metadata-information.component'; +import { dataRes } from './../../../../shared/mocks/section-sherpa-policies.service.mock'; +import { DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; describe('MetadataInformationComponent', () => { let component: MetadataInformationComponent; let fixture: ComponentFixture; + let de: DebugElement; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ MetadataInformationComponent ] + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [MetadataInformationComponent] }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(MetadataInformationComponent); component = fixture.componentInstance; + de = fixture.debugElement; + component.metadata = dataRes.sherpaResponse[0].metadata; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should show 4 rows', () => { + expect(de.queryAll(By.css('.row')).length).toEqual(4); + }); + }); diff --git a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts index 3f264df21f..e1acc016ab 100644 --- a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts @@ -1,25 +1,47 @@ +import { TranslateLoaderMock } from './../../../../shared/testing/translate-loader.mock'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PublicationInformationComponent } from './publication-information.component'; +import { dataRes } from './../../../../shared/mocks/section-sherpa-policies.service.mock'; +import { DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; describe('PublicationInformationComponent', () => { let component: PublicationInformationComponent; let fixture: ComponentFixture; + let de: DebugElement; + beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ PublicationInformationComponent ] + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [PublicationInformationComponent] }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(PublicationInformationComponent); component = fixture.componentInstance; + de = fixture.debugElement; + component.journal = dataRes.sherpaResponse[0].journals[0]; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should show 6 rows', () => { + expect(de.queryAll(By.css('.row')).length).toEqual(6); + }); + }); diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts index 1821a97795..9ba77adf94 100644 --- a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts @@ -1,25 +1,50 @@ +import { TranslateLoaderMock } from './../../../../shared/testing/translate-loader.mock'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PublisherPolicyComponent } from './publisher-policy.component'; +import { dataRes } from './../../../../shared/mocks/section-sherpa-policies.service.mock'; +import { By } from '@angular/platform-browser'; +import { DebugElement } from '@angular/core'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock'; describe('PublisherPolicyComponent', () => { let component: PublisherPolicyComponent; let fixture: ComponentFixture; + let de: DebugElement; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ PublisherPolicyComponent ] + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [PublisherPolicyComponent], }) - .compileComponents(); + .compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(PublisherPolicyComponent); component = fixture.componentInstance; + de = fixture.debugElement; + component.policy = dataRes.sherpaResponse[0].journals[0].policies[0]; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it('should show content accordion', () => { + expect(de.query(By.css('ds-content-accordion'))).toBeTruthy(); + }); + + it('should show 2 rows', () => { + expect(de.queryAll(By.css('.row')).length).toEqual(2); + }); }); diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts index 5509cab4bb..ce64406486 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts @@ -1,100 +1,80 @@ -import { FormService } from '../../../shared/form/form.service'; +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { SubmissionServiceStub } from './../../../shared/testing/submission-service.stub'; +import { dataRes, getSherpaPoliciesData } from './../../../shared/mocks/section-sherpa-policies.service.mock'; import { ComponentFixture, inject, TestBed } from '@angular/core/testing'; -import { SubmissionSectionAccessesComponent } from './section-accesses.component'; import { SectionsService } from '../sections.service'; import { SectionsServiceStub } from '../../../shared/testing/sections-service.stub'; import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock'; -import { SubmissionAccessesConfigService } from '../../../core/config/submission-accesses-config.service'; -import { - getSubmissionAccessesConfigNotChangeDiscoverableService, - getSubmissionAccessesConfigService -} from '../../../shared/mocks/section-accesses-config.service.mock'; -import { SectionAccessesService } from './section-accesses.service'; -import { SectionFormOperationsService } from '../form/section-form-operations.service'; import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; -import { TranslateModule, TranslateService } from '@ngx-translate/core'; -import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; -import { getSectionAccessesService } from '../../../shared/mocks/section-accesses.service.mock'; -import { getMockFormOperationsService } from '../../../shared/mocks/form-operations-service.mock'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock'; -import { SubmissionJsonPatchOperationsServiceStub } from '../../../shared/testing/submission-json-patch-operations-service.stub'; -import { BrowserModule } from '@angular/platform-browser'; +import { BrowserModule, By } from '@angular/platform-browser'; -import { of as observableOf } from 'rxjs'; import { Store } from '@ngrx/store'; -import { FormComponent } from '../../../shared/form/form.component'; -import { - DynamicCheckboxModel, - DynamicDatePickerModel, - DynamicFormArrayModel, - DynamicSelectModel -} from '@ng-dynamic-forms/core'; import { AppState } from '../../../app.reducer'; -import { getMockFormService } from '../../../shared/mocks/form-service.mock'; -import { mockAccessesFormData } from '../../../shared/mocks/submission.mock'; -import { accessConditionChangeEvent, checkboxChangeEvent } from '../../../shared/testing/form-event.stub'; +import { SectionSherpaPoliciesService } from './section-sherpa-policies.service'; +import { SubmissionSectionSherpaPoliciesComponent } from './section-sherpa-policies.component'; +import { SubmissionService } from '../../submission.service'; +import { DebugElement } from '@angular/core'; +import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock'; -describe('SubmissionSectionAccessesComponent', () => { - let component: SubmissionSectionAccessesComponent; - let fixture: ComponentFixture; +describe('SubmissionSectionSherpaPoliciesComponent', () => { + let component: SubmissionSectionSherpaPoliciesComponent; + let fixture: ComponentFixture; + let de: DebugElement; const sectionsServiceStub = new SectionsServiceStub(); // const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex); const builderService: FormBuilderService = getMockFormBuilderService(); - const submissionAccessesConfigService = getSubmissionAccessesConfigService(); - const sectionAccessesService = getSectionAccessesService(); - const sectionFormOperationsService = getMockFormOperationsService(); + const sectionSherpaPoliciesService = getSherpaPoliciesData(); + const operationsBuilder = jasmine.createSpyObj('operationsBuilder', { add: undefined, remove: undefined, replace: undefined, }); - let formService: any; - const storeStub = jasmine.createSpyObj('store', ['dispatch']); const sectionData = { - header: 'submit.progressbar.accessCondition', - config: 'http://localhost:8080/server/api/config/submissionaccessoptions/AccessConditionDefaultConfiguration', + header: 'submit.progressbar.sherpaPolicies', + config: 'http://localhost:8080/server/api/config/submissionaccessoptions/SherpaPoliciesDefaultConfiguration', mandatory: true, - sectionType: 'accessCondition', + sectionType: 'sherpaPolicies', collapsed: false, enabled: true, - data: { - discoverable: true, - accessConditions: [] - }, + data: dataRes, errorsToShow: [], serverValidationErrors: [], isLoading: false, isValid: true }; - describe('First with canChangeDiscoverable true', () => { + describe('SubmissionSectionSherpaPoliciesComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ BrowserModule, - TranslateModule.forRoot() + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + NgbAccordionModule ], - declarations: [SubmissionSectionAccessesComponent, FormComponent], + declarations: [SubmissionSectionSherpaPoliciesComponent], providers: [ { provide: SectionsService, useValue: sectionsServiceStub }, - { provide: FormBuilderService, useValue: builderService }, - { provide: SubmissionAccessesConfigService, useValue: submissionAccessesConfigService }, - { provide: SectionAccessesService, useValue: sectionAccessesService }, - { provide: SectionFormOperationsService, useValue: sectionFormOperationsService }, + { provide: SectionSherpaPoliciesService, useValue: sectionSherpaPoliciesService }, { provide: JsonPatchOperationsBuilder, useValue: operationsBuilder }, - { provide: TranslateService, useValue: getMockTranslateService() }, - { provide: FormService, useValue: getMockFormService() }, + { provide: SubmissionService, useValue: SubmissionServiceStub }, { provide: Store, useValue: storeStub }, - { provide: SubmissionJsonPatchOperationsService, useValue: SubmissionJsonPatchOperationsServiceStub }, { provide: 'sectionDataProvider', useValue: sectionData }, { provide: 'submissionIdProvider', useValue: '1508' }, ] @@ -103,12 +83,9 @@ describe('SubmissionSectionAccessesComponent', () => { }); beforeEach(inject([Store], (store: Store) => { - fixture = TestBed.createComponent(SubmissionSectionAccessesComponent); + fixture = TestBed.createComponent(SubmissionSectionSherpaPoliciesComponent); component = fixture.componentInstance; - formService = TestBed.inject(FormService); - formService.validateAllFormFields.and.callFake(() => null); - formService.isValid.and.returnValue(observableOf(true)); - formService.getFormData.and.returnValue(observableOf(mockAccessesFormData)); + de = fixture.debugElement; fixture.detectChanges(); })); @@ -117,88 +94,37 @@ describe('SubmissionSectionAccessesComponent', () => { expect(component).toBeTruthy(); }); - it('should have created formModel', () => { - expect(component.formModel).toBeTruthy(); + it('should show accordions', () => { + expect(de.query(By.css('ngb-accordion'))).toBeTruthy(); }); - it('should have formModel length should be 2', () => { - expect(component.formModel.length).toEqual(2); + it('should show expanded accordion', () => { + expect(component.acc.first.isExpanded('publication-information-0')).toBeTrue(); }); - it('formModel should have 1 model type checkbox and 1 model type array', () => { - expect(component.formModel[0] instanceof DynamicCheckboxModel).toBeTrue(); - expect(component.formModel[1] instanceof DynamicFormArrayModel).toBeTrue(); + it('should show refresh button', () => { + expect(de.query(By.css('.refresh-container > button'))).toBeTruthy(); }); - it('formModel type array should have formgroup with 1 input and 2 datepickers', () => { - const formModel: any = component.formModel[1]; - const formGroup = formModel.groupFactory()[0].group; - expect(formGroup[0] instanceof DynamicSelectModel).toBeTrue(); - expect(formGroup[1] instanceof DynamicDatePickerModel).toBeTrue(); - expect(formGroup[2] instanceof DynamicDatePickerModel).toBeTrue(); + it('should show publisher information', () => { + expect(de.query(By.css('ds-publication-information'))).toBeTruthy(); }); - it('when checkbox changed it should call operationsBuilder replace function', () => { - component.onChange(checkboxChangeEvent); + it('should show publisher policy', () => { + expect(de.query(By.css('ds-publisher-policy'))).toBeTruthy(); + }); + + it('should show metadata information', () => { + expect(de.query(By.css('ds-metadata-information'))).toBeTruthy(); + }); + + it('when refresh button click operationsBuilder.remove should have been called', () => { + de.query(By.css('.refresh-container > button')).nativeElement.click(); fixture.detectChanges(); - - expect(operationsBuilder.replace).toHaveBeenCalled(); + expect(operationsBuilder.remove).toHaveBeenCalled(); }); - it('when dropdown select changed it should call operationsBuilder add function', () => { - component.onChange(accessConditionChangeEvent); - fixture.detectChanges(); - expect(operationsBuilder.add).toHaveBeenCalled(); - }); + }); - describe('when canDescoverable is false', () => { - - - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - BrowserModule, - TranslateModule.forRoot() - ], - declarations: [SubmissionSectionAccessesComponent, FormComponent], - providers: [ - { provide: SectionsService, useValue: sectionsServiceStub }, - { provide: FormBuilderService, useValue: builderService }, - { provide: SubmissionAccessesConfigService, useValue: getSubmissionAccessesConfigNotChangeDiscoverableService() }, - { provide: SectionAccessesService, useValue: sectionAccessesService }, - { provide: SectionFormOperationsService, useValue: sectionFormOperationsService }, - { provide: JsonPatchOperationsBuilder, useValue: operationsBuilder }, - { provide: TranslateService, useValue: getMockTranslateService() }, - { provide: FormService, useValue: getMockFormService() }, - { provide: Store, useValue: storeStub }, - { provide: SubmissionJsonPatchOperationsService, useValue: SubmissionJsonPatchOperationsServiceStub }, - { provide: 'sectionDataProvider', useValue: sectionData }, - { provide: 'submissionIdProvider', useValue: '1508' }, - ] - }) - .compileComponents(); - }); - - beforeEach(inject([Store], (store: Store) => { - fixture = TestBed.createComponent(SubmissionSectionAccessesComponent); - component = fixture.componentInstance; - formService = TestBed.inject(FormService); - formService.validateAllFormFields.and.callFake(() => null); - formService.isValid.and.returnValue(observableOf(true)); - formService.getFormData.and.returnValue(observableOf(mockAccessesFormData)); - fixture.detectChanges(); - })); - - - it('should have formModel length should be 1', () => { - expect(component.formModel.length).toEqual(1); - }); - - it('formModel should have only 1 model type array', () => { - expect(component.formModel[0] instanceof DynamicFormArrayModel).toBeTrue(); - }); - - }); }); From f9d55dc3e8130db99926e34bf3852a1857f3a0e9 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 27 Apr 2022 17:19:38 +0200 Subject: [PATCH 10/75] [CST-5270] Refactoring implementation and remove mock response --- ...spaceitem-section-sherpa-policies.model.ts | 2 +- .../section-sherpa-policies.service.mock.ts | 17 +++----- src/app/submission/sections/sections-type.ts | 2 +- .../content-accordion.component.spec.ts | 7 ++-- .../content-accordion.component.ts | 3 +- .../metadata-information.component.spec.ts | 7 ++-- .../metadata-information.component.ts | 3 +- .../publication-information.component.spec.ts | 6 +-- .../publication-information.component.ts | 5 ++- .../publisher-policy.component.spec.ts | 11 +++-- .../publisher-policy.component.ts | 5 ++- .../section-sherpa-policies.component.html | 13 +++--- .../section-sherpa-policies.component.spec.ts | 22 ++++------ .../section-sherpa-policies.component.ts | 35 ++++++++++------ .../section-sherpa-policies.service.ts | 42 ------------------- src/app/submission/submission.module.ts | 36 +++++++++++----- src/assets/i18n/en.json5 | 6 ++- 17 files changed, 100 insertions(+), 122 deletions(-) delete mode 100644 src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts diff --git a/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts b/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts index cbef185090..c57beadbb9 100644 --- a/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-sherpa-policies.model.ts @@ -18,5 +18,5 @@ export interface WorkspaceitemSectionSherpaPoliciesObject { /** * The sherpa policies details */ - sherpaResponse: SherpaPoliciesDetailsObject[]; + sherpaResponse: SherpaPoliciesDetailsObject; } diff --git a/src/app/shared/mocks/section-sherpa-policies.service.mock.ts b/src/app/shared/mocks/section-sherpa-policies.service.mock.ts index 895e518d96..b4947ea5b4 100644 --- a/src/app/shared/mocks/section-sherpa-policies.service.mock.ts +++ b/src/app/shared/mocks/section-sherpa-policies.service.mock.ts @@ -1,11 +1,11 @@ -import { WorkspaceitemSectionSherpaPoliciesObject } from './../../core/submission/models/workspaceitem-section-sherpa-policies.model'; -import { SubmissionFormsModel } from '../../core/config/models/config-submission-forms.model'; -import { of as observableOf } from 'rxjs'; +import { + WorkspaceitemSectionSherpaPoliciesObject +} from '../../core/submission/models/workspaceitem-section-sherpa-policies.model'; -export const dataRes = { +export const SherpaDataResponse = { 'id': 'sherpaPolicies', 'retrievalTime': '2022-04-20T09:44:39.870+00:00', - 'sherpaResponse': [ + 'sherpaResponse': { 'error': false, 'message': null, @@ -98,11 +98,4 @@ export const dataRes = { 'inDOAJ': false }] } - ] } as WorkspaceitemSectionSherpaPoliciesObject; - -export function getSherpaPoliciesData() { - return jasmine.createSpyObj('SectionAccessesService', { - getSherpaPoliciesData: observableOf(dataRes), - }); -} diff --git a/src/app/submission/sections/sections-type.ts b/src/app/submission/sections/sections-type.ts index f998ef4554..6b6f839b7c 100644 --- a/src/app/submission/sections/sections-type.ts +++ b/src/app/submission/sections/sections-type.ts @@ -6,5 +6,5 @@ export enum SectionsType { CcLicense = 'cclicense', collection = 'collection', AccessesCondition = 'accessCondition', - SherpaPolicies = 'sherpaPolicies', + SherpaPolicies = 'sherpaPolicy', } diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts index ec1180a6c9..382450fbd3 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts @@ -1,12 +1,13 @@ import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateLoaderMock } from './../../../../shared/testing/translate-loader.mock'; +import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ContentAccordionComponent } from './content-accordion.component'; -import { dataRes } from './../../../../shared/mocks/section-sherpa-policies.service.mock'; + import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { SherpaDataResponse } from '../../../../shared/mocks/section-sherpa-policies.service.mock'; describe('ContentAccordionComponent', () => { let component: ContentAccordionComponent; @@ -33,7 +34,7 @@ describe('ContentAccordionComponent', () => { fixture = TestBed.createComponent(ContentAccordionComponent); component = fixture.componentInstance; de = fixture.debugElement; - component.version = dataRes.sherpaResponse[0].journals[0].policies[0].permittedVersions[0]; + component.version = SherpaDataResponse.sherpaResponse.journals[0].policies[0].permittedVersions[0]; fixture.detectChanges(); }); diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts index 378f08c8bc..a2decc38e1 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.ts @@ -1,6 +1,7 @@ -import { PermittedVersions } from './../../../../core/submission/models/sherpa-policies-details.model'; import { Component, Input } from '@angular/core'; +import { PermittedVersions } from '../../../../core/submission/models/sherpa-policies-details.model'; + @Component({ selector: 'ds-content-accordion', templateUrl: './content-accordion.component.html', diff --git a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts index b329fb1821..9a60a6d010 100644 --- a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.spec.ts @@ -1,11 +1,12 @@ -import { TranslateLoaderMock } from './../../../../shared/testing/translate-loader.mock'; +import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MetadataInformationComponent } from './metadata-information.component'; -import { dataRes } from './../../../../shared/mocks/section-sherpa-policies.service.mock'; + import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { SherpaDataResponse } from '../../../../shared/mocks/section-sherpa-policies.service.mock'; describe('MetadataInformationComponent', () => { let component: MetadataInformationComponent; @@ -31,7 +32,7 @@ describe('MetadataInformationComponent', () => { fixture = TestBed.createComponent(MetadataInformationComponent); component = fixture.componentInstance; de = fixture.debugElement; - component.metadata = dataRes.sherpaResponse[0].metadata; + component.metadata = SherpaDataResponse.sherpaResponse.metadata; fixture.detectChanges(); }); diff --git a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts index 8b85237762..cced669024 100644 --- a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts +++ b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.ts @@ -1,5 +1,6 @@ import { Component, Input } from '@angular/core'; -import { Metadata } from './../../../../core/submission/models/sherpa-policies-details.model'; + +import { Metadata } from '../../../../core/submission/models/sherpa-policies-details.model'; @Component({ selector: 'ds-metadata-information', diff --git a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts index e1acc016ab..c5dc896858 100644 --- a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.spec.ts @@ -1,11 +1,11 @@ -import { TranslateLoaderMock } from './../../../../shared/testing/translate-loader.mock'; +import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PublicationInformationComponent } from './publication-information.component'; -import { dataRes } from './../../../../shared/mocks/section-sherpa-policies.service.mock'; import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { SherpaDataResponse } from '../../../../shared/mocks/section-sherpa-policies.service.mock'; describe('PublicationInformationComponent', () => { let component: PublicationInformationComponent; @@ -32,7 +32,7 @@ describe('PublicationInformationComponent', () => { fixture = TestBed.createComponent(PublicationInformationComponent); component = fixture.componentInstance; de = fixture.debugElement; - component.journal = dataRes.sherpaResponse[0].journals[0]; + component.journal = SherpaDataResponse.sherpaResponse.journals[0]; fixture.detectChanges(); }); diff --git a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts index a5306406af..54973eba02 100644 --- a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts +++ b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.ts @@ -1,5 +1,6 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { Journal } from './../../../../core/submission/models/sherpa-policies-details.model'; +import { Component, Input } from '@angular/core'; + +import { Journal } from '../../../../core/submission/models/sherpa-policies-details.model'; @Component({ selector: 'ds-publication-information', diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts index 9ba77adf94..da97f824b1 100644 --- a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts @@ -1,12 +1,11 @@ -import { TranslateLoaderMock } from './../../../../shared/testing/translate-loader.mock'; import { ComponentFixture, TestBed } from '@angular/core/testing'; - import { PublisherPolicyComponent } from './publisher-policy.component'; -import { dataRes } from './../../../../shared/mocks/section-sherpa-policies.service.mock'; import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; -import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; -import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; + +import { SherpaDataResponse } from '../../../../shared/mocks/section-sherpa-policies.service.mock'; +import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; describe('PublisherPolicyComponent', () => { let component: PublisherPolicyComponent; @@ -32,7 +31,7 @@ describe('PublisherPolicyComponent', () => { fixture = TestBed.createComponent(PublisherPolicyComponent); component = fixture.componentInstance; de = fixture.debugElement; - component.policy = dataRes.sherpaResponse[0].journals[0].policies[0]; + component.policy = SherpaDataResponse.sherpaResponse.journals[0].policies[0]; fixture.detectChanges(); }); diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts index dcbc115ddd..639361beb0 100644 --- a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts @@ -1,5 +1,6 @@ -import { Policy } from './../../../../core/submission/models/sherpa-policies-details.model'; -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input } from '@angular/core'; + +import { Policy } from '../../../../core/submission/models/sherpa-policies-details.model'; @Component({ selector: 'ds-publisher-policy', diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index 6756793fd5..94553dd4c3 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -1,10 +1,13 @@ + + +
- + @@ -38,11 +41,11 @@
- +
- - + +
{{'submission.sections.sherpa.record.information' | translate}} @@ -51,4 +54,4 @@
-
\ No newline at end of file + diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts index ce64406486..fb8f8ba355 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts @@ -1,25 +1,21 @@ import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; -import { SubmissionServiceStub } from './../../../shared/testing/submission-service.stub'; -import { dataRes, getSherpaPoliciesData } from './../../../shared/mocks/section-sherpa-policies.service.mock'; +import { SubmissionServiceStub } from '../../../shared/testing/submission-service.stub'; +import { SherpaDataResponse } from '../../../shared/mocks/section-sherpa-policies.service.mock'; import { ComponentFixture, inject, TestBed } from '@angular/core/testing'; import { SectionsService } from '../sections.service'; import { SectionsServiceStub } from '../../../shared/testing/sections-service.stub'; - -import { FormBuilderService } from '../../../shared/form/builder/form-builder.service'; -import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock'; import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; -import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; -import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { BrowserModule, By } from '@angular/platform-browser'; import { Store } from '@ngrx/store'; import { AppState } from '../../../app.reducer'; -import { SectionSherpaPoliciesService } from './section-sherpa-policies.service'; import { SubmissionSectionSherpaPoliciesComponent } from './section-sherpa-policies.component'; import { SubmissionService } from '../../submission.service'; import { DebugElement } from '@angular/core'; -import { TranslateLoaderMock } from 'src/app/shared/mocks/translate-loader.mock'; +import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; +import { of as observableOf } from 'rxjs'; describe('SubmissionSectionSherpaPoliciesComponent', () => { let component: SubmissionSectionSherpaPoliciesComponent; @@ -27,10 +23,6 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { let de: DebugElement; const sectionsServiceStub = new SectionsServiceStub(); - // const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex); - - const builderService: FormBuilderService = getMockFormBuilderService(); - const sectionSherpaPoliciesService = getSherpaPoliciesData(); const operationsBuilder = jasmine.createSpyObj('operationsBuilder', { add: undefined, @@ -47,7 +39,7 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { sectionType: 'sherpaPolicies', collapsed: false, enabled: true, - data: dataRes, + data: SherpaDataResponse, errorsToShow: [], serverValidationErrors: [], isLoading: false, @@ -71,7 +63,6 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { declarations: [SubmissionSectionSherpaPoliciesComponent], providers: [ { provide: SectionsService, useValue: sectionsServiceStub }, - { provide: SectionSherpaPoliciesService, useValue: sectionSherpaPoliciesService }, { provide: JsonPatchOperationsBuilder, useValue: operationsBuilder }, { provide: SubmissionService, useValue: SubmissionServiceStub }, { provide: Store, useValue: storeStub }, @@ -86,6 +77,7 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { fixture = TestBed.createComponent(SubmissionSectionSherpaPoliciesComponent); component = fixture.componentInstance; de = fixture.debugElement; + sectionsServiceStub.getSectionData.and.returnValue(observableOf(SherpaDataResponse)) fixture.detectChanges(); })); diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts index f5eaaa773a..d287a182ba 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts @@ -1,18 +1,19 @@ -import { JsonPatchOperationPathCombiner } from './../../../core/json-patch/builder/json-patch-operation-path-combiner'; -import { JsonPatchOperationsBuilder } from './../../../core/json-patch/builder/json-patch-operations-builder'; -import { WorkspaceitemSectionSherpaPoliciesObject } from './../../../core/submission/models/workspaceitem-section-sherpa-policies.model'; -import { SectionSherpaPoliciesService } from './section-sherpa-policies.service'; -import { Component, Inject, ViewChildren, QueryList } from '@angular/core'; +import { Component, Inject, QueryList, ViewChildren } from '@angular/core'; -import { Observable, of, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; +import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; +import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; +import { + WorkspaceitemSectionSherpaPoliciesObject +} from '../../../core/submission/models/workspaceitem-section-sherpa-policies.model'; import { renderSectionFor } from '../sections-decorator'; import { SectionsType } from '../sections-type'; import { SectionDataObject } from '../models/section-data.model'; import { SectionsService } from '../sections.service'; import { SectionModelComponent } from '../models/section.model'; import { SubmissionService } from '../../submission.service'; -import { hasValue } from '../../../shared/empty.util'; +import { hasValue, isEmpty } from '../../../shared/empty.util'; /** * This component represents a section for managing item's access conditions. @@ -31,7 +32,7 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon * The accesses section data * @type {WorkspaceitemSectionAccessesObject} */ - public sherpaPoliciesData: WorkspaceitemSectionSherpaPoliciesObject; + public sherpaPoliciesData$: BehaviorSubject = new BehaviorSubject(null); /** * The [[JsonPatchOperationPathCombiner]] object @@ -50,14 +51,12 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon * * @param {SectionsService} sectionService * @param {SectionDataObject} injectedSectionData - * @param {SectionSherpaPoliciesService} sectionSherpaPoliciesService * @param {JsonPatchOperationsBuilder} operationsBuilder * @param {SubmissionService} submissionService * @param {string} injectedSubmissionId */ constructor( protected sectionService: SectionsService, - private sectionSherpaPoliciesService: SectionSherpaPoliciesService, protected operationsBuilder: JsonPatchOperationsBuilder, private submissionService: SubmissionService, @Inject('sectionDataProvider') public injectedSectionData: SectionDataObject, @@ -88,11 +87,14 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon * Initialize all instance variables and retrieve collection default access conditions */ protected onSectionInit(): void { + this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); this.subs.push( - this.sectionSherpaPoliciesService.getSherpaPoliciesData(this.submissionId, this.sectionData.id).subscribe((sherpaPolicies: WorkspaceitemSectionSherpaPoliciesObject) => { - this.sherpaPoliciesData = sherpaPolicies; - }) + this.sectionService.getSectionData(this.submissionId, this.sectionData.id, this.sectionData.sectionType) + .subscribe((sherpaPolicies: WorkspaceitemSectionSherpaPoliciesObject) => { + this.sherpaPoliciesData$.next(sherpaPolicies); + console.log(this.sherpaPoliciesData$.value) + }) ); } @@ -106,6 +108,13 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon return of(true); } + /** + * Check if section has no data + */ + hasNoData(): boolean { + return isEmpty(this.sherpaPoliciesData$.value); + } + /** * Refresh sherpa information */ diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts deleted file mode 100644 index 4ddf4509cb..0000000000 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.service.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { WorkspaceitemSectionSherpaPoliciesObject } from './../../../core/submission/models/workspaceitem-section-sherpa-policies.model'; -import { Injectable } from '@angular/core'; - -import { Observable } from 'rxjs'; -import { distinctUntilChanged, filter } from 'rxjs/operators'; -import { Store } from '@ngrx/store'; - -import { SubmissionState } from '../../submission.reducers'; -import { isNotUndefined } from '../../../shared/empty.util'; -import { submissionSectionDataFromIdSelector } from '../../selectors'; - -/** - * A service that provides methods to handle submission item's sherpa policies state. - */ -@Injectable() -export class SectionSherpaPoliciesService { - - /** - * Initialize service variables - * - * @param {Store} store - */ - constructor(private store: Store) { } - - - /** - * Return item's sherpa policies state. - * - * @param submissionId - * The submission id - * @param sectionId - * The section id - * @returns {Observable} - * Emits bitstream's metadata - */ - public getSherpaPoliciesData(submissionId: string, sectionId: string): Observable { - - return this.store.select(submissionSectionDataFromIdSelector(submissionId, sectionId)).pipe( - filter((state) => isNotUndefined(state)), - distinctUntilChanged()); - } -} diff --git a/src/app/submission/submission.module.ts b/src/app/submission/submission.module.ts index 85b8067d0e..b0b6d5195d 100644 --- a/src/app/submission/submission.module.ts +++ b/src/app/submission/submission.module.ts @@ -1,4 +1,3 @@ -import { SectionSherpaPoliciesService } from './sections/sherpa-policies/section-sherpa-policies.service'; import { NgModule } from '@angular/core'; import { CoreModule } from '../core/core.module'; import { SharedModule } from '../shared/shared.module'; @@ -23,15 +22,27 @@ import { SubmissionSectionLicenseComponent } from './sections/license/section-li import { SubmissionUploadsConfigService } from '../core/config/submission-uploads-config.service'; import { SubmissionEditComponent } from './edit/submission-edit.component'; import { SubmissionSectionUploadFileComponent } from './sections/upload/file/section-upload-file.component'; -import { SubmissionSectionUploadFileEditComponent } from './sections/upload/file/edit/section-upload-file-edit.component'; -import { SubmissionSectionUploadFileViewComponent } from './sections/upload/file/view/section-upload-file-view.component'; -import { SubmissionSectionUploadAccessConditionsComponent } from './sections/upload/accessConditions/submission-section-upload-access-conditions.component'; +import { + SubmissionSectionUploadFileEditComponent +} from './sections/upload/file/edit/section-upload-file-edit.component'; +import { + SubmissionSectionUploadFileViewComponent +} from './sections/upload/file/view/section-upload-file-view.component'; +import { + SubmissionSectionUploadAccessConditionsComponent +} from './sections/upload/accessConditions/submission-section-upload-access-conditions.component'; import { SubmissionSubmitComponent } from './submit/submission-submit.component'; import { storeModuleConfig } from '../app.reducer'; import { SubmissionImportExternalComponent } from './import-external/submission-import-external.component'; -import { SubmissionImportExternalSearchbarComponent } from './import-external/import-external-searchbar/submission-import-external-searchbar.component'; -import { SubmissionImportExternalPreviewComponent } from './import-external/import-external-preview/submission-import-external-preview.component'; -import { SubmissionImportExternalCollectionComponent } from './import-external/import-external-collection/submission-import-external-collection.component'; +import { + SubmissionImportExternalSearchbarComponent +} from './import-external/import-external-searchbar/submission-import-external-searchbar.component'; +import { + SubmissionImportExternalPreviewComponent +} from './import-external/import-external-preview/submission-import-external-preview.component'; +import { + SubmissionImportExternalCollectionComponent +} from './import-external/import-external-collection/submission-import-external-collection.component'; import { SubmissionSectionCcLicensesComponent } from './sections/cc-license/submission-section-cc-licenses.component'; import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module'; @@ -46,8 +57,12 @@ import { SectionAccessesService } from './sections/accesses/section-accesses.ser import { SubmissionSectionSherpaPoliciesComponent } from './sections/sherpa-policies/section-sherpa-policies.component'; import { ContentAccordionComponent } from './sections/sherpa-policies/content-accordion/content-accordion.component'; import { PublisherPolicyComponent } from './sections/sherpa-policies/publisher-policy/publisher-policy.component'; -import { PublicationInformationComponent } from './sections/sherpa-policies/publication-information/publication-information.component'; -import { MetadataInformationComponent } from './sections/sherpa-policies/metadata-information/metadata-information.component'; +import { + PublicationInformationComponent +} from './sections/sherpa-policies/publication-information/publication-information.component'; +import { + MetadataInformationComponent +} from './sections/sherpa-policies/metadata-information/metadata-information.component'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -108,8 +123,7 @@ const DECLARATIONS = [ SectionsService, SubmissionUploadsConfigService, SubmissionAccessesConfigService, - SectionAccessesService, - SectionSherpaPoliciesService + SectionAccessesService ] }) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index ec9bc5b3a0..d9f6523ed4 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3874,10 +3874,14 @@ "submission.sections.submit.progressbar.license": "Deposit license", + "submission.sections.submit.progressbar.sherpapolicy": "Sherpa policies", + "submission.sections.submit.progressbar.upload": "Upload files", + "submission.sections.sherpa-policy.title-empty": "No information available", + "submission.sections.status.errors.title": "Errors", "submission.sections.status.valid.title": "Valid", @@ -3990,7 +3994,7 @@ "submission.sections.sherpa.publication.information": "Publication information", - + "submission.sections.sherpa.publication.information.title": "Title", "submission.sections.sherpa.publication.information.issns": "ISSNs", From e307c5de9fdb57809b30427237e0d24b63eb4026 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 27 Apr 2022 17:20:26 +0200 Subject: [PATCH 11/75] [CST-5270] add string type to attribute declaration --- src/app/shared/alert/alert.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/alert/alert.component.ts b/src/app/shared/alert/alert.component.ts index 93535d2057..0fcce39d38 100644 --- a/src/app/shared/alert/alert.component.ts +++ b/src/app/shared/alert/alert.component.ts @@ -33,7 +33,7 @@ export class AlertComponent { /** * The alert type */ - @Input() type: AlertType; + @Input() type: AlertType|string; /** * An event fired when alert is dismissed. From a754a20ec6d7ca96a4b93421cf333dbdcb002ffa Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Thu, 28 Apr 2022 18:12:15 +0200 Subject: [PATCH 12/75] [CST-5270] Fixing ISSN removal and unit testing --- .../objects/submission-objects.effects.ts | 67 ++++++++------- .../section-sherpa-policies.component.html | 81 ++++++++++--------- .../section-sherpa-policies.component.spec.ts | 9 ++- .../section-sherpa-policies.component.ts | 24 ++++-- 4 files changed, 103 insertions(+), 78 deletions(-) diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index e79670306f..0c5f3fb61b 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; -import { isEqual, union } from 'lodash'; +import { isEqual, isUndefined, union } from 'lodash'; import { from as observableFrom, Observable, of as observableOf } from 'rxjs'; import { catchError, filter, map, mergeMap, switchMap, take, tap, withLatestFrom } from 'rxjs/operators'; @@ -43,7 +43,7 @@ import { UpdateSectionDataAction, UpdateSectionDataSuccessAction } from './submission-objects.actions'; -import { SubmissionObjectEntry} from './submission-objects.reducer'; +import { SubmissionObjectEntry } from './submission-objects.reducer'; import { Item } from '../../core/shared/item.model'; import { RemoteData } from '../../core/data/remote-data'; import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; @@ -60,7 +60,7 @@ export class SubmissionObjectEffects { /** * Dispatch a [InitSectionAction] for every submission sections and dispatch a [CompleteInitSubmissionFormAction] */ - loadForm$ = createEffect(() => this.actions$.pipe( + loadForm$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.INIT_SUBMISSION_FORM), map((action: InitSubmissionFormAction) => { const definition = action.payload.submissionDefinition; @@ -104,7 +104,7 @@ export class SubmissionObjectEffects { /** * Dispatch a [InitSubmissionFormAction] */ - resetForm$ = createEffect(() => this.actions$.pipe( + resetForm$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.RESET_SUBMISSION_FORM), map((action: ResetSubmissionFormAction) => new InitSubmissionFormAction( @@ -120,35 +120,35 @@ export class SubmissionObjectEffects { /** * Dispatch a [SaveSubmissionFormSuccessAction] or a [SaveSubmissionFormErrorAction] on error */ - saveSubmission$ = createEffect(() => this.actions$.pipe( + saveSubmission$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM), switchMap((action: SaveSubmissionFormAction) => { return this.operationsService.jsonPatchByResourceType( this.submissionService.getSubmissionObjectLinkName(), action.payload.submissionId, 'sections').pipe( - map((response: SubmissionObject[]) => new SaveSubmissionFormSuccessAction(action.payload.submissionId, response, action.payload.isManual, action.payload.isManual)), - catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); + map((response: SubmissionObject[]) => new SaveSubmissionFormSuccessAction(action.payload.submissionId, response, action.payload.isManual, action.payload.isManual)), + catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); }))); /** * Dispatch a [SaveForLaterSubmissionFormSuccessAction] or a [SaveSubmissionFormErrorAction] on error */ - saveForLaterSubmission$ = createEffect(() => this.actions$.pipe( + saveForLaterSubmission$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM), switchMap((action: SaveForLaterSubmissionFormAction) => { return this.operationsService.jsonPatchByResourceType( this.submissionService.getSubmissionObjectLinkName(), action.payload.submissionId, 'sections').pipe( - map((response: SubmissionObject[]) => new SaveForLaterSubmissionFormSuccessAction(action.payload.submissionId, response)), - catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); + map((response: SubmissionObject[]) => new SaveForLaterSubmissionFormSuccessAction(action.payload.submissionId, response)), + catchError(() => observableOf(new SaveSubmissionFormErrorAction(action.payload.submissionId)))); }))); /** * Call parseSaveResponse and dispatch actions */ - saveSubmissionSuccess$ = createEffect(() => this.actions$.pipe( + saveSubmissionSuccess$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS), withLatestFrom(this.store$), map(([action, currentState]: [SaveSubmissionFormSuccessAction, any]) => { @@ -162,7 +162,7 @@ export class SubmissionObjectEffects { * Call parseSaveResponse and dispatch actions. * Notification system is forced to be disabled. */ - saveSubmissionSectionSuccess$ = createEffect(() => this.actions$.pipe( + saveSubmissionSectionSuccess$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS), withLatestFrom(this.store$), map(([action, currentState]: [SaveSubmissionSectionFormSuccessAction, any]) => { @@ -174,7 +174,7 @@ export class SubmissionObjectEffects { /** * Dispatch a [SaveSubmissionSectionFormSuccessAction] or a [SaveSubmissionSectionFormErrorAction] on error */ - saveSection$ = createEffect(() => this.actions$.pipe( + saveSection$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM), switchMap((action: SaveSubmissionSectionFormAction) => { return this.operationsService.jsonPatchByResourceID( @@ -182,14 +182,14 @@ export class SubmissionObjectEffects { action.payload.submissionId, 'sections', action.payload.sectionId).pipe( - map((response: SubmissionObject[]) => new SaveSubmissionSectionFormSuccessAction(action.payload.submissionId, response)), - catchError(() => observableOf(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId)))); + map((response: SubmissionObject[]) => new SaveSubmissionSectionFormSuccessAction(action.payload.submissionId, response)), + catchError(() => observableOf(new SaveSubmissionSectionFormErrorAction(action.payload.submissionId)))); }))); /** * Show a notification on error */ - saveError$ = createEffect(() => this.actions$.pipe( + saveError$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_ERROR, SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_ERROR), withLatestFrom(this.store$), tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.save_error_notice')))), { dispatch: false }); @@ -197,7 +197,7 @@ export class SubmissionObjectEffects { /** * Call parseSaveResponse and dispatch actions or dispatch [SaveSubmissionFormErrorAction] on error */ - saveAndDeposit$ = createEffect(() => this.actions$.pipe( + saveAndDeposit$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_AND_DEPOSIT_SUBMISSION), withLatestFrom(this.submissionService.hasUnsavedModification()), switchMap(([action, hasUnsavedModification]: [SaveAndDepositSubmissionAction, boolean]) => { @@ -233,7 +233,7 @@ export class SubmissionObjectEffects { /** * Dispatch a [DepositSubmissionSuccessAction] or a [DepositSubmissionErrorAction] on error */ - depositSubmission$ = createEffect(() => this.actions$.pipe( + depositSubmission$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION), withLatestFrom(this.store$), switchMap(([action, state]: [DepositSubmissionAction, any]) => { @@ -245,7 +245,7 @@ export class SubmissionObjectEffects { /** * Show a notification on success and redirect to MyDSpace page */ - saveForLaterSubmissionSuccess$ = createEffect(() => this.actions$.pipe( + saveForLaterSubmissionSuccess$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM_SUCCESS), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.save_success_notice'))), tap(() => this.submissionService.redirectToMyDSpace())), { dispatch: false }); @@ -253,7 +253,7 @@ export class SubmissionObjectEffects { /** * Show a notification on success and redirect to MyDSpace page */ - depositSubmissionSuccess$ = createEffect(() => this.actions$.pipe( + depositSubmissionSuccess$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_SUCCESS), tap(() => this.notificationsService.success(null, this.translate.get('submission.sections.general.deposit_success_notice'))), tap(() => this.submissionService.redirectToMyDSpace())), { dispatch: false }); @@ -261,14 +261,14 @@ export class SubmissionObjectEffects { /** * Show a notification on error */ - depositSubmissionError$ = createEffect(() => this.actions$.pipe( + depositSubmissionError$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DEPOSIT_SUBMISSION_ERROR), tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.deposit_error_notice')))), { dispatch: false }); /** * Dispatch a [DiscardSubmissionSuccessAction] or a [DiscardSubmissionErrorAction] on error */ - discardSubmission$ = createEffect(() => this.actions$.pipe( + discardSubmission$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION), switchMap((action: DepositSubmissionAction) => { return this.submissionService.discardSubmission(action.payload.submissionId).pipe( @@ -279,7 +279,7 @@ export class SubmissionObjectEffects { /** * Adds all metadata an item to the SubmissionForm sections of the submission */ - addAllMetadataToSectionData = createEffect(() => this.actions$.pipe( + addAllMetadataToSectionData = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.UPDATE_SECTION_DATA), switchMap((action: UpdateSectionDataAction) => { return this.sectionService.getSectionState(action.payload.submissionId, action.payload.sectionId, SectionsType.Upload) @@ -320,18 +320,18 @@ export class SubmissionObjectEffects { /** * Show a notification on error */ - discardSubmissionError$ = createEffect(() => this.actions$.pipe( + discardSubmissionError$ = createEffect(() => this.actions$.pipe( ofType(SubmissionObjectActionTypes.DISCARD_SUBMISSION_ERROR), tap(() => this.notificationsService.error(null, this.translate.get('submission.sections.general.discard_error_notice')))), { dispatch: false }); constructor(private actions$: Actions, - private notificationsService: NotificationsService, - private operationsService: SubmissionJsonPatchOperationsService, - private sectionService: SectionsService, - private store$: Store, - private submissionService: SubmissionService, - private submissionObjectService: SubmissionObjectDataService, - private translate: TranslateService) { + private notificationsService: NotificationsService, + private operationsService: SubmissionJsonPatchOperationsService, + private sectionService: SectionsService, + private store$: Store, + private submissionService: SubmissionService, + private submissionObjectService: SubmissionObjectDataService, + private translate: TranslateService) { } /** @@ -426,6 +426,11 @@ export class SubmissionObjectEffects { mappedActions.push(new UpdateSectionDataAction(submissionId, sectionId, sectionData, filteredErrors, sectionErrors)); } }); + let currentStateId = currentState.selfUrl.split('/')[currentState.selfUrl.split('/').length - 1]; + let currentResponseItem = response.find(item => item.id.toString() === currentStateId); + if (!isUndefined(currentState.sections.sherpaPolicies?.data) && isUndefined(currentResponseItem.sections.sherpaPolicies)) { + mappedActions.push(new UpdateSectionDataAction(submissionId, 'sherpaPolicies', null, [], [])); + } } return mappedActions; } diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index 94553dd4c3..b7b24ffcf0 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -1,57 +1,62 @@ -
+
- - - -
- - -
- {{'submission.sections.sherpa.publication.information' - | translate}} -
- - - -
- - + + +
- - + +
- {{'submission.sections.sherpa.publisher.policy' + {{'submission.sections.sherpa.publication.information' | translate}}
- +
+ + + +
+ + +
+ {{'submission.sections.sherpa.publisher.policy' + | translate}} +
+ + + +
+
+ + +
+ + +
+ {{'submission.sections.sherpa.record.information' + | translate}} +
+ + + +
- - -
- - -
- {{'submission.sections.sherpa.record.information' - | translate}} -
- - - -
-
+ \ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts index fb8f8ba355..0637745939 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts @@ -1,7 +1,8 @@ +import { SharedModule } from './../../../shared/shared.module'; import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; import { SubmissionServiceStub } from '../../../shared/testing/submission-service.stub'; import { SherpaDataResponse } from '../../../shared/mocks/section-sherpa-policies.service.mock'; -import { ComponentFixture, inject, TestBed } from '@angular/core/testing'; +import { ComponentFixture, inject, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { SectionsService } from '../sections.service'; import { SectionsServiceStub } from '../../../shared/testing/sections-service.stub'; @@ -58,7 +59,8 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { useClass: TranslateLoaderMock } }), - NgbAccordionModule + NgbAccordionModule, + SharedModule ], declarations: [SubmissionSectionSherpaPoliciesComponent], providers: [ @@ -77,7 +79,7 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { fixture = TestBed.createComponent(SubmissionSectionSherpaPoliciesComponent); component = fixture.componentInstance; de = fixture.debugElement; - sectionsServiceStub.getSectionData.and.returnValue(observableOf(SherpaDataResponse)) + sectionsServiceStub.getSectionData.and.returnValue(observableOf(SherpaDataResponse)); fixture.detectChanges(); })); @@ -112,7 +114,6 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { it('when refresh button click operationsBuilder.remove should have been called', () => { de.query(By.css('.refresh-container > button')).nativeElement.click(); - fixture.detectChanges(); expect(operationsBuilder.remove).toHaveBeenCalled(); }); diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts index d287a182ba..a56eb5f663 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts @@ -1,6 +1,6 @@ import { Component, Inject, QueryList, ViewChildren } from '@angular/core'; -import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; +import { BehaviorSubject, interval, Observable, of, Subscription } from 'rxjs'; import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; @@ -14,6 +14,7 @@ import { SectionsService } from '../sections.service'; import { SectionModelComponent } from '../models/section.model'; import { SubmissionService } from '../../submission.service'; import { hasValue, isEmpty } from '../../../shared/empty.util'; +import { debounce, debounceTime, timeInterval } from 'rxjs/operators'; /** * This component represents a section for managing item's access conditions. @@ -77,9 +78,11 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon * Expand all primary accordions */ ngAfterViewInit() { - this.acc.forEach(accordion => { - accordion.expandAll(); - }); + if (this.acc) { + this.acc.forEach(accordion => { + accordion.expandAll(); + }); + } } @@ -93,9 +96,20 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon this.sectionService.getSectionData(this.submissionId, this.sectionData.id, this.sectionData.sectionType) .subscribe((sherpaPolicies: WorkspaceitemSectionSherpaPoliciesObject) => { this.sherpaPoliciesData$.next(sherpaPolicies); - console.log(this.sherpaPoliciesData$.value) }) ); + + this.subs.push( + this.sherpaPoliciesData$.pipe( + debounceTime(500) + ).subscribe((sherpaPolicies: WorkspaceitemSectionSherpaPoliciesObject) => { + if (this.acc) { + this.acc.forEach(accordion => { + accordion.expandAll(); + }); + } + }) + ); } /** From b023a5a03c428faffd64eff771811f59bc94fd25 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 28 Apr 2022 18:58:35 +0200 Subject: [PATCH 13/75] [CST-5270] Fix submission-objects.effects sherpa romeo customization --- .../objects/submission-objects.effects.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app/submission/objects/submission-objects.effects.ts b/src/app/submission/objects/submission-objects.effects.ts index 0c5f3fb61b..4a7907cab1 100644 --- a/src/app/submission/objects/submission-objects.effects.ts +++ b/src/app/submission/objects/submission-objects.effects.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { TranslateService } from '@ngx-translate/core'; -import { isEqual, isUndefined, union } from 'lodash'; +import { findKey, isEqual, union } from 'lodash'; import { from as observableFrom, Observable, of as observableOf } from 'rxjs'; import { catchError, filter, map, mergeMap, switchMap, take, tap, withLatestFrom } from 'rxjs/operators'; @@ -425,12 +425,15 @@ export class SubmissionObjectEffects { const filteredErrors = filterErrors(sectionForm, sectionErrors, currentState.sections[sectionId].sectionType, showErrors); mappedActions.push(new UpdateSectionDataAction(submissionId, sectionId, sectionData, filteredErrors, sectionErrors)); } + + // Sherpa Policies section needs to be updated when the rest response section is empty + const sherpaPoliciesSectionId = findKey(currentState.sections, (section) => section.sectionType === SectionsType.SherpaPolicies); + if (isNotUndefined(sherpaPoliciesSectionId) && isNotEmpty(currentState.sections[sherpaPoliciesSectionId]?.data) + && isEmpty(sections[sherpaPoliciesSectionId])) { + mappedActions.push(new UpdateSectionDataAction(submissionId, sherpaPoliciesSectionId, null, [], [])); + } }); - let currentStateId = currentState.selfUrl.split('/')[currentState.selfUrl.split('/').length - 1]; - let currentResponseItem = response.find(item => item.id.toString() === currentStateId); - if (!isUndefined(currentState.sections.sherpaPolicies?.data) && isUndefined(currentResponseItem.sections.sherpaPolicies)) { - mappedActions.push(new UpdateSectionDataAction(submissionId, 'sherpaPolicies', null, [], [])); - } + } return mappedActions; } From d435d8eeb1762acd2d03893858609f42b29a9f9a Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Thu, 28 Apr 2022 19:07:38 +0200 Subject: [PATCH 14/75] [CST-5270] Fixed graphical changes requested --- .../content-accordion.component.html | 4 +-- .../section-sherpa-policies.component.html | 34 +++++++++---------- .../section-sherpa-policies.component.scss | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html index bec999603d..c2d7207c98 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html @@ -6,8 +6,8 @@ translate }}
- - + +
diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index b7b24ffcf0..f74e38bf8f 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -12,15 +12,15 @@ -
- - -
{{'submission.sections.sherpa.publication.information' | translate}} +
+ + +
@@ -29,15 +29,15 @@ -
- - -
{{'submission.sections.sherpa.publisher.policy' | translate}} +
+ + +
@@ -47,12 +47,12 @@
-
- - -
- {{'submission.sections.sherpa.record.information' + {{'submission.sections.sherpa.record.information' | translate}} +
+ + +
diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss index 5a8e0e3c34..adaa627d56 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss @@ -12,7 +12,7 @@ button { text-align: left; padding: 0px; - width: auto; + // width: auto; font-weight: bold; .fas { From 8498504b939c5e184800fa030ff1e97d88b82a46 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 29 Apr 2022 16:14:47 +0200 Subject: [PATCH 15/75] [CST-5270] Add target blank for links --- .../metadata-information.component.html | 4 ++-- .../publication-information.component.html | 24 +++++++------------ .../publisher-policy.component.html | 4 ++-- 3 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.html b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.html index bfbfe5f2fd..683821d62c 100644 --- a/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.html +++ b/src/app/submission/sections/sherpa-policies/metadata-information/metadata-information.component.html @@ -32,8 +32,8 @@
-
\ No newline at end of file +
diff --git a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.html b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.html index 2e6da459ae..079535b2fb 100644 --- a/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.html +++ b/src/app/submission/sections/sherpa-policies/publication-information/publication-information.component.html @@ -1,8 +1,7 @@
-

{{'submission.sections.sherpa.publication.information.title' - | translate}}

+

{{'submission.sections.sherpa.publication.information.title' | translate}}

{{title}} @@ -11,8 +10,7 @@

-

{{'submission.sections.sherpa.publication.information.issns' - | translate}}

+

{{'submission.sections.sherpa.publication.information.issns' | translate}}

{{issn}} @@ -21,12 +19,11 @@

-

{{'submission.sections.sherpa.publication.information.url' - | translate}}

+

{{'submission.sections.sherpa.publication.information.url' | translate}}

- + {{journal.url}}

@@ -34,12 +31,11 @@
-

{{'submission.sections.sherpa.publication.information.publishers' - | translate}}

+

{{'submission.sections.sherpa.publication.information.publishers' | translate}}

- + {{publisher.name}}

@@ -47,8 +43,7 @@
-

{{'submission.sections.sherpa.publication.information.romeoPub' - | translate}}

+

{{'submission.sections.sherpa.publication.information.romeoPub' | translate}}

@@ -58,8 +53,7 @@

-

{{'submission.sections.sherpa.publication.information.zetoPub' - | translate}}

+

{{'submission.sections.sherpa.publication.information.zetoPub' | translate}}

@@ -67,4 +61,4 @@

-
\ No newline at end of file +
diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html index 4a03bae735..ada6613aa2 100644 --- a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.html @@ -15,9 +15,9 @@

-
\ No newline at end of file +
From 32a91f64d9d8db83390589628c76882b212c968b Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 3 May 2022 12:06:48 +0200 Subject: [PATCH 16/75] [CST-5535] Refactoring health page --- package.json | 1 - src/app/app-routing-paths.ts | 2 + src/app/app-routing.module.ts | 19 ++- .../health-info-component.component.html | 28 +++ .../health-info-component.component.scss | 0 .../health-info-component.component.spec.ts | 72 ++++++++ .../health-info-component.component.ts | 35 ++++ .../health-info/health-info.component.html | 7 + .../health-info/health-info.component.scss | 0 .../health-info/health-info.component.spec.ts | 37 ++++ .../health-info/health-info.component.ts | 14 ++ .../health-page/health-page.component.html | 21 +++ .../health-page/health-page.component.scss | 0 .../health-page/health-page.component.spec.ts | 72 ++++++++ src/app/health-page/health-page.component.ts | 41 +++++ src/app/health-page/health-page.module.ts | 35 ++++ ...odule.ts => health-page.routing.module.ts} | 19 +-- .../health-component.component.html | 27 +++ .../health-component.component.scss | 0 .../health-component.component.spec.ts | 77 +++++++++ .../health-component.component.ts | 27 +++ .../health-panel/health-panel.component.html | 21 +++ .../health-panel/health-panel.component.scss | 0 .../health-panel.component.spec.ts | 58 +++++++ .../health-panel/health-panel.component.ts | 21 +++ .../health-status.component.html | 6 + .../health-status.component.scss | 0 .../health-status.component.spec.ts | 48 ++++++ .../health-status/health-status.component.ts | 20 +++ src/app/health-page/health.module.ts | 23 --- .../health-page/health/health.component.html | 39 ----- .../health-page/health/health.component.scss | 8 - .../health/health.component.spec.ts | 160 ------------------ .../health-page/health/health.component.ts | 100 ----------- .../models/health-component.model.ts | 48 ++++++ src/app/shared/mocks/health-endpoint.mocks.ts | 140 +++++++++++++++ 36 files changed, 878 insertions(+), 348 deletions(-) create mode 100644 src/app/health-page/health-info/health-info-component/health-info-component.component.html create mode 100644 src/app/health-page/health-info/health-info-component/health-info-component.component.scss create mode 100644 src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts create mode 100644 src/app/health-page/health-info/health-info-component/health-info-component.component.ts create mode 100644 src/app/health-page/health-info/health-info.component.html create mode 100644 src/app/health-page/health-info/health-info.component.scss create mode 100644 src/app/health-page/health-info/health-info.component.spec.ts create mode 100644 src/app/health-page/health-info/health-info.component.ts create mode 100644 src/app/health-page/health-page.component.html create mode 100644 src/app/health-page/health-page.component.scss create mode 100644 src/app/health-page/health-page.component.spec.ts create mode 100644 src/app/health-page/health-page.component.ts create mode 100644 src/app/health-page/health-page.module.ts rename src/app/health-page/{health.routing.module.ts => health-page.routing.module.ts} (57%) create mode 100644 src/app/health-page/health-panel/health-component/health-component.component.html create mode 100644 src/app/health-page/health-panel/health-component/health-component.component.scss create mode 100644 src/app/health-page/health-panel/health-component/health-component.component.spec.ts create mode 100644 src/app/health-page/health-panel/health-component/health-component.component.ts create mode 100644 src/app/health-page/health-panel/health-panel.component.html create mode 100644 src/app/health-page/health-panel/health-panel.component.scss create mode 100644 src/app/health-page/health-panel/health-panel.component.spec.ts create mode 100644 src/app/health-page/health-panel/health-panel.component.ts create mode 100644 src/app/health-page/health-panel/health-status/health-status.component.html create mode 100644 src/app/health-page/health-panel/health-status/health-status.component.scss create mode 100644 src/app/health-page/health-panel/health-status/health-status.component.spec.ts create mode 100644 src/app/health-page/health-panel/health-status/health-status.component.ts delete mode 100644 src/app/health-page/health.module.ts delete mode 100644 src/app/health-page/health/health.component.html delete mode 100644 src/app/health-page/health/health.component.scss delete mode 100644 src/app/health-page/health/health.component.spec.ts delete mode 100644 src/app/health-page/health/health.component.ts create mode 100644 src/app/health-page/models/health-component.model.ts create mode 100644 src/app/shared/mocks/health-endpoint.mocks.ts diff --git a/package.json b/package.json index d6c234ef9e..75e22b40f3 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "@angular/core": "~13.2.6", "@angular/forms": "~13.2.6", "@angular/localize": "13.2.6", - "@angular/material": "13.3.5", "@angular/platform-browser": "~13.2.6", "@angular/platform-browser-dynamic": "~13.2.6", "@angular/platform-server": "~13.2.6", diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index 57767b6f3e..929cdbeaa2 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -116,3 +116,5 @@ export const REQUEST_COPY_MODULE_PATH = 'request-a-copy'; export function getRequestCopyModulePath() { return `/${REQUEST_COPY_MODULE_PATH}`; } + +export const HEALTH_PAGE_PATH = 'health'; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 80310774c8..243fe05f59 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -3,13 +3,16 @@ import { RouterModule } from '@angular/router'; import { AuthBlockingGuard } from './core/auth/auth-blocking.guard'; import { AuthenticatedGuard } from './core/auth/authenticated.guard'; -import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { + SiteAdministratorGuard +} from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { ACCESS_CONTROL_MODULE_PATH, ADMIN_MODULE_PATH, BITSTREAM_MODULE_PATH, FORBIDDEN_PATH, FORGOT_PASSWORD_PATH, + HEALTH_PAGE_PATH, INFO_MODULE_PATH, INTERNAL_SERVER_ERROR, LEGACY_BITSTREAM_MODULE_PATH, @@ -27,8 +30,12 @@ import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end- import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard'; import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component'; import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component'; -import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; -import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component'; +import { + GroupAdministratorGuard +} from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; +import { + ThemedPageInternalServerErrorComponent +} from './page-internal-server-error/themed-page-internal-server-error.component'; import { ServerCheckGuard } from './core/server-check/server-check.guard'; @NgModule({ @@ -209,9 +216,9 @@ import { ServerCheckGuard } from './core/server-check/server-check.guard'; .then((m) => m.StatisticsPageRoutingModule) }, { - path: 'health', - loadChildren: () => import('./health-page/health.module') - .then((m) => m.HealthModule) + path: HEALTH_PAGE_PATH, + loadChildren: () => import('./health-page/health-page.module') + .then((m) => m.HealthPageModule) }, { path: ACCESS_CONTROL_MODULE_PATH, diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.html b/src/app/health-page/health-info/health-info-component/health-info-component.component.html new file mode 100644 index 0000000000..55c1b3372f --- /dev/null +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.html @@ -0,0 +1,28 @@ + +
+
+ +
+ + +
+
+
+
+
+ +
+
+
+
+
+ +

{{ healthInfoComponentName | titlecase }} : {{healthInfoComponent}}

+
diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.scss b/src/app/health-page/health-info/health-info-component/health-info-component.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts b/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts new file mode 100644 index 0000000000..2297007cd5 --- /dev/null +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts @@ -0,0 +1,72 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommonModule } from '@angular/common'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'; + +import { HealthInfoComponentComponent } from './health-info-component.component'; +import { HealthInfoComponentOne, HealthInfoComponentTwo } from '../../../shared/mocks/health-endpoint.mocks'; +import { ObjNgFor } from '../../../shared/utils/object-ngfor.pipe'; + +describe('HealthInfoComponentComponent', () => { + let component: HealthInfoComponentComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + NgbCollapseModule, + NoopAnimationsModule + ], + declarations: [ + HealthInfoComponentComponent, + ObjNgFor + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthInfoComponentComponent); + component = fixture.componentInstance; + }); + + describe('when has nested components', () => { + beforeEach(() => { + component.healthInfoComponentName = 'App'; + component.healthInfoComponent = HealthInfoComponentOne; + component.isCollapsed = false; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should display property', () => { + const components = fixture.debugElement.queryAll(By.css('[data-test="component"]')); + expect(components.length).toBe(4); + }); + + }); + + describe('when has plain properties', () => { + beforeEach(() => { + component.healthInfoComponentName = 'Java'; + component.healthInfoComponent = HealthInfoComponentTwo; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should display property', () => { + const property = fixture.debugElement.queryAll(By.css('[data-test="property"]')); + expect(property.length).toBe(1); + }); + + }); +}); diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.ts b/src/app/health-page/health-info/health-info-component/health-info-component.component.ts new file mode 100644 index 0000000000..b6c31214c8 --- /dev/null +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.ts @@ -0,0 +1,35 @@ +import { Component, Input } from '@angular/core'; + +import { HealthInfoComponent } from '../../models/health-component.model'; + +@Component({ + selector: 'ds-health-info-component', + templateUrl: './health-info-component.component.html', + styleUrls: ['./health-info-component.component.scss'] +}) +export class HealthInfoComponentComponent { + + /** + * The HealthInfoComponent object to display + */ + @Input() healthInfoComponent: HealthInfoComponent|string; + + /** + * The HealthInfoComponent object name + */ + @Input() healthInfoComponentName: string; + + /** + * A boolean representing if div should start collapsed + */ + @Input() isNested = false; + + /** + * A boolean representing if div should start collapsed + */ + public isCollapsed = true; + + isPlainProperty(entry: HealthInfoComponent | string): boolean { + return typeof entry === 'string'; + } +} diff --git a/src/app/health-page/health-info/health-info.component.html b/src/app/health-page/health-info/health-info.component.html new file mode 100644 index 0000000000..e4d29adf54 --- /dev/null +++ b/src/app/health-page/health-info/health-info.component.html @@ -0,0 +1,7 @@ + +
+ +
+
diff --git a/src/app/health-page/health-info/health-info.component.scss b/src/app/health-page/health-info/health-info.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/health-page/health-info/health-info.component.spec.ts b/src/app/health-page/health-info/health-info.component.spec.ts new file mode 100644 index 0000000000..3af1d71db3 --- /dev/null +++ b/src/app/health-page/health-info/health-info.component.spec.ts @@ -0,0 +1,37 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { HealthInfoComponent } from './health-info.component'; +import { HealthInfoResponseObj } from '../../shared/mocks/health-endpoint.mocks'; +import { ObjNgFor } from '../../shared/utils/object-ngfor.pipe'; +import { By } from '@angular/platform-browser'; + +describe('HealthInfoComponent', () => { + let component: HealthInfoComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ + HealthInfoComponent, + ObjNgFor + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthInfoComponent); + component = fixture.componentInstance; + component.healthInfoResponse = HealthInfoResponseObj; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create info component properly', () => { + const components = fixture.debugElement.queryAll(By.css('[data-test="info-component"]')); + expect(components.length).toBe(3); + }); +}); diff --git a/src/app/health-page/health-info/health-info.component.ts b/src/app/health-page/health-info/health-info.component.ts new file mode 100644 index 0000000000..a5fb0b282b --- /dev/null +++ b/src/app/health-page/health-info/health-info.component.ts @@ -0,0 +1,14 @@ +import { Component, Input } from '@angular/core'; + +import { HealthInfoResponse } from '../models/health-component.model'; + +@Component({ + selector: 'ds-health-info', + templateUrl: './health-info.component.html', + styleUrls: ['./health-info.component.scss'] +}) +export class HealthInfoComponent { + + @Input() healthInfoResponse: HealthInfoResponse; + +} diff --git a/src/app/health-page/health-page.component.html b/src/app/health-page/health-page.component.html new file mode 100644 index 0000000000..6ec9abddcb --- /dev/null +++ b/src/app/health-page/health-page.component.html @@ -0,0 +1,21 @@ + diff --git a/src/app/health-page/health-page.component.scss b/src/app/health-page/health-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/health-page/health-page.component.spec.ts b/src/app/health-page/health-page.component.spec.ts new file mode 100644 index 0000000000..205af8036a --- /dev/null +++ b/src/app/health-page/health-page.component.spec.ts @@ -0,0 +1,72 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommonModule } from '@angular/common'; +import { By } from '@angular/platform-browser'; + +import { of } from 'rxjs'; +import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; + +import { HealthPageComponent } from './health-page.component'; +import { HealthDataService } from './health-data.service'; +import { HealthInfoResponseObj, HealthResponseObj } from '../shared/mocks/health-endpoint.mocks'; +import { RawRestResponse } from '../core/dspace-rest/raw-rest-response.model'; +import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; + +describe('HealthPageComponent', () => { + let component: HealthPageComponent; + let fixture: ComponentFixture; + + const healthService = jasmine.createSpyObj('healthDataService', { + getHealth: jasmine.createSpy('getHealth'), + getInfo: jasmine.createSpy('getInfo'), + }); + + const healthRestResponse$ = of({ + payload: HealthResponseObj, + statusCode: 200, + statusText: 'OK' + } as RawRestResponse); + + const healthInfoRestResponse$ = of({ + payload: HealthInfoResponseObj, + statusCode: 200, + statusText: 'OK' + } as RawRestResponse); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + NgbNavModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) + ], + declarations: [ HealthPageComponent ], + providers: [ + { provide: HealthDataService, useValue: healthService } + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthPageComponent); + component = fixture.componentInstance; + healthService.getHealth.and.returnValue(healthRestResponse$); + healthService.getInfo.and.returnValue(healthInfoRestResponse$); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create nav items properly', () => { + const navItems = fixture.debugElement.queryAll(By.css('li.nav-item')); + expect(navItems.length).toBe(2); + }); +}); diff --git a/src/app/health-page/health-page.component.ts b/src/app/health-page/health-page.component.ts new file mode 100644 index 0000000000..e4f4be7a03 --- /dev/null +++ b/src/app/health-page/health-page.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit } from '@angular/core'; + +import { BehaviorSubject } from 'rxjs'; +import { take } from 'rxjs/operators'; + +import { HealthDataService } from './health-data.service'; +import { HealthInfoResponse, HealthResponse } from './models/health-component.model'; + +@Component({ + selector: 'ds-health-page', + templateUrl: './health-page.component.html', + styleUrls: ['./health-page.component.scss'] +}) +export class HealthPageComponent implements OnInit { + + /** + * Health info endpoint response + */ + healthInfoResponse: BehaviorSubject = new BehaviorSubject(null); + + /** + * Health endpoint response + */ + healthResponse: BehaviorSubject = new BehaviorSubject(null); + + constructor(private healthDataService: HealthDataService) { + } + + /** + * Retrieve responses from rest + */ + ngOnInit(): void { + this.healthDataService.getHealth().pipe(take(1)).subscribe((data: any) => { + this.healthResponse.next(data.payload); + }); + + this.healthDataService.getInfo().pipe(take(1)).subscribe((data) => { + this.healthInfoResponse.next(data.payload); + }); + } +} diff --git a/src/app/health-page/health-page.module.ts b/src/app/health-page/health-page.module.ts new file mode 100644 index 0000000000..02a6a91a5f --- /dev/null +++ b/src/app/health-page/health-page.module.ts @@ -0,0 +1,35 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; + +import { HealthPageRoutingModule } from './health-page.routing.module'; +import { HealthPanelComponent } from './health-panel/health-panel.component'; +import { HealthStatusComponent } from './health-panel/health-status/health-status.component'; +import { SharedModule } from '../shared/shared.module'; +import { HealthPageComponent } from './health-page.component'; +import { HealthComponentComponent } from './health-panel/health-component/health-component.component'; +import { HealthInfoComponent } from './health-info/health-info.component'; +import { HealthInfoComponentComponent } from './health-info/health-info-component/health-info-component.component'; + + +@NgModule({ + imports: [ + CommonModule, + HealthPageRoutingModule, + NgbModule, + SharedModule, + TranslateModule + ], + declarations: [ + HealthPageComponent, + HealthPanelComponent, + HealthStatusComponent, + HealthComponentComponent, + HealthInfoComponent, + HealthInfoComponentComponent, + ] +}) +export class HealthPageModule { +} diff --git a/src/app/health-page/health.routing.module.ts b/src/app/health-page/health-page.routing.module.ts similarity index 57% rename from src/app/health-page/health.routing.module.ts rename to src/app/health-page/health-page.routing.module.ts index a8d94d9d1f..37c8b626eb 100644 --- a/src/app/health-page/health.routing.module.ts +++ b/src/app/health-page/health-page.routing.module.ts @@ -1,8 +1,11 @@ import { RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; -import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; -import { HealthComponent } from './health/health.component'; + import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { HealthPageComponent } from './health-page.component'; +import { + SiteAdministratorGuard +} from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; @NgModule({ imports: [ @@ -11,15 +14,9 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso path: '', resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { breadcrumbKey: 'health' }, - canActivate: [AuthenticatedGuard], - children: [ - { - path: '', - component: HealthComponent, - }, - ] - }, - + canActivate: [SiteAdministratorGuard], + component: HealthPageComponent + } ]) ] }) diff --git a/src/app/health-page/health-panel/health-component/health-component.component.html b/src/app/health-page/health-panel/health-component/health-component.component.html new file mode 100644 index 0000000000..8171917767 --- /dev/null +++ b/src/app/health-page/health-panel/health-component/health-component.component.html @@ -0,0 +1,27 @@ + +
+
+ +
+ + +
+
+
+
+
+ +
+
+
+
+
+ +
+ {{ item.key | titlecase }} : {{item.value}} +
+
diff --git a/src/app/health-page/health-panel/health-component/health-component.component.scss b/src/app/health-page/health-panel/health-component/health-component.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/health-page/health-panel/health-component/health-component.component.spec.ts b/src/app/health-page/health-panel/health-component/health-component.component.spec.ts new file mode 100644 index 0000000000..149d504c23 --- /dev/null +++ b/src/app/health-page/health-panel/health-component/health-component.component.spec.ts @@ -0,0 +1,77 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CommonModule } from '@angular/common'; +import { By } from '@angular/platform-browser'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'; + +import { HealthComponentComponent } from './health-component.component'; +import { HealthComponentOne, HealthComponentTwo } from '../../../shared/mocks/health-endpoint.mocks'; +import { ObjNgFor } from '../../../shared/utils/object-ngfor.pipe'; + +describe('HealthComponentComponent', () => { + let component: HealthComponentComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + CommonModule, + NgbCollapseModule, + NoopAnimationsModule + ], + declarations: [ + HealthComponentComponent, + ObjNgFor + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthComponentComponent); + component = fixture.componentInstance; + }); + + describe('when has nested components', () => { + beforeEach(() => { + component.healthComponentName = 'db'; + component.healthComponent = HealthComponentOne; + component.isCollapsed = false; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create collapsible divs properly', () => { + const collapseDivs = fixture.debugElement.queryAll(By.css('[data-test="collapse"]')); + expect(collapseDivs.length).toBe(2); + const detailsDivs = fixture.debugElement.queryAll(By.css('[data-test="details"]')); + expect(detailsDivs.length).toBe(6); + }); + }); + + describe('when has details', () => { + beforeEach(() => { + component.healthComponentName = 'geoIp'; + component.healthComponent = HealthComponentTwo; + + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create detail divs properly', () => { + const detailsDivs = fixture.debugElement.queryAll(By.css('[data-test="details"]')); + expect(detailsDivs.length).toBe(1); + const collapseDivs = fixture.debugElement.queryAll(By.css('[data-test="collapse"]')); + expect(collapseDivs.length).toBe(0); + }); + }); +}); diff --git a/src/app/health-page/health-panel/health-component/health-component.component.ts b/src/app/health-page/health-panel/health-component/health-component.component.ts new file mode 100644 index 0000000000..5ad40c9469 --- /dev/null +++ b/src/app/health-page/health-panel/health-component/health-component.component.ts @@ -0,0 +1,27 @@ +import { Component, Input } from '@angular/core'; + +import { HealthComponent } from '../../models/health-component.model'; + +@Component({ + selector: 'ds-health-component', + templateUrl: './health-component.component.html', + styleUrls: ['./health-component.component.scss'] +}) +export class HealthComponentComponent { + + /** + * The HealthComponent object to display + */ + @Input() healthComponent: HealthComponent; + + /** + * The HealthComponent object name + */ + @Input() healthComponentName: string; + + /** + * A boolean representing if div should start collapsed + */ + public isCollapsed = true; + +} diff --git a/src/app/health-page/health-panel/health-panel.component.html b/src/app/health-page/health-panel/health-panel.component.html new file mode 100644 index 0000000000..d582fb77f3 --- /dev/null +++ b/src/app/health-page/health-panel/health-panel.component.html @@ -0,0 +1,21 @@ +

{{'health-page.status' | translate}} :

+
+
+ +
+ + + +
+
+
+
+
+ +
+
+
+
diff --git a/src/app/health-page/health-panel/health-panel.component.scss b/src/app/health-page/health-panel/health-panel.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/health-page/health-panel/health-panel.component.spec.ts b/src/app/health-page/health-panel/health-panel.component.spec.ts new file mode 100644 index 0000000000..da392f7ba8 --- /dev/null +++ b/src/app/health-page/health-panel/health-panel.component.spec.ts @@ -0,0 +1,58 @@ +import { CommonModule } from '@angular/common'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; + +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { NgbCollapseModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; + +import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; +import { HealthPanelComponent } from './health-panel.component'; +import { HealthResponseObj } from '../../shared/mocks/health-endpoint.mocks'; +import { ObjNgFor } from '../../shared/utils/object-ngfor.pipe'; + +describe('HealthComponent', () => { + let component: HealthPanelComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + NgbNavModule, + NgbCollapseModule, + CommonModule, + BrowserAnimationsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [ + HealthPanelComponent, + ObjNgFor + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthPanelComponent); + component = fixture.componentInstance; + component.healthResponse = HealthResponseObj; + component.isCollapsed = false; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should render a card for each component', () => { + const components = fixture.debugElement.queryAll(By.css('[data-test="component"]')); + expect(components.length).toBe(5); + }); + +}); diff --git a/src/app/health-page/health-panel/health-panel.component.ts b/src/app/health-page/health-panel/health-panel.component.ts new file mode 100644 index 0000000000..549544c370 --- /dev/null +++ b/src/app/health-page/health-panel/health-panel.component.ts @@ -0,0 +1,21 @@ +import { Component, Input } from '@angular/core'; +import { HealthResponse } from '../models/health-component.model'; + +@Component({ + selector: 'ds-health-panel', + templateUrl: './health-panel.component.html', + styleUrls: ['./health-panel.component.scss'] +}) +export class HealthPanelComponent { + + /** + * Health endpoint response + */ + @Input() healthResponse: HealthResponse; + + /** + * A boolean representing if div should start collapsed + */ + public isCollapsed = true; + +} diff --git a/src/app/health-page/health-panel/health-status/health-status.component.html b/src/app/health-page/health-panel/health-status/health-status.component.html new file mode 100644 index 0000000000..fdd726cddf --- /dev/null +++ b/src/app/health-page/health-panel/health-status/health-status.component.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/app/health-page/health-panel/health-status/health-status.component.scss b/src/app/health-page/health-panel/health-status/health-status.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/health-page/health-panel/health-status/health-status.component.spec.ts b/src/app/health-page/health-panel/health-status/health-status.component.spec.ts new file mode 100644 index 0000000000..13df9c23e3 --- /dev/null +++ b/src/app/health-page/health-panel/health-status/health-status.component.spec.ts @@ -0,0 +1,48 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { HealthStatusComponent } from './health-status.component'; +import { HealthStatus } from '../../models/health-component.model'; + +describe('HealthStatusComponent', () => { + let component: HealthStatusComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ HealthStatusComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthStatusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create success icon', () => { + component.status = HealthStatus.UP; + fixture.detectChanges(); + const icon = fixture.debugElement.query(By.css('i.text-success')); + expect(icon).toBeTruthy(); + }); + + it('should create warning icon', () => { + component.status = HealthStatus.UP_WITH_ISSUES; + fixture.detectChanges(); + const icon = fixture.debugElement.query(By.css('i.text-warning')); + expect(icon).toBeTruthy(); + }); + + it('should create success icon', () => { + component.status = HealthStatus.DOWN; + fixture.detectChanges(); + const icon = fixture.debugElement.query(By.css('i.text-danger')); + expect(icon).toBeTruthy(); + }); +}); diff --git a/src/app/health-page/health-panel/health-status/health-status.component.ts b/src/app/health-page/health-panel/health-status/health-status.component.ts new file mode 100644 index 0000000000..9285483a97 --- /dev/null +++ b/src/app/health-page/health-panel/health-status/health-status.component.ts @@ -0,0 +1,20 @@ +import { Component, Input } from '@angular/core'; +import { HealthStatus } from '../../models/health-component.model'; + +@Component({ + selector: 'ds-health-status', + templateUrl: './health-status.component.html', + styleUrls: ['./health-status.component.scss'] +}) +export class HealthStatusComponent { + /** + * The current status to show + */ + @Input() status: HealthStatus; + + /** + * He + */ + HealthStatus = HealthStatus; + +} diff --git a/src/app/health-page/health.module.ts b/src/app/health-page/health.module.ts deleted file mode 100644 index 8731b77f77..0000000000 --- a/src/app/health-page/health.module.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NgModule } from '@angular/core'; -import { HealthPageRoutingModule } from './health.routing.module'; -import { HealthComponent } from './health/health.component'; -import { MatExpansionModule } from '@angular/material/expansion'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateModule } from '@ngx-translate/core'; - - -@NgModule({ - imports: [ - CommonModule, - HealthPageRoutingModule, - MatExpansionModule, - NgbModule, - TranslateModule - ], - declarations: [ - HealthComponent - ] - }) - export class HealthModule { - } diff --git a/src/app/health-page/health/health.component.html b/src/app/health-page/health/health.component.html deleted file mode 100644 index 05f77225fb..0000000000 --- a/src/app/health-page/health/health.component.html +++ /dev/null @@ -1,39 +0,0 @@ -
- -
-
- - - - diff --git a/src/app/health-page/health/health.component.scss b/src/app/health-page/health/health.component.scss deleted file mode 100644 index c085111079..0000000000 --- a/src/app/health-page/health/health.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.mat-expansion-panel-header { - padding-left: 0px; -} - -.circle-red { - color:red; - align-items: center; -} \ No newline at end of file diff --git a/src/app/health-page/health/health.component.spec.ts b/src/app/health-page/health/health.component.spec.ts deleted file mode 100644 index 4515aef2cb..0000000000 --- a/src/app/health-page/health/health.component.spec.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatExpansionModule } from '@angular/material/expansion'; -import { By } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { of } from 'rxjs'; -import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; -import { HealthDataService } from '../health-data.service'; -import { HealthPageRoutingModule } from '../health.routing.module'; -import { HealthComponent } from './health.component'; - - function getHealth() { - return of({ - 'payload':{ - 'status':'UP_WITH_ISSUES', - 'components':{ - 'db':{ - 'status':'UP', - 'components':{ - 'dataSource':{ - 'status':'UP', - 'details':{ - 'database':'PostgreSQL', - 'result':1, - 'validationQuery':'SELECT 1' - } - }, - 'dspaceDataSource':{ - 'status':'UP', - 'details':{ - 'database':'PostgreSQL', - 'result':1, - 'validationQuery':'SELECT 1' - } - } - } - }, - 'geoIp':{ - 'status':'UP_WITH_ISSUES', - 'details':{ - 'reason':'The GeoLite Database file is missing (/var/lib/GeoIP/GeoLite2-City.mmdb)! Solr Statistics cannot generate location based reports! Please see the DSpace installation instructions for instructions to install this file.' - } - }, - 'solrOaiCore':{ - 'status':'UP', - 'details':{ - 'status':0, - 'detectedPathType':'particular core' - } - }, - 'solrSearchCore':{ - 'status':'UP', - 'details':{ - 'status':0, - 'detectedPathType':'particular core' - } - }, - 'solrStatisticsCore':{ - 'status':'UP', - 'details':{ - 'status':0, - 'detectedPathType':'particular core' - } - } - } - }, - 'statusCode':200, - 'statusText':'OK' - }); - } - - function getInfo() { - return of({ - 'payload':{ - 'app':{ - 'name':'DSpace at My University', - 'version':'7.3', - 'dir':'/Users/pratikrajkotiya/Documents/Project/FrontEnd/dspace-cris-install', - 'url':'http://localhost:8080/server', - 'db':'jdbc:postgresql://localhost:5432/4science', - 'solr':{ - 'server':'http://localhost:8983/solr', - 'prefix':'' - }, - 'mail':{ - 'server':'smtp.example.com', - 'from-address':'dspace-noreply@myu.edu', - 'feedback-recipient':'dspace-help@myu.edu', - 'mail-admin':'dspace-help@myu.edu', - 'mail-helpdesk':'dspace-help@myu.edu', - 'alert-recipient':'dspace-help@myu.edu' - }, - 'cors':{ - 'allowed-origins':'http://localhost:4000' - }, - 'ui':{ - 'url':'http://localhost:4000' - } - } - }, - 'statusCode':200, - 'statusText':'OK' - }); - } - -function getMockHealthDataService() { - return jasmine.createSpyObj('healthDataService', { - getHealth: getHealth(), - getInfo: getInfo() - }); -} - -describe('HealthComponent', () => { - let component: HealthComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ - NgbNavModule, - CommonModule, - HealthPageRoutingModule, - MatExpansionModule, - BrowserAnimationsModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - }), - ], - declarations: [ HealthComponent ], - providers:[{ provide: HealthDataService, useValue: getMockHealthDataService() }] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(HealthComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should render health tab.', () => { - const healthTab = fixture.debugElement.query(By.css('#health')); - expect(healthTab).toBeTruthy(); - }); - - it('should render info tab.', () => { - const infoTab = fixture.debugElement.query(By.css('#info')); - expect(infoTab).toBeFalsy(); - }); - -}); diff --git a/src/app/health-page/health/health.component.ts b/src/app/health-page/health/health.component.ts deleted file mode 100644 index 89aa6611f6..0000000000 --- a/src/app/health-page/health/health.component.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { HealthDataService } from '../health-data.service'; - - -enum HealthStatus { - UP = 'UP', - UP_WITH_ISSUES = 'UP_WITH_ISSUES', - DOWN = 'DOWN' -} -@Component({ - selector: 'ds-health', - templateUrl: './health.component.html', - styleUrls: ['./health.component.scss'] -}) -export class HealthComponent implements OnInit { - healthArr: string[]; - serverInfoArr: string[]; - healthGlobalStatus: string; - activeId ='Health'; - constructor(private healthDataService: HealthDataService) { } - - ngOnInit(): void { - this.healthDataService.getHealth().subscribe((data) => { - this.healthArr = this.getHealth(data.payload.components); - this.healthGlobalStatus = data.payload.status; - }); - - this.healthDataService.getInfo().subscribe((data) => { - this.serverInfoArr = this.getInfo(data.payload, null, []); - }); - } - - /** - * @param obj represents a info - * @param key represents a nested key of info - * @param arr represents a key value pair or only key - * @returns {{arr}} of key value pair or only key - */ - getInfo(obj, key, arr) { - if (typeof obj === 'object' && key !== null) { - arr.push({style: {'font-weight': 'bold' ,'font-size.px': key === 'app' ? '30' : '20' }, value: key}); - } - if (typeof obj !== 'object') { - arr.push({style: {'font-size.px': '15'}, value: `${key} = ${obj}`}); - return obj; - } - // tslint:disable-next-line: forin - for (const objKey in obj) { - this.getInfo(obj[objKey], objKey, arr); - } - return arr; - } - - /** - * @param subCompObj represent nested sub component - * @param superCompkey represents a key of super component - * @returns linear components array - */ - getHealthSubComponents(subCompObj, superCompkey) { - const subCompArr = []; - // tslint:disable-next-line: forin - for (const key in subCompObj) { - subCompArr.push({ ...subCompObj[key], components: superCompkey + '.' + key }); - } - return subCompArr; - } - - /** - * @param componentsObj represent health data - * @returns linear components array - */ - getHealth(componentsObj) { - let componentsArr = []; - for (const key in componentsObj) { - if (componentsObj[key].hasOwnProperty('components')) { - componentsArr.push({ ...componentsObj[key], components: key }); - // tslint:disable-next-line: no-string-literal - componentsArr = [...componentsArr, ...this.getHealthSubComponents(componentsObj[key]['components'], key)]; - } else { - componentsArr.push({ ...componentsObj[key], components: key }); - } - } - return componentsArr; - } - - /** - * @param status of perticular block - * @returns {{ string }} class respective status - */ - getHealthIconClass(status: string): string { - if (status === HealthStatus.UP) { - return 'fa fa-check-circle text-success ml-2 mt-1'; - } else if (status === HealthStatus.UP_WITH_ISSUES) { - return 'fa fa-exclamation-triangle text-warning ml-2 mt-1'; - } else { - return 'fa fa-times-circle circle-red ml-2 mt-1'; - } - } - -} diff --git a/src/app/health-page/models/health-component.model.ts b/src/app/health-page/models/health-component.model.ts new file mode 100644 index 0000000000..8461d4d967 --- /dev/null +++ b/src/app/health-page/models/health-component.model.ts @@ -0,0 +1,48 @@ +/** + * Interface for Health Status + */ +export enum HealthStatus { + UP = 'UP', + UP_WITH_ISSUES = 'UP_WITH_ISSUES', + DOWN = 'DOWN' +} + +/** + * Interface describing the Health endpoint response + */ +export interface HealthResponse { + status: HealthStatus; + components: { + [name: string]: HealthComponent; + }; +} + +/** + * Interface describing a single component retrieved from the Health endpoint response + */ +export interface HealthComponent { + status: HealthStatus; + details?: { + [name: string]: number|string; + }; + components?: { + [name: string]: HealthComponent; + }; +} + +/** + * Interface describing the Health info endpoint response + */ +export interface HealthInfoResponse { + [name: string]: HealthInfoComponent|string; +} + +/** + * Interface describing a single component retrieved from the Health info endpoint response + */ +export interface HealthInfoComponent { + [property: string]: HealthInfoComponent|string; +} + + + diff --git a/src/app/shared/mocks/health-endpoint.mocks.ts b/src/app/shared/mocks/health-endpoint.mocks.ts new file mode 100644 index 0000000000..9bd3956139 --- /dev/null +++ b/src/app/shared/mocks/health-endpoint.mocks.ts @@ -0,0 +1,140 @@ +import { + HealthComponent, + HealthInfoComponent, + HealthInfoResponse, + HealthResponse, + HealthStatus +} from '../../health-page/models/health-component.model'; + +export const HealthResponseObj: HealthResponse = { + 'status': HealthStatus.UP_WITH_ISSUES, + 'components': { + 'db': { + 'status': HealthStatus.UP, + 'components': { + 'dataSource': { + 'status': HealthStatus.UP, + 'details': { + 'database': 'PostgreSQL', + 'result': 1, + 'validationQuery': 'SELECT 1' + } + }, + 'dspaceDataSource': { + 'status': HealthStatus.UP, + 'details': { + 'database': 'PostgreSQL', + 'result': 1, + 'validationQuery': 'SELECT 1' + } + } + } + }, + 'geoIp': { + 'status': HealthStatus.UP_WITH_ISSUES, + 'details': { + 'reason': 'The GeoLite Database file is missing (/var/lib/GeoIP/GeoLite2-City.mmdb)! Solr Statistics cannot generate location based reports! Please see the DSpace installation instructions for instructions to install this file.' + } + }, + 'solrOaiCore': { + 'status': HealthStatus.UP, + 'details': { + 'status': 0, + 'detectedPathType': 'particular core' + } + }, + 'solrSearchCore': { + 'status': HealthStatus.UP, + 'details': { + 'status': 0, + 'detectedPathType': 'particular core' + } + }, + 'solrStatisticsCore': { + 'status': HealthStatus.UP, + 'details': { + 'status': 0, + 'detectedPathType': 'particular core' + } + } + } +}; + +export const HealthComponentOne: HealthComponent = { + 'status': HealthStatus.UP, + 'components': { + 'dataSource': { + 'status': HealthStatus.UP, + 'details': { + 'database': 'PostgreSQL', + 'result': 1, + 'validationQuery': 'SELECT 1' + } + }, + 'dspaceDataSource': { + 'status': HealthStatus.UP, + 'details': { + 'database': 'PostgreSQL', + 'result': 1, + 'validationQuery': 'SELECT 1' + } + } + } +}; + +export const HealthComponentTwo: HealthComponent = { + 'status': HealthStatus.UP_WITH_ISSUES, + 'details': { + 'reason': 'The GeoLite Database file is missing (/var/lib/GeoIP/GeoLite2-City.mmdb)! Solr Statistics cannot generate location based reports! Please see the DSpace installation instructions for instructions to install this file.' + } +}; + +export const HealthInfoResponseObj: HealthInfoResponse = { + 'app': { + 'name': 'DSpace at My University', + 'dir': '/home/giuseppe/development/java/install/dspace7-review', + 'url': 'http://localhost:8080/server', + 'db': 'jdbc:postgresql://localhost:5432/dspace7', + 'solr': { + 'server': 'http://localhost:8983/solr', + 'prefix': '' + }, + 'mail': { + 'server': 'smtp.example.com', + 'from-address': 'dspace-noreply@myu.edu', + 'feedback-recipient': 'dspace-help@myu.edu', + 'mail-admin': 'dspace-help@myu.edu', + 'mail-helpdesk': 'dspace-help@myu.edu', + 'alert-recipient': 'dspace-help@myu.edu' + }, + 'cors': { + 'allowed-origins': 'http://localhost:4000' + }, + 'ui': { + 'url': 'http://localhost:4000' + } + }, + 'java': { + 'vendor': 'Private Build', + 'version': '11.0.15', + 'runtime': { + 'name': 'OpenJDK Runtime Environment', + 'version': '11.0.15+10-Ubuntu-0ubuntu0.20.04.1' + }, + 'jvm': { + 'name': 'OpenJDK 64-Bit Server VM', + 'vendor': 'Private Build', + 'version': '11.0.15+10-Ubuntu-0ubuntu0.20.04.1' + } + }, + 'version': '7.3-SNAPSHOT' +}; + +export const HealthInfoComponentOne: HealthInfoComponent = { + 'name': 'DSpace at My University', + 'dir': '/home/giuseppe/development/java/install/dspace7-review', + 'url': 'http://localhost:8080/server', + 'db': 'jdbc:postgresql://localhost:5432/dspace7' +}; + +export const HealthInfoComponentTwo = '7.3-SNAPSHOT'; From 3c2f26f6c18bbf379e9b0be64f713a91cada110f Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 3 May 2022 15:30:20 +0200 Subject: [PATCH 17/75] [CST-5535] Add environment configuration for Actuators --- src/config/actuators.config.ts | 11 +++++++++++ src/config/app-config.interface.ts | 2 ++ src/config/default-app-config.ts | 5 +++++ src/environments/environment.test.ts | 4 ++++ 4 files changed, 22 insertions(+) create mode 100644 src/config/actuators.config.ts diff --git a/src/config/actuators.config.ts b/src/config/actuators.config.ts new file mode 100644 index 0000000000..8f59a13c98 --- /dev/null +++ b/src/config/actuators.config.ts @@ -0,0 +1,11 @@ +import { Config } from './config.interface'; + +/** + * Config that determines the spring Actuators options + */ +export class ActuatorsConfig implements Config { + /** + * The endpoint path + */ + public endpointPath: string; +} diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index a41ec05b82..e8bda53373 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -15,6 +15,7 @@ import { UIServerConfig } from './ui-server-config.interface'; import { MediaViewerConfig } from './media-viewer-config.interface'; import { BrowseByConfig } from './browse-by-config.interface'; import { BundleConfig } from './bundle-config.interface'; +import { ActuatorsConfig } from './actuators.config'; interface AppConfig extends Config { ui: UIServerConfig; @@ -34,6 +35,7 @@ interface AppConfig extends Config { themes: ThemeConfig[]; mediaViewer: MediaViewerConfig; bundle: BundleConfig; + actuators: ActuatorsConfig } const APP_CONFIG = new InjectionToken('APP_CONFIG'); diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index a7360bd1d1..27950f5269 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -15,6 +15,7 @@ import { SubmissionConfig } from './submission-config.interface'; import { ThemeConfig } from './theme.model'; import { UIServerConfig } from './ui-server-config.interface'; import { BundleConfig } from './bundle-config.interface'; +import { ActuatorsConfig } from './actuators.config'; export class DefaultAppConfig implements AppConfig { production = false; @@ -48,6 +49,10 @@ export class DefaultAppConfig implements AppConfig { nameSpace: '/', }; + actuators: ActuatorsConfig = { + endpointPath: '/actuator/health' + }; + // Caching settings cache: CacheConfig = { // NOTE: how long should objects be cached for by default diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index bc1622f5f2..d2f2ad7426 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -38,6 +38,10 @@ export const environment: BuildConfig = { baseUrl: 'https://rest.com/api' }, + actuators: { + endpointPath: '/actuator/health' + }, + // Caching settings cache: { // NOTE: how long should objects be cached for by default From 675f3910ccb23a2eb2376d61af067a018db59e72 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 3 May 2022 15:32:02 +0200 Subject: [PATCH 18/75] [CST-5535] Refactoring health check server side request --- package.json | 1 + server.ts | 31 +++++++++++++++++++------------ yarn.lock | 10 +++++++++- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 75e22b40f3..14e2436ccd 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@nicky-lenaers/ngx-scroll-to": "^9.0.0", "angular-idle-preload": "3.0.0", "angulartics2": "^10.0.0", + "axios": "^0.27.2", "bootstrap": "4.3.1", "caniuse-lite": "^1.0.30001165", "cerialize": "0.1.18", diff --git a/server.ts b/server.ts index 7f8bb9bbdb..14ce33ed36 100644 --- a/server.ts +++ b/server.ts @@ -19,6 +19,7 @@ import 'zone.js/node'; import 'reflect-metadata'; import 'rxjs'; +import axios from 'axios'; import * as pem from 'pem'; import * as https from 'https'; import * as morgan from 'morgan'; @@ -37,14 +38,14 @@ import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens'; import { environment } from './src/environments/environment'; import { createProxyMiddleware } from 'http-proxy-middleware'; -import { hasValue, hasNoValue } from './src/app/shared/empty.util'; +import { hasNoValue, hasValue } from './src/app/shared/empty.util'; import { UIServerConfig } from './src/config/ui-server-config.interface'; import { ServerAppModule } from './src/main.server'; import { buildAppConfig } from './src/config/config.server'; -import { AppConfig, APP_CONFIG } from './src/config/app-config.interface'; +import { APP_CONFIG, AppConfig } from './src/config/app-config.interface'; import { extendEnvironmentWithAppConfig } from './src/config/config.util'; /* @@ -160,16 +161,7 @@ export function app() { /** * Checking server status */ - server.get('/app/health', async (req,res) => { - try { - const serverStatus = await https.get(`${environment.rest.baseUrl}/actuator/health`); - res.send(serverStatus); - } catch (error) { - res.send({ - error: error.message - }); - } - }); + server.get('/app/health', healthCheck); // Register the ngApp callback function to handle incoming requests server.get('*', ngApp); @@ -301,6 +293,21 @@ function start() { } } +/* + * The callback function to serve health check requests + */ +function healthCheck(req, res) { + const baseUrl = `${environment.rest.baseUrl}${environment.actuators.endpointPath}`; + axios.get(baseUrl) + .then((response) => { + res.status(response.status).send(response.data); + }) + .catch((error) => { + res.status(error.response.status).send({ + error: error.message + }); + }); +} // Webpack will replace 'require' with '__webpack_require__' // '__non_webpack_require__' is a proxy to Node 'require' // The below code is to ensure that the server is run only when not requiring the bundle. diff --git a/yarn.lock b/yarn.lock index 00c93618d9..a612a985d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3226,6 +3226,14 @@ axios@0.21.4: dependencies: follow-redirects "^1.14.0" +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -6108,7 +6116,7 @@ flatted@^3.1.0, flatted@^3.2.5: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== -follow-redirects@^1.0.0, follow-redirects@^1.14.0: +follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.9: version "1.14.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== From 0b664431afb6306a67034e70261b406081b805f0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 10 May 2022 18:10:50 +0200 Subject: [PATCH 19/75] [CST-5307] Create a standalone component for person claim button --- .../item-pages/person/person.component.html | 14 +- .../person/person.component.spec.ts | 52 +++- .../item-pages/person/person.component.ts | 81 +------ .../person-page-claim-button.component.html | 1 + .../person-page-claim-button.component.scss | 0 ...person-page-claim-button.component.spec.ts | 186 ++++++++++++++ .../person-page-claim-button.component.ts | 85 +++++++ src/app/shared/shared.module.ts | 229 +++++++++++++----- src/assets/i18n/en.json5 | 2 + 9 files changed, 501 insertions(+), 149 deletions(-) create mode 100644 src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html create mode 100644 src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.scss create mode 100644 src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.spec.ts create mode 100644 src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index 7505a31327..96cfdbfacd 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -4,7 +4,7 @@
- +
@@ -20,18 +20,10 @@ [fields]="['person.email']" [label]="'person.page.email'"> - - - - - - - -
+ +
{{"item.page.link.full" | translate}} diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.spec.ts b/src/app/entity-groups/research-entities/item-pages/person/person.component.spec.ts index 93b3cf208d..efbc48a209 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.spec.ts @@ -17,24 +17,12 @@ const mockItem: Item = Object.assign(new Item(), { value: 'fake@email.com' } ], - // 'person.identifier.orcid': [ - // { - // language: 'en_US', - // value: 'ORCID-1' - // } - // ], 'person.birthDate': [ { language: 'en_US', value: '1993' } ], - // 'person.identifier.staffid': [ - // { - // language: 'en_US', - // value: '1' - // } - // ], 'person.jobTitle': [ { language: 'en_US', @@ -62,4 +50,42 @@ const mockItem: Item = Object.assign(new Item(), { } }); -describe('PersonComponent', getItemPageFieldsTest(mockItem, PersonComponent)); +const mockItemWithTitle: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])), + metadata: { + 'person.email': [ + { + language: 'en_US', + value: 'fake@email.com' + } + ], + 'person.birthDate': [ + { + language: 'en_US', + value: '1993' + } + ], + 'person.jobTitle': [ + { + language: 'en_US', + value: 'Developer' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Doe, John' + } + ] + }, + relationships: createRelationshipsObservable(), + _links: { + self : { + href: 'item-href' + } + } +}); + +describe('PersonComponent with family and given names', getItemPageFieldsTest(mockItem, PersonComponent)); + +describe('PersonComponent with dc.title', getItemPageFieldsTest(mockItemWithTitle, PersonComponent)); diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.ts b/src/app/entity-groups/research-entities/item-pages/person/person.component.ts index 33db18681f..27fdd2ab15 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.ts +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.ts @@ -1,20 +1,10 @@ -import {Component, OnInit} from '@angular/core'; +import { Component } from '@angular/core'; import { ItemComponent } from '../../../../item-page/simple/item-types/shared/item.component'; import { ViewMode } from '../../../../core/shared/view-mode.model'; -import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; -import {MetadataValue} from '../../../../core/shared/metadata.models'; -import {FeatureID} from '../../../../core/data/feature-authorization/feature-id'; -import {mergeMap, take} from 'rxjs/operators'; -import {getFirstSucceededRemoteData} from '../../../../core/shared/operators'; -import {RemoteData} from '../../../../core/data/remote-data'; -import {ResearcherProfile} from '../../../../core/profile/model/researcher-profile.model'; -import {isNotUndefined} from '../../../../shared/empty.util'; -import {BehaviorSubject, Observable} from 'rxjs'; -import {RouteService} from '../../../../core/services/route.service'; -import {AuthorizationDataService} from '../../../../core/data/feature-authorization/authorization-data.service'; -import {ResearcherProfileService} from '../../../../core/profile/researcher-profile.service'; -import {NotificationsService} from '../../../../shared/notifications/notifications.service'; -import {TranslateService} from '@ngx-translate/core'; +import { + listableObjectComponent +} from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { MetadataValue } from '../../../../core/shared/metadata.models'; @listableObjectComponent('Person', ViewMode.StandalonePage) @Component({ @@ -25,76 +15,25 @@ import {TranslateService} from '@ngx-translate/core'; /** * The component for displaying metadata and relations of an item of the type Person */ -export class PersonComponent extends ItemComponent implements OnInit { - - claimable$: BehaviorSubject = new BehaviorSubject(false); - - constructor(protected routeService: RouteService, - protected authorizationService: AuthorizationDataService, - protected notificationsService: NotificationsService, - protected translate: TranslateService, - protected researcherProfileService: ResearcherProfileService) { - super(routeService); - } - - ngOnInit(): void { - super.ngOnInit(); - - this.authorizationService.isAuthorized(FeatureID.CanClaimItem, this.object._links.self.href).pipe( - take(1) - ).subscribe((isAuthorized: boolean) => { - this.claimable$.next(isAuthorized); - }); - - } - - /** - * Create a new researcher profile claiming the current item. - */ - claim() { - this.researcherProfileService.createFromExternalSource(this.object._links.self.href).pipe( - getFirstSucceededRemoteData(), - mergeMap((rd: RemoteData) => { - return this.researcherProfileService.findRelatedItemId(rd.payload); - })) - .subscribe((id: string) => { - if (isNotUndefined(id)) { - this.notificationsService.success(this.translate.get('researcherprofile.success.claim.title'), - this.translate.get('researcherprofile.success.claim.body')); - this.claimable$.next(false); - } else { - this.notificationsService.error( - this.translate.get('researcherprofile.error.claim.title'), - this.translate.get('researcherprofile.error.claim.body')); - } - }); - } - - /** - * Returns true if the item is claimable, false otherwise. - */ - isClaimable(): Observable { - return this.claimable$; - } +export class PersonComponent extends ItemComponent { /** * Returns the metadata values to be used for the page title. */ - getTitleMetadataValues(): MetadataValue[]{ + getTitleMetadataValues(): MetadataValue[] { const metadataValues = []; const familyName = this.object?.firstMetadata('person.familyName'); const givenName = this.object?.firstMetadata('person.givenName'); const title = this.object?.firstMetadata('dc.title'); - if (familyName){ + if (familyName) { metadataValues.push(familyName); } - if (givenName){ + if (givenName) { metadataValues.push(givenName); } - if (metadataValues.length === 0 && title){ + if (metadataValues.length === 0 && title) { metadataValues.push(title); } return metadataValues; } - } diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html new file mode 100644 index 0000000000..12d5d2b47d --- /dev/null +++ b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.html @@ -0,0 +1 @@ + diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.scss b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.spec.ts b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.spec.ts new file mode 100644 index 0000000000..168517b47a --- /dev/null +++ b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.spec.ts @@ -0,0 +1,186 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import { of as observableOf } from 'rxjs'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; + +import { PersonPageClaimButtonComponent } from './person-page-claim-button.component'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; +import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; +import { ResearcherProfileService } from '../../../core/profile/researcher-profile.service'; +import { RouteService } from '../../../core/services/route.service'; +import { routeServiceStub } from '../../testing/route-service.stub'; +import { Item } from '../../../core/shared/item.model'; +import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; +import { getTestScheduler } from 'jasmine-marbles'; +import { TestScheduler } from 'rxjs/testing'; + +describe('PersonPageClaimButtonComponent', () => { + let scheduler: TestScheduler; + let component: PersonPageClaimButtonComponent; + let fixture: ComponentFixture; + + const mockItem: Item = Object.assign(new Item(), { + metadata: { + 'person.email': [ + { + language: 'en_US', + value: 'fake@email.com' + } + ], + 'person.birthDate': [ + { + language: 'en_US', + value: '1993' + } + ], + 'person.jobTitle': [ + { + language: 'en_US', + value: 'Developer' + } + ], + 'person.familyName': [ + { + language: 'en_US', + value: 'Doe' + } + ], + 'person.givenName': [ + { + language: 'en_US', + value: 'John' + } + ] + }, + _links: { + self: { + href: 'item-href' + } + } + }); + + const mockResearcherProfile: ResearcherProfile = Object.assign(new ResearcherProfile(), { + id: 'test-id', + visible: true, + type: 'profile', + _links: { + item: { + href: 'https://rest.api/rest/api/profiles/test-id/item' + }, + self: { + href: 'https://rest.api/rest/api/profiles/test-id' + }, + } + }); + + const notificationsService = new NotificationsServiceStub(); + + const authorizationDataService = jasmine.createSpyObj('authorizationDataService', { + isAuthorized: jasmine.createSpy('isAuthorized') + }); + + const researcherProfileService = jasmine.createSpyObj('researcherProfileService', { + createFromExternalSource: jasmine.createSpy('createFromExternalSource'), + findRelatedItemId: jasmine.createSpy('findRelatedItemId'), + }); + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) + ], + declarations: [PersonPageClaimButtonComponent], + providers: [ + { provide: AuthorizationDataService, useValue: authorizationDataService }, + { provide: NotificationsService, useValue: notificationsService }, + { provide: ResearcherProfileService, useValue: researcherProfileService }, + { provide: RouteService, useValue: routeServiceStub }, + ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PersonPageClaimButtonComponent); + component = fixture.componentInstance; + component.object = mockItem; + }); + + describe('when item can be claimed', () => { + beforeEach(() => { + authorizationDataService.isAuthorized.and.returnValue(observableOf(true)); + researcherProfileService.createFromExternalSource.calls.reset(); + researcherProfileService.findRelatedItemId.calls.reset(); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create claim button', () => { + const btn = fixture.debugElement.query(By.css('[data-test="item-claim"]')); + expect(btn).toBeTruthy(); + }); + + describe('claim', () => { + describe('when successfully', () => { + beforeEach(() => { + scheduler = getTestScheduler(); + researcherProfileService.createFromExternalSource.and.returnValue(createSuccessfulRemoteDataObject$(mockResearcherProfile)); + researcherProfileService.findRelatedItemId.and.returnValue(observableOf('test-id')); + }); + + it('should display success notification', () => { + scheduler.schedule(() => component.claim()); + scheduler.flush(); + + expect(researcherProfileService.findRelatedItemId).toHaveBeenCalled(); + expect(notificationsService.success).toHaveBeenCalled(); + }); + }); + + describe('when not successfully', () => { + beforeEach(() => { + scheduler = getTestScheduler(); + researcherProfileService.createFromExternalSource.and.returnValue(createFailedRemoteDataObject$()); + }); + + it('should display success notification', () => { + scheduler.schedule(() => component.claim()); + scheduler.flush(); + + expect(researcherProfileService.findRelatedItemId).not.toHaveBeenCalled(); + expect(notificationsService.error).toHaveBeenCalled(); + }); + }); + }); + + }); + + describe('when item cannot be claimed', () => { + beforeEach(() => { + authorizationDataService.isAuthorized.and.returnValue(observableOf(false)); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should create claim button', () => { + const btn = fixture.debugElement.query(By.css('[data-test="item-claim"]')); + expect(btn).toBeFalsy(); + }); + + }); +}); diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts new file mode 100644 index 0000000000..d2e8e888e1 --- /dev/null +++ b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts @@ -0,0 +1,85 @@ +import { Component, Input, OnInit } from '@angular/core'; + +import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; +import { mergeMap, take } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { RouteService } from '../../../core/services/route.service'; +import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { ResearcherProfileService } from '../../../core/profile/researcher-profile.service'; +import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { RemoteData } from '../../../core/data/remote-data'; +import { ResearcherProfile } from '../../../core/profile/model/researcher-profile.model'; +import { isNotEmpty } from '../../empty.util'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; + +@Component({ + selector: 'ds-person-page-claim-button', + templateUrl: './person-page-claim-button.component.html', + styleUrls: ['./person-page-claim-button.component.scss'] +}) +export class PersonPageClaimButtonComponent implements OnInit { + + /** + * The target person item to claim + */ + @Input() object: DSpaceObject; + + /** + * A boolean representing if item can be claimed or not + */ + claimable$: BehaviorSubject = new BehaviorSubject(false); + + constructor(protected routeService: RouteService, + protected authorizationService: AuthorizationDataService, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected researcherProfileService: ResearcherProfileService) { + } + + ngOnInit(): void { + this.authorizationService.isAuthorized(FeatureID.CanClaimItem, this.object._links.self.href).pipe( + take(1) + ).subscribe((isAuthorized: boolean) => { + this.claimable$.next(isAuthorized); + }); + + } + + /** + * Create a new researcher profile claiming the current item. + */ + claim() { + this.researcherProfileService.createFromExternalSource(this.object._links.self.href).pipe( + getFirstCompletedRemoteData(), + mergeMap((rd: RemoteData) => { + console.log(rd); + if (rd.hasSucceeded) { + return this.researcherProfileService.findRelatedItemId(rd.payload); + } else { + return observableOf(null); + } + })) + .subscribe((id: string) => { + if (isNotEmpty(id)) { + this.notificationsService.success(this.translate.get('researcherprofile.success.claim.title'), + this.translate.get('researcherprofile.success.claim.body')); + this.claimable$.next(false); + } else { + this.notificationsService.error( + this.translate.get('researcherprofile.error.claim.title'), + this.translate.get('researcherprofile.error.claim.body')); + } + }); + } + + /** + * Returns true if the item is claimable, false otherwise. + */ + isClaimable(): Observable { + return this.claimable$; + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 847b3910e0..b2a36285bb 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -7,7 +7,12 @@ import { DragDropModule } from '@angular/cdk/drag-drop'; import { NouisliderModule } from 'ng2-nouislider'; import { - NgbDatepickerModule, NgbDropdownModule, NgbNavModule, NgbPaginationModule, NgbTimepickerModule, NgbTooltipModule, + NgbDatepickerModule, + NgbDropdownModule, + NgbNavModule, + NgbPaginationModule, + NgbTimepickerModule, + NgbTooltipModule, NgbTypeaheadModule, } from '@ng-bootstrap/ng-bootstrap'; import { MissingTranslationHandler, TranslateModule } from '@ngx-translate/core'; @@ -16,7 +21,9 @@ import { FileUploadModule } from 'ng2-file-upload'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { MomentModule } from 'ngx-moment'; import { ConfirmationModalComponent } from './confirmation-modal/confirmation-modal.component'; -import { ExportMetadataSelectorComponent } from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; +import { + ExportMetadataSelectorComponent +} from './dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component'; import { FileDropzoneNoUploaderComponent } from './file-dropzone-no-uploader/file-dropzone-no-uploader.component'; import { ItemListElementComponent } from './object-list/item-list-element/item-types/item/item-list-element.component'; import { EnumKeysPipe } from './utils/enum-keys-pipe'; @@ -24,13 +31,21 @@ import { FileSizePipe } from './utils/file-size-pipe'; import { MetadataFieldValidator } from './utils/metadatafield-validator.directive'; import { SafeUrlPipe } from './utils/safe-url-pipe'; import { ConsolePipe } from './utils/console.pipe'; -import { CollectionListElementComponent } from './object-list/collection-list-element/collection-list-element.component'; +import { + CollectionListElementComponent +} from './object-list/collection-list-element/collection-list-element.component'; import { CommunityListElementComponent } from './object-list/community-list-element/community-list-element.component'; -import { SearchResultListElementComponent } from './object-list/search-result-list-element/search-result-list-element.component'; +import { + SearchResultListElementComponent +} from './object-list/search-result-list-element/search-result-list-element.component'; import { ObjectListComponent } from './object-list/object-list.component'; -import { CollectionGridElementComponent } from './object-grid/collection-grid-element/collection-grid-element.component'; +import { + CollectionGridElementComponent +} from './object-grid/collection-grid-element/collection-grid-element.component'; import { CommunityGridElementComponent } from './object-grid/community-grid-element/community-grid-element.component'; -import { AbstractListableElementComponent } from './object-collection/shared/object-collection-element/abstract-listable-element.component'; +import { + AbstractListableElementComponent +} from './object-collection/shared/object-collection-element/abstract-listable-element.component'; import { ObjectGridComponent } from './object-grid/object-grid.component'; import { ObjectCollectionComponent } from './object-collection/object-collection.component'; import { ErrorComponent } from './error/error.component'; @@ -38,7 +53,9 @@ import { LoadingComponent } from './loading/loading.component'; import { PaginationComponent } from './pagination/pagination.component'; import { ThumbnailComponent } from '../thumbnail/thumbnail.component'; import { SearchFormComponent } from './search-form/search-form.component'; -import { SearchResultGridElementComponent } from './object-grid/search-result-grid-element/search-result-grid-element.component'; +import { + SearchResultGridElementComponent +} from './object-grid/search-result-grid-element/search-result-grid-element.component'; import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component'; import { VarDirective } from './utils/var.directive'; import { AuthNavMenuComponent } from './auth-nav-menu/auth-nav-menu.component'; @@ -53,21 +70,33 @@ import { ChipsComponent } from './chips/chips.component'; import { NumberPickerComponent } from './number-picker/number-picker.component'; import { MockAdminGuard } from './mocks/admin-guard.service.mock'; import { AlertComponent } from './alert/alert.component'; -import { SearchResultDetailElementComponent } from './object-detail/my-dspace-result-detail-element/search-result-detail-element.component'; +import { + SearchResultDetailElementComponent +} from './object-detail/my-dspace-result-detail-element/search-result-detail-element.component'; import { ClaimedTaskActionsComponent } from './mydspace-actions/claimed-task/claimed-task-actions.component'; import { PoolTaskActionsComponent } from './mydspace-actions/pool-task/pool-task-actions.component'; import { ObjectDetailComponent } from './object-detail/object-detail.component'; -import { ItemDetailPreviewComponent } from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component'; -import { MyDSpaceItemStatusComponent } from './object-collection/shared/mydspace-item-status/my-dspace-item-status.component'; +import { + ItemDetailPreviewComponent +} from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component'; +import { + MyDSpaceItemStatusComponent +} from './object-collection/shared/mydspace-item-status/my-dspace-item-status.component'; import { WorkspaceitemActionsComponent } from './mydspace-actions/workspaceitem/workspaceitem-actions.component'; import { WorkflowitemActionsComponent } from './mydspace-actions/workflowitem/workflowitem-actions.component'; import { ItemSubmitterComponent } from './object-collection/shared/mydspace-item-submitter/item-submitter.component'; import { ItemActionsComponent } from './mydspace-actions/item/item-actions.component'; -import { ClaimedTaskActionsApproveComponent } from './mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component'; -import { ClaimedTaskActionsRejectComponent } from './mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component'; +import { + ClaimedTaskActionsApproveComponent +} from './mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component'; +import { + ClaimedTaskActionsRejectComponent +} from './mydspace-actions/claimed-task/reject/claimed-task-actions-reject.component'; import { ObjNgFor } from './utils/object-ngfor.pipe'; import { BrowseByComponent } from './browse-by/browse-by.component'; -import { BrowseEntryListElementComponent } from './object-list/browse-entry-list-element/browse-entry-list-element.component'; +import { + BrowseEntryListElementComponent +} from './object-list/browse-entry-list-element/browse-entry-list-element.component'; import { DebounceDirective } from './utils/debounce.directive'; import { ClickOutsideDirective } from './utils/click-outside.directive'; import { EmphasizePipe } from './utils/emphasize.pipe'; @@ -76,53 +105,105 @@ import { CapitalizePipe } from './utils/capitalize.pipe'; import { ObjectKeysPipe } from './utils/object-keys-pipe'; import { AuthorityConfidenceStateDirective } from './authority-confidence/authority-confidence-state.directive'; import { LangSwitchComponent } from './lang-switch/lang-switch.component'; -import { PlainTextMetadataListElementComponent } from './object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component'; -import { ItemMetadataListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-list-element.component'; -import { MetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/metadata-representation-list-element.component'; +import { + PlainTextMetadataListElementComponent +} from './object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component'; +import { + ItemMetadataListElementComponent +} from './object-list/metadata-representation-list-element/item/item-metadata-list-element.component'; +import { + MetadataRepresentationListElementComponent +} from './object-list/metadata-representation-list-element/metadata-representation-list-element.component'; import { ObjectValuesPipe } from './utils/object-values-pipe'; import { InListValidator } from './utils/in-list-validator.directive'; import { AutoFocusDirective } from './utils/auto-focus.directive'; import { StartsWithDateComponent } from './starts-with/date/starts-with-date.component'; import { StartsWithTextComponent } from './starts-with/text/starts-with-text.component'; import { DSOSelectorComponent } from './dso-selector/dso-selector/dso-selector.component'; -import { CreateCommunityParentSelectorComponent } from './dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component'; -import { CreateItemParentSelectorComponent } from './dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component'; -import { CreateCollectionParentSelectorComponent } from './dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component'; -import { CommunitySearchResultListElementComponent } from './object-list/search-result-list-element/community-search-result/community-search-result-list-element.component'; -import { CollectionSearchResultListElementComponent } from './object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component'; -import { EditItemSelectorComponent } from './dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; -import { EditCommunitySelectorComponent } from './dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component'; -import { EditCollectionSelectorComponent } from './dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component'; -import { ItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; -import { MetadataFieldWrapperComponent } from '../item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component'; +import { + CreateCommunityParentSelectorComponent +} from './dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component'; +import { + CreateItemParentSelectorComponent +} from './dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component'; +import { + CreateCollectionParentSelectorComponent +} from './dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component'; +import { + CommunitySearchResultListElementComponent +} from './object-list/search-result-list-element/community-search-result/community-search-result-list-element.component'; +import { + CollectionSearchResultListElementComponent +} from './object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component'; +import { + EditItemSelectorComponent +} from './dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; +import { + EditCommunitySelectorComponent +} from './dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component'; +import { + EditCollectionSelectorComponent +} from './dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component'; +import { + ItemListPreviewComponent +} from './object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; +import { + MetadataFieldWrapperComponent +} from '../item-page/field-components/metadata-field-wrapper/metadata-field-wrapper.component'; import { MetadataValuesComponent } from '../item-page/field-components/metadata-values/metadata-values.component'; import { RoleDirective } from './roles/role.directive'; import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component'; -import { ClaimedTaskActionsReturnToPoolComponent } from './mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component'; -import { ItemDetailPreviewFieldComponent } from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component'; -import { CollectionSearchResultGridElementComponent } from './object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component'; -import { CommunitySearchResultGridElementComponent } from './object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component'; +import { + ClaimedTaskActionsReturnToPoolComponent +} from './mydspace-actions/claimed-task/return-to-pool/claimed-task-actions-return-to-pool.component'; +import { + ItemDetailPreviewFieldComponent +} from './object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component'; +import { + CollectionSearchResultGridElementComponent +} from './object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component'; +import { + CommunitySearchResultGridElementComponent +} from './object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component'; import { PageSizeSelectorComponent } from './page-size-selector/page-size-selector.component'; import { AbstractTrackableComponent } from './trackable/abstract-trackable.component'; -import { ComcolMetadataComponent } from './comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component'; +import { + ComcolMetadataComponent +} from './comcol/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component'; import { ItemSelectComponent } from './object-select/item-select/item-select.component'; import { CollectionSelectComponent } from './object-select/collection-select/collection-select.component'; -import { FilterInputSuggestionsComponent } from './input-suggestions/filter-suggestions/filter-input-suggestions.component'; -import { DsoInputSuggestionsComponent } from './input-suggestions/dso-input-suggestions/dso-input-suggestions.component'; +import { + FilterInputSuggestionsComponent +} from './input-suggestions/filter-suggestions/filter-input-suggestions.component'; +import { + DsoInputSuggestionsComponent +} from './input-suggestions/dso-input-suggestions/dso-input-suggestions.component'; import { ItemGridElementComponent } from './object-grid/item-grid-element/item-types/item/item-grid-element.component'; import { TypeBadgeComponent } from './object-list/type-badge/type-badge.component'; -import { MetadataRepresentationLoaderComponent } from './metadata-representation/metadata-representation-loader.component'; +import { + MetadataRepresentationLoaderComponent +} from './metadata-representation/metadata-representation-loader.component'; import { MetadataRepresentationDirective } from './metadata-representation/metadata-representation.directive'; -import { ListableObjectComponentLoaderComponent } from './object-collection/shared/listable-object/listable-object-component-loader.component'; -import { ItemSearchResultListElementComponent } from './object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; +import { + ListableObjectComponentLoaderComponent +} from './object-collection/shared/listable-object/listable-object-component-loader.component'; +import { + ItemSearchResultListElementComponent +} from './object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; import { ListableObjectDirective } from './object-collection/shared/listable-object/listable-object.directive'; -import { ItemMetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component'; +import { + ItemMetadataRepresentationListElementComponent +} from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component'; import { PageWithSidebarComponent } from './sidebar/page-with-sidebar.component'; import { SidebarDropdownComponent } from './sidebar/sidebar-dropdown.component'; import { SidebarFilterComponent } from './sidebar/filter/sidebar-filter.component'; import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component'; -import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component'; -import { ImportableListItemControlComponent } from './object-collection/shared/importable-list-item-control/importable-list-item-control.component'; +import { + SelectableListItemControlComponent +} from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component'; +import { + ImportableListItemControlComponent +} from './object-collection/shared/importable-list-item-control/importable-list-item-control.component'; import { ItemVersionsComponent } from './item/item-versions/item-versions.component'; import { SortablejsModule } from 'ngx-sortablejs'; import { LogInContainerComponent } from './log-in/container/log-in-container.component'; @@ -135,10 +216,16 @@ import { ItemVersionsNoticeComponent } from './item/item-versions/notice/item-ve import { FileValidator } from './utils/require-file.validator'; import { FileValueAccessorDirective } from './utils/file-value-accessor.directive'; import { FileSectionComponent } from '../item-page/simple/field-components/file-section/file-section.component'; -import { ModifyItemOverviewComponent } from '../item-page/edit-item-page/modify-item-overview/modify-item-overview.component'; -import { ClaimedTaskActionsLoaderComponent } from './mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component'; +import { + ModifyItemOverviewComponent +} from '../item-page/edit-item-page/modify-item-overview/modify-item-overview.component'; +import { + ClaimedTaskActionsLoaderComponent +} from './mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component'; import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/switcher/claimed-task-actions.directive'; -import { ClaimedTaskActionsEditMetadataComponent } from './mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component'; +import { + ClaimedTaskActionsEditMetadataComponent +} from './mydspace-actions/claimed-task/edit-metadata/claimed-task-actions-edit-metadata.component'; import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component'; import { NgForTrackByIdDirective } from './ng-for-track-by-id.directive'; import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component'; @@ -146,34 +233,63 @@ import { CollectionDropdownComponent } from './collection-dropdown/collection-dr import { EntityDropdownComponent } from './entity-dropdown/entity-dropdown.component'; import { VocabularyTreeviewComponent } from './vocabulary-treeview/vocabulary-treeview.component'; import { CurationFormComponent } from '../curation-form/curation-form.component'; -import { PublicationSidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component'; -import { SidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/sidebar-search-list-element.component'; -import { CollectionSidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/collection/collection-sidebar-search-list-element.component'; -import { CommunitySidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/community/community-sidebar-search-list-element.component'; -import { AuthorizedCollectionSelectorComponent } from './dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component'; +import { + PublicationSidebarSearchListElementComponent +} from './object-list/sidebar-search-list-element/item-types/publication/publication-sidebar-search-list-element.component'; +import { + SidebarSearchListElementComponent +} from './object-list/sidebar-search-list-element/sidebar-search-list-element.component'; +import { + CollectionSidebarSearchListElementComponent +} from './object-list/sidebar-search-list-element/collection/collection-sidebar-search-list-element.component'; +import { + CommunitySidebarSearchListElementComponent +} from './object-list/sidebar-search-list-element/community/community-sidebar-search-list-element.component'; +import { + AuthorizedCollectionSelectorComponent +} from './dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component'; import { DsoPageEditButtonComponent } from './dso-page/dso-page-edit-button/dso-page-edit-button.component'; import { DsoPageVersionButtonComponent } from './dso-page/dso-page-version-button/dso-page-version-button.component'; import { HoverClassDirective } from './hover-class.directive'; -import { ValidationSuggestionsComponent } from './input-suggestions/validation-suggestions/validation-suggestions.component'; +import { + ValidationSuggestionsComponent +} from './input-suggestions/validation-suggestions/validation-suggestions.component'; import { ItemAlertsComponent } from './item/item-alerts/item-alerts.component'; -import { ItemSearchResultGridElementComponent } from './object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component'; +import { + ItemSearchResultGridElementComponent +} from './object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component'; import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component'; -import { GenericItemPageFieldComponent } from '../item-page/simple/field-components/specific-field/generic/generic-item-page-field.component'; -import { MetadataRepresentationListComponent } from '../item-page/simple/metadata-representation-list/metadata-representation-list.component'; +import { + GenericItemPageFieldComponent +} from '../item-page/simple/field-components/specific-field/generic/generic-item-page-field.component'; +import { + MetadataRepresentationListComponent +} from '../item-page/simple/metadata-representation-list/metadata-representation-list.component'; import { RelatedItemsComponent } from '../item-page/simple/related-items/related-items-component'; import { LinkMenuItemComponent } from './menu/menu-item/link-menu-item.component'; import { OnClickMenuItemComponent } from './menu/menu-item/onclick-menu-item.component'; import { TextMenuItemComponent } from './menu/menu-item/text-menu-item.component'; import { SearchNavbarComponent } from '../search-navbar/search-navbar.component'; -import { ItemVersionsSummaryModalComponent } from './item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component'; -import { ItemVersionsDeleteModalComponent } from './item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component'; +import { + ItemVersionsSummaryModalComponent +} from './item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component'; +import { + ItemVersionsDeleteModalComponent +} from './item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component'; import { ScopeSelectorModalComponent } from './search-form/scope-selector-modal/scope-selector-modal.component'; -import { BitstreamRequestACopyPageComponent } from './bitstream-request-a-copy-page/bitstream-request-a-copy-page.component'; +import { + BitstreamRequestACopyPageComponent +} from './bitstream-request-a-copy-page/bitstream-request-a-copy-page.component'; import { DsSelectComponent } from './ds-select/ds-select.component'; import { LogInOidcComponent } from './log-in/methods/oidc/log-in-oidc.component'; -import { ThemedItemListPreviewComponent } from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; -import { ClaimItemSelectorComponent } from './dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component'; +import { + ThemedItemListPreviewComponent +} from './object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component'; +import { + ClaimItemSelectorComponent +} from './dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component'; import { ExternalLinkMenuItemComponent } from './menu/menu-item/external-link-menu-item.component'; +import { PersonPageClaimButtonComponent } from './dso-page/person-page-claim-button/person-page-claim-button.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -410,6 +526,7 @@ const SHARED_ITEM_PAGE_COMPONENTS = [ MetadataValuesComponent, DsoPageEditButtonComponent, DsoPageVersionButtonComponent, + PersonPageClaimButtonComponent, ItemAlertsComponent, GenericItemPageFieldComponent, MetadataRepresentationListComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index e4875d153e..868f79e490 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2762,6 +2762,8 @@ "person.page.lastname": "Last Name", + "person.page.name": "Name", + "person.page.link.full": "Show all metadata", "person.page.orcid": "ORCID", From 853dcecfb8e5cad295ae7f15ecfaf5433ae408f1 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 10 May 2022 18:14:00 +0200 Subject: [PATCH 20/75] [CST-5307] Refactoring and adding missing unit tests --- .../core/breadcrumbs/dso-name.service.spec.ts | 31 +- .../researcher-profile.service.spec.ts | 290 +++++++++++++++++ .../profile/researcher-profile.service.ts | 208 ++++++------ .../profile-claim.service.spec.ts | 215 ++++++++++++ .../profile-claim/profile-claim.service.ts | 59 ++-- ...ile-page-researcher-form.component.spec.ts | 7 +- .../profile-page-researcher-form.component.ts | 20 +- .../profile-page.component.spec.ts | 306 +++++++++++------- .../profile-page/profile-page.component.ts | 21 +- yarn.lock | 5 - 10 files changed, 876 insertions(+), 286 deletions(-) create mode 100644 src/app/core/profile/researcher-profile.service.spec.ts create mode 100644 src/app/profile-page/profile-claim/profile-claim.service.spec.ts diff --git a/src/app/core/breadcrumbs/dso-name.service.spec.ts b/src/app/core/breadcrumbs/dso-name.service.spec.ts index 7a399ce748..9f2f76599a 100644 --- a/src/app/core/breadcrumbs/dso-name.service.spec.ts +++ b/src/app/core/breadcrumbs/dso-name.service.spec.ts @@ -78,15 +78,32 @@ describe(`DSONameService`, () => { }); describe(`factories.Person`, () => { - beforeEach(() => { - spyOn(mockPerson, 'firstMetadataValue').and.returnValues(...mockPersonName.split(', ')); + describe(`with person.familyName and person.givenName`, () => { + beforeEach(() => { + spyOn(mockPerson, 'firstMetadataValue').and.returnValues(...mockPersonName.split(', ')); + }); + + it(`should return 'person.familyName, person.givenName'`, () => { + const result = (service as any).factories.Person(mockPerson); + expect(result).toBe(mockPersonName); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.familyName'); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.givenName'); + expect(mockPerson.firstMetadataValue).not.toHaveBeenCalledWith('dc.title'); + }); }); - it(`should return 'person.familyName, person.givenName'`, () => { - const result = (service as any).factories.Person(mockPerson); - expect(result).toBe(mockPersonName); - expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.familyName'); - expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.givenName'); + describe(`without person.familyName and person.givenName`, () => { + beforeEach(() => { + spyOn(mockPerson, 'firstMetadataValue').and.returnValues(undefined, undefined, mockPersonName); + }); + + it(`should return dc.title`, () => { + const result = (service as any).factories.Person(mockPerson); + expect(result).toBe(mockPersonName); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.familyName'); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.givenName'); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('dc.title'); + }); }); }); diff --git a/src/app/core/profile/researcher-profile.service.spec.ts b/src/app/core/profile/researcher-profile.service.spec.ts new file mode 100644 index 0000000000..103bae2719 --- /dev/null +++ b/src/app/core/profile/researcher-profile.service.spec.ts @@ -0,0 +1,290 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; + +import { cold, getTestScheduler, hot } from 'jasmine-marbles'; +import { of as observableOf } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; + +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestService } from '../data/request.service'; +import { PageInfo } from '../shared/page-info.model'; +import { buildPaginatedList } from '../data/paginated-list.model'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { RestResponse } from '../cache/response.models'; +import { RequestEntry } from '../data/request-entry.model'; +import { ResearcherProfileService } from './researcher-profile.service'; +import { RouterMock } from '../../shared/mocks/router.mock'; +import { ResearcherProfile } from './model/researcher-profile.model'; +import { Item } from '../shared/item.model'; +import { ReplaceOperation } from 'fast-json-patch'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; +import { PostRequest } from '../data/request.models'; + +describe('ResearcherProfileService', () => { + let scheduler: TestScheduler; + let service: ResearcherProfileService; + let serviceAsAny: any; + let requestService: RequestService; + let rdbService: RemoteDataBuildService; + let objectCache: ObjectCacheService; + let halService: HALEndpointService; + let responseCacheEntry: RequestEntry; + + const researcherProfileId = 'beef9946-rt56-479e-8f11-b90cbe9f7241'; + const itemId = 'beef9946-rt56-479e-8f11-b90cbe9f7241'; + const researcherProfileItem: Item = Object.assign(new Item(), { + id: itemId, + _links: { + self: { + href: `https://rest.api/rest/api/items/${itemId}` + }, + } + }); + const researcherProfile: ResearcherProfile = Object.assign(new ResearcherProfile(), { + id: researcherProfileId, + visible: false, + type: 'profile', + _links: { + item: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId}/item` + }, + self: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId}` + }, + } + }); + + const researcherProfilePatched: ResearcherProfile = Object.assign(new ResearcherProfile(), { + id: researcherProfileId, + visible: true, + type: 'profile', + _links: { + item: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId}/item` + }, + self: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId}` + }, + } + }); + + const researcherProfileId2 = 'agbf9946-f4ce-479e-8f11-b90cbe9f7241'; + const anotherResearcherProfile: ResearcherProfile = Object.assign(new ResearcherProfile(), { + id: researcherProfileId2, + visible: false, + type: 'profile', + _links: { + self: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId2}` + }, + } + }); + const endpointURL = `https://rest.api/rest/api/profiles`; + const sourceUri = `https://rest.api/rest/api/external-source/profile`; + const requestURL = `https://rest.api/rest/api/profiles/${researcherProfileId}`; + const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a'; + + const pageInfo = new PageInfo(); + const array = [researcherProfile, anotherResearcherProfile]; + const paginatedList = buildPaginatedList(pageInfo, array); + const researcherProfileRD = createSuccessfulRemoteDataObject(researcherProfile); + const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + + beforeEach(() => { + scheduler = getTestScheduler(); + + halService = jasmine.createSpyObj('halService', { + getEndpoint: cold('a', { a: endpointURL }) + }); + + responseCacheEntry = new RequestEntry(); + responseCacheEntry.request = { href: 'https://rest.api/' } as any; + responseCacheEntry.response = new RestResponse(true, 200, 'Success'); + + requestService = jasmine.createSpyObj('requestService', { + generateRequestId: requestUUID, + send: true, + removeByHrefSubstring: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: observableOf(responseCacheEntry), + setStaleByHrefSubstring: jasmine.createSpy('setStaleByHrefSubstring') + }); + rdbService = jasmine.createSpyObj('rdbService', { + buildSingle: hot('a|', { + a: researcherProfileRD + }), + buildList: hot('a|', { + a: paginatedListRD + }), + buildFromRequestUUID: hot('a|', { + a: researcherProfileRD + }) + }); + objectCache = {} as ObjectCacheService; + const notificationsService = {} as NotificationsService; + const http = {} as HttpClient; + const comparator = {} as any; + const routerStub: any = new RouterMock(); + const itemService = jasmine.createSpyObj('ItemService', { + findByHref: jasmine.createSpy('findByHref') + }); + + service = new ResearcherProfileService( + requestService, + rdbService, + objectCache, + halService, + notificationsService, + http, + routerStub, + comparator, + itemService + ); + serviceAsAny = service; + + spyOn((service as any).dataService, 'create').and.callThrough(); + spyOn((service as any).dataService, 'delete').and.callThrough(); + spyOn((service as any).dataService, 'update').and.callThrough(); + spyOn((service as any).dataService, 'findById').and.callThrough(); + spyOn((service as any).dataService, 'findByHref').and.callThrough(); + spyOn((service as any).dataService, 'searchBy').and.callThrough(); + spyOn((service as any).dataService, 'getLinkPath').and.returnValue(observableOf(endpointURL)); + + }); + + describe('findById', () => { + it('should proxy the call to dataservice.findById with eperson UUID', () => { + scheduler.schedule(() => service.findById(researcherProfileId)); + scheduler.flush(); + + expect((service as any).dataService.findById).toHaveBeenCalledWith(researcherProfileId, true, true); + }); + + it('should return a ResearcherProfile object with the given id', () => { + const result = service.findById(researcherProfileId); + const expected = cold('a|', { + a: researcherProfileRD + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('create', () => { + it('should proxy the call to dataservice.create with eperson UUID', () => { + scheduler.schedule(() => service.create()); + scheduler.flush(); + + expect((service as any).dataService.create).toHaveBeenCalled(); + }); + + it('should return the RemoteData created', () => { + const result = service.create(); + const expected = cold('a|', { + a: researcherProfileRD + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('delete', () => { + it('should proxy the call to dataservice.delete', () => { + scheduler.schedule(() => service.delete(researcherProfile)); + scheduler.flush(); + + expect((service as any).dataService.delete).toHaveBeenCalledWith(researcherProfile.id); + }); + }); + + describe('findRelatedItemId', () => { + describe('with a related item', () => { + + beforeEach(() => { + (service as any).itemService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(researcherProfileItem)); + }); + + it('should proxy the call to dataservice.findById with eperson UUID', () => { + scheduler.schedule(() => service.findRelatedItemId(researcherProfile)); + scheduler.flush(); + + expect((service as any).itemService.findByHref).toHaveBeenCalledWith(researcherProfile._links.item.href, false); + }); + + it('should return a ResearcherProfile object with the given id', () => { + const result = service.findRelatedItemId(researcherProfile); + const expected = cold('(a|)', { + a: itemId + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('without a related item', () => { + + beforeEach(() => { + (service as any).itemService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(null)); + }); + + it('should proxy the call to dataservice.findById with eperson UUID', () => { + scheduler.schedule(() => service.findRelatedItemId(researcherProfile)); + scheduler.flush(); + + expect((service as any).itemService.findByHref).toHaveBeenCalledWith(researcherProfile._links.item.href, false); + }); + + it('should return a ResearcherProfile object with the given id', () => { + const result = service.findRelatedItemId(researcherProfile); + const expected = cold('(a|)', { + a: undefined + }); + expect(result).toBeObservable(expected); + }); + }); + }); + + describe('setVisibility', () => { + let patchSpy; + beforeEach(() => { + spyOn((service as any), 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + }); + + it('should proxy the call to dataservice.patch', () => { + const replaceOperation: ReplaceOperation = { + path: '/visible', + op: 'replace', + value: true + }; + + scheduler.schedule(() => service.setVisibility(researcherProfile, true)); + scheduler.flush(); + + expect((service as any).patch).toHaveBeenCalledWith(researcherProfile, [replaceOperation]); + }); + }); + + describe('createFromExternalSource', () => { + let patchSpy; + beforeEach(() => { + spyOn((service as any), 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + }); + + it('should proxy the call to dataservice.patch', () => { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + const request = new PostRequest(requestUUID, endpointURL, sourceUri, options); + + scheduler.schedule(() => service.createFromExternalSource(sourceUri)); + scheduler.flush(); + + expect((service as any).requestService.send).toHaveBeenCalledWith(request); + expect((service as any).rdbService.buildFromRequestUUID).toHaveBeenCalledWith(requestUUID); + + }); + }); + +}); diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index 0220afb964..eab6ea3d48 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -2,27 +2,25 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; + import { Store } from '@ngrx/store'; -import { Operation, RemoveOperation, ReplaceOperation } from 'fast-json-patch'; -import { combineLatest, Observable, of as observableOf } from 'rxjs'; +import { Operation, ReplaceOperation } from 'fast-json-patch'; +import { Observable, of as observableOf } from 'rxjs'; import { catchError, find, map, switchMap, tap } from 'rxjs/operators'; -import { environment } from '../../../environments/environment'; + import { NotificationsService } from '../../shared/notifications/notifications.service'; import { dataService } from '../cache/builders/build-decorators'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { ConfigurationDataService } from '../data/configuration-data.service'; import { DataService } from '../data/data.service'; import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; import { ItemDataService } from '../data/item-data.service'; import { RemoteData } from '../data/remote-data'; import { RequestService } from '../data/request.service'; -import { ConfigurationProperty } from '../shared/configuration-property.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { Item } from '../shared/item.model'; import { NoContent } from '../shared/NoContent.model'; import { - getFinishedRemoteData, + getAllCompletedRemoteData, getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../shared/operators'; @@ -31,25 +29,26 @@ import { RESEARCHER_PROFILE } from './model/researcher-profile.resource-type'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { PostRequest } from '../data/request.models'; import { hasValue } from '../../shared/empty.util'; -import {CoreState} from '../core-state.model'; +import { CoreState } from '../core-state.model'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; /** * A private DataService implementation to delegate specific methods to. */ class ResearcherProfileServiceImpl extends DataService { - protected linkPath = 'profiles'; + protected linkPath = 'profiles'; - constructor( - protected requestService: RequestService, - protected rdbService: RemoteDataBuildService, - protected store: Store, - protected objectCache: ObjectCacheService, - protected halService: HALEndpointService, - protected notificationsService: NotificationsService, - protected http: HttpClient, - protected comparator: DefaultChangeAnalyzer) { - super(); - } + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer) { + super(); + } } @@ -60,100 +59,102 @@ class ResearcherProfileServiceImpl extends DataService { @dataService(RESEARCHER_PROFILE) export class ResearcherProfileService { - dataService: ResearcherProfileServiceImpl; + dataService: ResearcherProfileServiceImpl; - responseMsToLive: number = 10 * 1000; + responseMsToLive: number = 10 * 1000; - constructor( - protected requestService: RequestService, - protected rdbService: RemoteDataBuildService, - protected store: Store, - protected objectCache: ObjectCacheService, - protected halService: HALEndpointService, - protected notificationsService: NotificationsService, - protected http: HttpClient, - protected router: Router, - protected comparator: DefaultChangeAnalyzer, - protected itemService: ItemDataService, - protected configurationService: ConfigurationDataService ) { + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected router: Router, + protected comparator: DefaultChangeAnalyzer, + protected itemService: ItemDataService) { - this.dataService = new ResearcherProfileServiceImpl(requestService, rdbService, store, objectCache, halService, - notificationsService, http, comparator); + this.dataService = new ResearcherProfileServiceImpl(requestService, rdbService, null, objectCache, halService, + notificationsService, http, comparator); - } + } - /** - * Find the researcher profile with the given uuid. - * - * @param uuid the profile uuid - */ - findById(uuid: string): Observable { - return this.dataService.findById(uuid, false) - .pipe ( getFinishedRemoteData(), - map((remoteData) => remoteData.payload)); - } + /** + * Find the researcher profile with the given uuid. + * + * @param uuid the profile uuid + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + */ + public findById(uuid: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable> { + return this.dataService.findById(uuid, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe( + getAllCompletedRemoteData(), + ); + } - /** - * Create a new researcher profile for the current user. - */ - create(): Observable> { - return this.dataService.create( new ResearcherProfile()); - } + /** + * Create a new researcher profile for the current user. + */ + public create(): Observable> { + return this.dataService.create(new ResearcherProfile()); + } - /** - * Delete a researcher profile. - * - * @param researcherProfile the profile to delete - */ - delete(researcherProfile: ResearcherProfile): Observable { - return this.dataService.delete(researcherProfile.id).pipe( - getFirstCompletedRemoteData(), - tap((response: RemoteData) => { - if (response.isSuccess) { - this.requestService.setStaleByHrefSubstring(researcherProfile._links.self.href); - } + /** + * Delete a researcher profile. + * + * @param researcherProfile the profile to delete + */ + public delete(researcherProfile: ResearcherProfile): Observable { + return this.dataService.delete(researcherProfile.id).pipe( + getFirstCompletedRemoteData(), + tap((response: RemoteData) => { + if (response.isSuccess) { + this.requestService.setStaleByHrefSubstring(researcherProfile._links.self.href); + } + }), + map((response: RemoteData) => response.isSuccess) + ); + } + + /** + * Find the item id related to the given researcher profile. + * + * @param researcherProfile the profile to find for + */ + public findRelatedItemId(researcherProfile: ResearcherProfile): Observable { + return this.itemService.findByHref(researcherProfile._links.item.href, false) + .pipe(getFirstSucceededRemoteDataPayload(), + catchError((error) => { + console.debug(error); + return observableOf(null); }), - map((response: RemoteData) => response.isSuccess) + map((item) => item?.id) ); - } + } - /** - * Find the item id related to the given researcher profile. - * - * @param researcherProfile the profile to find for - */ - findRelatedItemId( researcherProfile: ResearcherProfile ): Observable { - return this.itemService.findByHref(researcherProfile._links.item.href, false) - .pipe (getFirstSucceededRemoteDataPayload(), - catchError((error) => { - console.debug(error); - return observableOf(null); - }), - map((item) => item != null ? item.id : null )); - } + /** + * Change the visibility of the given researcher profile setting the given value. + * + * @param researcherProfile the profile to update + * @param visible the visibility value to set + */ + public setVisibility(researcherProfile: ResearcherProfile, visible: boolean): Observable { - /** - * Change the visibility of the given researcher profile setting the given value. - * - * @param researcherProfile the profile to update - * @param visible the visibility value to set - */ - setVisibility(researcherProfile: ResearcherProfile, visible: boolean): Observable { + const replaceOperation: ReplaceOperation = { + path: '/visible', + op: 'replace', + value: visible + }; - const replaceOperation: ReplaceOperation = { - path: '/visible', - op: 'replace', - value: visible - }; - - return this.patch(researcherProfile, [replaceOperation]).pipe ( - switchMap( ( ) => this.findById(researcherProfile.id)) - ); - } - - patch(researcherProfile: ResearcherProfile, operations: Operation[]): Observable> { - return this.dataService.patch(researcherProfile, operations); - } + return this.patch(researcherProfile, [replaceOperation]).pipe( + switchMap(() => this.findById(researcherProfile.id)), + getFirstSucceededRemoteDataPayload() + ); + } /** * Creates a researcher profile starting from an external source URI @@ -179,4 +180,7 @@ export class ResearcherProfileService { return this.rdbService.buildFromRequestUUID(requestId); } + private patch(researcherProfile: ResearcherProfile, operations: Operation[]): Observable> { + return this.dataService.patch(researcherProfile, operations); + } } diff --git a/src/app/profile-page/profile-claim/profile-claim.service.spec.ts b/src/app/profile-page/profile-claim/profile-claim.service.spec.ts new file mode 100644 index 0000000000..4030c7900c --- /dev/null +++ b/src/app/profile-page/profile-claim/profile-claim.service.spec.ts @@ -0,0 +1,215 @@ +import { cold, getTestScheduler } from 'jasmine-marbles'; + +import { of as observableOf } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; + +import { ProfileClaimService } from './profile-claim.service'; +import { SearchService } from '../../core/shared/search/search.service'; +import { ItemSearchResult } from '../../shared/object-collection/shared/item-search-result.model'; +import { SearchObjects } from '../../shared/search/models/search-objects.model'; +import { Item } from '../../core/shared/item.model'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { EPerson } from '../../core/eperson/models/eperson.model'; + +describe('ProfileClaimService', () => { + let scheduler: TestScheduler; + let service: ProfileClaimService; + let serviceAsAny: any; + let searchService: jasmine.SpyObj; + + const eperson: EPerson = Object.assign(new EPerson(), { + id: 'id', + metadata: { + 'eperson.firstname': [ + { + value: 'John' + } + ], + 'eperson.lastname': [ + { + value: 'Doe' + }, + ], + }, + email: 'fake@email.com' + }); + const item1: Item = Object.assign(new Item(), { + uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e', + metadata: { + 'person.email': [ + { + value: 'fake@email.com' + } + ], + 'person.familyName': [ + { + value: 'Doe' + } + ], + 'person.givenName': [ + { + value: 'John' + } + ] + }, + _links: { + self: { + href: 'item-href' + } + } + }); + const item2: Item = Object.assign(new Item(), { + uuid: 'c8279647-1acc-41ae-b036-951d5f65649b', + metadata: { + 'person.email': [ + { + value: 'fake2@email.com' + } + ], + 'dc.title': [ + { + value: 'John, Doe' + } + ] + }, + _links: { + self: { + href: 'item-href' + } + } + }); + const item3: Item = Object.assign(new Item(), { + uuid: 'c8279647-1acc-41ae-b036-951d5f65649b', + metadata: { + 'person.email': [ + { + value: 'fake3@email.com' + } + ], + 'dc.title': [ + { + value: 'John, Doe' + } + ] + }, + _links: { + self: { + href: 'item-href' + } + } + }); + + const searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 }); + const searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 }); + const searchResult3 = Object.assign(new ItemSearchResult(), { indexableObject: item3 }); + + const searchResult = Object.assign(new SearchObjects(), { + page: [searchResult1, searchResult2, searchResult3] + }); + const emptySearchResult = Object.assign(new SearchObjects(), { + page: [] + }); + const searchResultRD = createSuccessfulRemoteDataObject(searchResult); + const emptyRearchResultRD = createSuccessfulRemoteDataObject(emptySearchResult); + + beforeEach(() => { + scheduler = getTestScheduler(); + + searchService = jasmine.createSpyObj('SearchService', { + search: jasmine.createSpy('search') + }); + + service = new ProfileClaimService(searchService); + serviceAsAny = service; + }); + + describe('hasProfilesToSuggest', () => { + + describe('when has suggestions', () => { + beforeEach(() => { + spyOn(service, 'search').and.returnValue(observableOf(searchResultRD)); + }); + + it('should return true', () => { + const result = service.hasProfilesToSuggest(eperson); + const expected = cold('(a|)', { + a: true + }); + expect(result).toBeObservable(expected); + }); + + }); + + describe('when has not suggestions', () => { + beforeEach(() => { + spyOn(service, 'search').and.returnValue(observableOf(emptyRearchResultRD)); + }); + + it('should return false', () => { + const result = service.hasProfilesToSuggest(eperson); + const expected = cold('(a|)', { + a: false + }); + expect(result).toBeObservable(expected); + }); + + }); + + describe('when has not valid eperson', () => { + it('should return false', () => { + const result = service.hasProfilesToSuggest(null); + const expected = cold('(a|)', { + a: false + }); + expect(result).toBeObservable(expected); + }); + + }); + + }); + + describe('search', () => { + + describe('when has search results', () => { + beforeEach(() => { + searchService.search.and.returnValue(observableOf(searchResultRD)); + }); + + it('should return the proper search object', () => { + const result = service.search(eperson); + const expected = cold('(a|)', { + a: searchResultRD + }); + expect(result).toBeObservable(expected); + }); + + }); + + describe('when has not suggestions', () => { + beforeEach(() => { + searchService.search.and.returnValue(observableOf(emptyRearchResultRD)); + }); + + it('should return null', () => { + const result = service.search(eperson); + const expected = cold('(a|)', { + a: emptyRearchResultRD + }); + expect(result).toBeObservable(expected); + }); + + }); + + describe('when has not valid eperson', () => { + it('should return null', () => { + const result = service.search(null); + const expected = cold('(a|)', { + a: null + }); + expect(result).toBeObservable(expected); + }); + + }); + + }); +}); diff --git a/src/app/profile-page/profile-claim/profile-claim.service.ts b/src/app/profile-page/profile-claim/profile-claim.service.ts index 9ee2462778..a61404540b 100644 --- a/src/app/profile-page/profile-claim/profile-claim.service.ts +++ b/src/app/profile-page/profile-claim/profile-claim.service.ts @@ -1,16 +1,16 @@ import { Injectable } from '@angular/core'; + import { Observable, of } from 'rxjs'; -import { mergeMap, take } from 'rxjs/operators'; -import { ConfigurationDataService } from '../../core/data/configuration-data.service'; -import { PaginatedList } from '../../core/data/paginated-list.model'; +import { map } from 'rxjs/operators'; + import { RemoteData } from '../../core/data/remote-data'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { SearchService } from '../../core/shared/search/search.service'; -import { hasValue } from '../../shared/empty.util'; +import { isEmpty, isNotEmpty } from '../../shared/empty.util'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; -import { SearchResult } from '../../shared/search/models/search-result.model'; -import { getFirstSucceededRemoteData } from './../../core/shared/operators'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { SearchObjects } from '../../shared/search/models/search-objects.model'; /** * Service that handle profiles claim. @@ -18,8 +18,7 @@ import { getFirstSucceededRemoteData } from './../../core/shared/operators'; @Injectable() export class ProfileClaimService { - constructor(private searchService: SearchService, - private configurationService: ConfigurationDataService) { + constructor(private searchService: SearchService) { } /** @@ -27,27 +26,21 @@ export class ProfileClaimService { * * @param eperson the eperson */ - canClaimProfiles(eperson: EPerson): Observable { - - const query = this.personQueryData(eperson); - - if (!hasValue(query) || query.length === 0) { - return of(false); - } - - return this.lookup(query).pipe( - mergeMap((rd: RemoteData>>) => of(rd.payload.totalElements > 0)) + hasProfilesToSuggest(eperson: EPerson): Observable { + return this.search(eperson).pipe( + map((rd: RemoteData>) => { + return isNotEmpty(rd) && rd.hasSucceeded && rd.payload?.page?.length > 0; + }) ); - } /** * Returns profiles that could be associated with the given user. * @param eperson the user */ - search(eperson: EPerson): Observable>>> { + search(eperson: EPerson): Observable>> { const query = this.personQueryData(eperson); - if (!hasValue(query) || query.length === 0) { + if (isEmpty(query)) { return of(null); } return this.lookup(query); @@ -57,21 +50,31 @@ export class ProfileClaimService { * Search object by the given query. * @param query the query for the search */ - private lookup(query: string): Observable>>> { - if (!hasValue(query)) { + private lookup(query: string): Observable>> { + if (isEmpty(query)) { return of(null); } return this.searchService.search(new PaginatedSearchOptions({ configuration: 'eperson_claims', query: query - })) - .pipe( - getFirstSucceededRemoteData(), - take(1)); + })).pipe( + getFirstCompletedRemoteData() + ); } + /** + * Return the search query for person lookup, from the given eperson + * + * @param eperson The eperson to use for the lookup + */ private personQueryData(eperson: EPerson): string { - return 'dc.title:' + eperson.name; + if (eperson) { + const firstname = eperson.firstMetadataValue('eperson.firstname'); + const lastname = eperson.firstMetadataValue('eperson.lastname'); + return 'dc.title:' + eperson.name + ' OR (person.familyName:' + lastname + ' AND person.givenName:' + firstname + ')'; + } else { + return null; + } } } diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts index d12c445ce4..b928b20eef 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts @@ -16,6 +16,7 @@ import { ProfilePageResearcherFormComponent } from './profile-page-researcher-fo import { ProfileClaimService } from '../profile-claim/profile-claim.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AuthService } from 'src/app/core/auth/auth.service'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; describe('ProfilePageResearcherFormComponent', () => { @@ -51,7 +52,7 @@ describe('ProfilePageResearcherFormComponent', () => { }); researcherProfileService = jasmine.createSpyObj('researcherProfileService', { - findById: observableOf(profile), + findById: createSuccessfulRemoteDataObject$(profile), create: observableOf(profile), setVisibility: observableOf(profile), delete: observableOf(true), @@ -61,7 +62,7 @@ describe('ProfilePageResearcherFormComponent', () => { notificationsServiceStub = new NotificationsServiceStub(); profileClaimService = jasmine.createSpyObj('profileClaimService', { - canClaimProfiles: observableOf(false), + hasProfilesToSuggest: observableOf(false), }); } @@ -91,7 +92,7 @@ describe('ProfilePageResearcherFormComponent', () => { }); it('should search the researcher profile for the current user', () => { - expect(researcherProfileService.findById).toHaveBeenCalledWith(user.id); + expect(researcherProfileService.findById).toHaveBeenCalledWith(user.id, false); }); describe('createProfile', () => { diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts index 9bb3028ff4..a1887f8b31 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts @@ -4,17 +4,18 @@ import { Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, Observable } from 'rxjs'; -import { filter, mergeMap, switchMap, take, tap } from 'rxjs/operators'; +import { mergeMap, switchMap, take, tap } from 'rxjs/operators'; -import { getFirstCompletedRemoteData } from '../../core/shared/operators'; -import { ClaimItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { + ClaimItemSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { AuthService } from '../../core/auth/auth.service'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model'; import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; import { ProfileClaimService } from '../profile-claim/profile-claim.service'; -import { isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-profile-page-researcher-form', @@ -77,10 +78,10 @@ export class ProfilePageResearcherFormComponent implements OnInit { this.processingCreate$.next(true); this.authService.getAuthenticatedUserFromStore().pipe( - switchMap((currentUser) => this.profileClaimService.canClaimProfiles(currentUser))) - .subscribe((canClaimProfiles) => { + switchMap((currentUser) => this.profileClaimService.hasProfilesToSuggest(currentUser))) + .subscribe((hasProfilesToSuggest) => { - if (canClaimProfiles) { + if (hasProfilesToSuggest) { this.processingCreate$.next(false); const modal = this.modalService.open(ClaimItemSelectorComponent); modal.componentInstance.dso = this.user; @@ -174,9 +175,8 @@ export class ProfilePageResearcherFormComponent implements OnInit { * Initializes the researcherProfile and researcherProfileItemId attributes using the profile of the current user. */ private initResearchProfile(): void { - this.researcherProfileService.findById(this.user.id).pipe( - take(1), - filter((researcherProfile) => isNotEmpty(researcherProfile)), + this.researcherProfileService.findById(this.user.id, false).pipe( + getFirstSucceededRemoteDataPayload(), tap((researcherProfile) => this.researcherProfile$.next(researcherProfile)), mergeMap((researcherProfile) => this.researcherProfileService.findRelatedItemId(researcherProfile)), ).subscribe((itemId: string) => { diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts index 84aec0c0f1..fcbd4d9e4a 100644 --- a/src/app/profile-page/profile-page.component.spec.ts +++ b/src/app/profile-page/profile-page.component.spec.ts @@ -11,17 +11,17 @@ import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model'; import { EPersonDataService } from '../core/eperson/eperson-data.service'; import { NotificationsService } from '../shared/notifications/notifications.service'; import { authReducer } from '../core/auth/auth.reducer'; -import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { createPaginatedList } from '../shared/testing/utils.test'; import { BehaviorSubject, of as observableOf } from 'rxjs'; import { AuthService } from '../core/auth/auth.service'; import { RestResponse } from '../core/cache/response.models'; import { provideMockStore } from '@ngrx/store/testing'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; -import { getTestScheduler } from 'jasmine-marbles'; +import { cold, getTestScheduler } from 'jasmine-marbles'; import { By } from '@angular/platform-browser'; -import {ConfigurationDataService} from '../core/data/configuration-data.service'; -import {ConfigurationProperty} from '../core/shared/configuration-property.model'; +import { ConfigurationDataService } from '../core/data/configuration-data.service'; +import { ConfigurationProperty } from '../core/shared/configuration-property.model'; describe('ProfilePageComponent', () => { let component: ProfilePageComponent; @@ -30,16 +30,28 @@ describe('ProfilePageComponent', () => { let initialState: any; let authService; + let authorizationService; let epersonService; let notificationsService; + let configurationService; const canChangePassword = new BehaviorSubject(true); + const validConfiguration = Object.assign(new ConfigurationProperty(), { + name: 'researcher-profile.entity-type', + values: [ + 'Person' + ] + }); + const emptyConfiguration = Object.assign(new ConfigurationProperty(), { + name: 'researcher-profile.entity-type', + values: [] + }); function init() { user = Object.assign(new EPerson(), { id: 'userId', groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), - _links: {self: {href: 'test.com/uuid/1234567654321'}} + _links: { self: { href: 'test.com/uuid/1234567654321' } } }); initialState = { core: { @@ -54,7 +66,7 @@ describe('ProfilePageComponent', () => { } } }; - + authorizationService = jasmine.createSpyObj('authorizationService', { isAuthorized: canChangePassword }); authService = jasmine.createSpyObj('authService', { getAuthenticatedUserFromStore: observableOf(user) }); @@ -67,6 +79,9 @@ describe('ProfilePageComponent', () => { error: {}, warning: {} }); + configurationService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: jasmine.createSpy('findByPropertyName') + }); } beforeEach(waitForAsync(() => { @@ -82,15 +97,8 @@ describe('ProfilePageComponent', () => { { provide: EPersonDataService, useValue: epersonService }, { provide: NotificationsService, useValue: notificationsService }, { provide: AuthService, useValue: authService }, - { provide: ConfigurationDataService, useValue: jasmine.createSpyObj('configurationDataService', { - findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { - name: 'researcher-profile.entity-type', - values: [ - 'Person' - ] - })) - })}, - { provide: AuthorizationDataService, useValue: jasmine.createSpyObj('authorizationService', { isAuthorized: canChangePassword }) }, + { provide: ConfigurationDataService, useValue: configurationService }, + { provide: AuthorizationDataService, useValue: authorizationService }, provideMockStore({ initialState }), ], schemas: [NO_ERRORS_SCHEMA] @@ -100,148 +108,206 @@ describe('ProfilePageComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ProfilePageComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); - describe('updateProfile', () => { - describe('when the metadata form returns false and the security form returns true', () => { - beforeEach(() => { - component.metadataForm = jasmine.createSpyObj('metadataForm', { - updateProfile: false + describe('', () => { + + beforeEach(() => { + configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(validConfiguration)); + fixture.detectChanges(); + }); + + describe('updateProfile', () => { + describe('when the metadata form returns false and the security form returns true', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: false + }); + spyOn(component, 'updateSecurity').and.returnValue(true); + component.updateProfile(); + }); + + it('should not display a warning', () => { + expect(notificationsService.warning).not.toHaveBeenCalled(); }); - spyOn(component, 'updateSecurity').and.returnValue(true); - component.updateProfile(); }); - it('should not display a warning', () => { - expect(notificationsService.warning).not.toHaveBeenCalled(); + describe('when the metadata form returns true and the security form returns false', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: true + }); + component.updateProfile(); + }); + + it('should not display a warning', () => { + expect(notificationsService.warning).not.toHaveBeenCalled(); + }); + }); + + describe('when the metadata form returns true and the security form returns true', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: true + }); + component.updateProfile(); + }); + + it('should not display a warning', () => { + expect(notificationsService.warning).not.toHaveBeenCalled(); + }); + }); + + describe('when the metadata form returns false and the security form returns false', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: false + }); + component.updateProfile(); + }); + + it('should display a warning', () => { + expect(notificationsService.warning).toHaveBeenCalled(); + }); }); }); - describe('when the metadata form returns true and the security form returns false', () => { - beforeEach(() => { - component.metadataForm = jasmine.createSpyObj('metadataForm', { - updateProfile: true + describe('updateSecurity', () => { + describe('when no password value present', () => { + let result; + + beforeEach(() => { + component.setPasswordValue(''); + + result = component.updateSecurity(); + }); + + it('should return false', () => { + expect(result).toEqual(false); + }); + + it('should not call epersonService.patch', () => { + expect(epersonService.patch).not.toHaveBeenCalled(); }); - component.updateProfile(); }); - it('should not display a warning', () => { - expect(notificationsService.warning).not.toHaveBeenCalled(); + describe('when password is filled in, but the password is invalid', () => { + let result; + + beforeEach(() => { + component.setPasswordValue('test'); + component.setInvalid(true); + result = component.updateSecurity(); + }); + + it('should return true', () => { + expect(result).toEqual(true); + expect(epersonService.patch).not.toHaveBeenCalled(); + }); + }); + + describe('when password is filled in, and is valid', () => { + let result; + let operations; + + beforeEach(() => { + component.setPasswordValue('testest'); + component.setInvalid(false); + + operations = [{ op: 'add', path: '/password', value: 'testest' }]; + result = component.updateSecurity(); + }); + + it('should return true', () => { + expect(result).toEqual(true); + }); + + it('should return call epersonService.patch', () => { + expect(epersonService.patch).toHaveBeenCalledWith(user, operations); + }); }); }); - describe('when the metadata form returns true and the security form returns true', () => { - beforeEach(() => { - component.metadataForm = jasmine.createSpyObj('metadataForm', { - updateProfile: true + describe('canChangePassword$', () => { + describe('when the user is allowed to change their password', () => { + beforeEach(() => { + canChangePassword.next(true); }); - component.updateProfile(); - }); - it('should not display a warning', () => { - expect(notificationsService.warning).not.toHaveBeenCalled(); - }); - }); - - describe('when the metadata form returns false and the security form returns false', () => { - beforeEach(() => { - component.metadataForm = jasmine.createSpyObj('metadataForm', { - updateProfile: false + it('should contain true', () => { + getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: true }); + }); + + it('should show the security section on the page', () => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.security-section'))).not.toBeNull(); }); - component.updateProfile(); }); - it('should display a warning', () => { - expect(notificationsService.warning).toHaveBeenCalled(); + describe('when the user is not allowed to change their password', () => { + beforeEach(() => { + canChangePassword.next(false); + }); + + it('should contain false', () => { + getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: false }); + }); + + it('should not show the security section on the page', () => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.security-section'))).toBeNull(); + }); }); }); }); - describe('updateSecurity', () => { - describe('when no password value present', () => { - let result; + describe('isResearcherProfileEnabled', () => { + + describe('when configuration service return values', () => { beforeEach(() => { - component.setPasswordValue(''); + configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(validConfiguration)); + fixture.detectChanges(); + }); - result = component.updateSecurity(); + it('should return true', () => { + const result = component.isResearcherProfileEnabled(); + const expected = cold('a', { + a: true + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('when configuration service return no values', () => { + + beforeEach(() => { + configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(emptyConfiguration)); + fixture.detectChanges(); }); it('should return false', () => { - expect(result).toEqual(false); - }); - - it('should not call epersonService.patch', () => { - expect(epersonService.patch).not.toHaveBeenCalled(); + const result = component.isResearcherProfileEnabled(); + const expected = cold('a', { + a: false + }); + expect(result).toBeObservable(expected); }); }); - describe('when password is filled in, but the password is invalid', () => { - let result; + describe('when configuration service return an error', () => { beforeEach(() => { - component.setPasswordValue('test'); - component.setInvalid(true); - result = component.updateSecurity(); - }); - - it('should return true', () => { - expect(result).toEqual(true); - expect(epersonService.patch).not.toHaveBeenCalled(); - }); - }); - - describe('when password is filled in, and is valid', () => { - let result; - let operations; - - beforeEach(() => { - component.setPasswordValue('testest'); - component.setInvalid(false); - - operations = [{ op: 'add', path: '/password', value: 'testest' }]; - result = component.updateSecurity(); - }); - - it('should return true', () => { - expect(result).toEqual(true); - }); - - it('should return call epersonService.patch', () => { - expect(epersonService.patch).toHaveBeenCalledWith(user, operations); - }); - }); - }); - - describe('canChangePassword$', () => { - describe('when the user is allowed to change their password', () => { - beforeEach(() => { - canChangePassword.next(true); - }); - - it('should contain true', () => { - getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: true }); - }); - - it('should show the security section on the page', () => { + configurationService.findByPropertyName.and.returnValue(createFailedRemoteDataObject$()); fixture.detectChanges(); - expect(fixture.debugElement.query(By.css('.security-section'))).not.toBeNull(); - }); - }); - - describe('when the user is not allowed to change their password', () => { - beforeEach(() => { - canChangePassword.next(false); }); - it('should contain false', () => { - getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: false }); - }); - - it('should not show the security section on the page', () => { - fixture.detectChanges(); - expect(fixture.debugElement.query(By.css('.security-section'))).toBeNull(); + it('should return false', () => { + const result = component.isResearcherProfileEnabled(); + const expected = cold('a', { + a: false + }); + expect(result).toBeObservable(expected); }); }); }); diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index 9c22c8c950..374fa5220b 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import {BehaviorSubject, Observable} from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { EPerson } from '../core/eperson/models/eperson.model'; import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form/profile-page-metadata-form.component'; import { NotificationsService } from '../shared/notifications/notifications.service'; @@ -9,18 +9,15 @@ import { RemoteData } from '../core/data/remote-data'; import { PaginatedList } from '../core/data/paginated-list.model'; import { filter, switchMap, tap } from 'rxjs/operators'; import { EPersonDataService } from '../core/eperson/eperson-data.service'; -import { - getAllSucceededRemoteData, - getRemoteDataPayload, - getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload -} from '../core/shared/operators'; +import { getAllSucceededRemoteData, getFirstCompletedRemoteData, getRemoteDataPayload } from '../core/shared/operators'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { followLink } from '../shared/utils/follow-link-config.model'; import { AuthService } from '../core/auth/auth.service'; import { Operation } from 'fast-json-patch'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../core/data/feature-authorization/feature-id'; -import {ConfigurationDataService} from '../core/data/configuration-data.service'; +import { ConfigurationDataService } from '../core/data/configuration-data.service'; +import { ConfigurationProperty } from '../core/shared/configuration-property.model'; @Component({ selector: 'ds-profile-page', @@ -94,8 +91,10 @@ export class ProfilePageComponent implements OnInit { this.canChangePassword$ = this.user$.pipe(switchMap((user: EPerson) => this.authorizationService.isAuthorized(FeatureID.CanChangePassword, user._links.self.href))); this.configurationService.findByPropertyName('researcher-profile.entity-type').pipe( - getFirstSucceededRemoteDataPayload() - ).subscribe(() => this.isResearcherProfileEnabled$.next(true)); + getFirstCompletedRemoteData() + ).subscribe((configRD: RemoteData) => { + this.isResearcherProfileEnabled$.next(configRD.hasSucceeded && configRD.payload.values.length > 0); + }); } /** @@ -175,8 +174,8 @@ export class ProfilePageComponent implements OnInit { /** * Returns true if the researcher profile feature is enabled, false otherwise. */ - isResearcherProfileEnabled(){ - return this.isResearcherProfileEnabled$; + isResearcherProfileEnabled(): Observable { + return this.isResearcherProfileEnabled$.asObservable(); } } diff --git a/yarn.lock b/yarn.lock index c06853e625..65c1a61aba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8884,11 +8884,6 @@ ngx-ui-switch@^11.0.1: resolved "https://registry.yarnpkg.com/ngx-ui-switch/-/ngx-ui-switch-11.0.1.tgz#c7f1e97ebe698f827a26f49951b50492b22c7839" integrity sha512-N8QYT/wW+xJdyh/aeebTSLPA6Sgrwp69H6KAcW0XZueg/LF+FKiqyG6Po/gFHq2gDhLikwyJEMpny8sudTI08w== -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - nice-napi@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b" From 1e9d393edf1e5ccb5c5849c1e4d9dfe4e8dfc5c6 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 11 May 2022 11:59:38 +0200 Subject: [PATCH 21/75] [CST-5307] Move profile-claim-item-modal.component to profile page module and add unit tests --- .../profile-claim-item-modal.component.html} | 4 +- ...profile-claim-item-modal.component.spec.ts | 223 ++++++++++++++++++ .../profile-claim-item-modal.component.ts | 105 +++++++++ src/app/profile-page/profile-page.module.ts | 6 +- .../claim-item-selector.component.spec.ts | 45 ---- .../claim-item-selector.component.ts | 69 ------ src/app/shared/shared.module.ts | 8 +- 7 files changed, 336 insertions(+), 124 deletions(-) rename src/app/{shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.html => profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html} (93%) create mode 100644 src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.spec.ts create mode 100644 src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.ts delete mode 100644 src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.spec.ts delete mode 100644 src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.ts diff --git a/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.html b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html similarity index 93% rename from src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.html rename to src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html index 9df49ba24b..eec9f437f1 100644 --- a/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.html +++ b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.html @@ -14,7 +14,7 @@
@@ -26,7 +26,7 @@

{{'researcher.profile.not.associated' | translate}}

-
- - + +
diff --git a/src/app/health-page/health-panel/health-component/health-component.component.html b/src/app/health-page/health-panel/health-component/health-component.component.html index 8171917767..4569d06dad 100644 --- a/src/app/health-page/health-panel/health-component/health-component.component.html +++ b/src/app/health-page/health-panel/health-component/health-component.component.html @@ -6,8 +6,8 @@ {{ entry.key | titlecase }}
- - + +
diff --git a/src/app/health-page/health-panel/health-panel.component.html b/src/app/health-page/health-panel/health-panel.component.html index d582fb77f3..eebcfe55ec 100644 --- a/src/app/health-page/health-panel/health-panel.component.html +++ b/src/app/health-page/health-panel/health-panel.component.html @@ -1,14 +1,14 @@

{{'health-page.status' | translate}} :

-
+
- - + +
From 80ff8a517ce95c16ab21b376d2e4b664dbe4b5d4 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 16 May 2022 16:46:33 +0200 Subject: [PATCH 26/75] [CST-5535] Rename health-data.service --- src/app/health-page/health-page.component.spec.ts | 4 ++-- src/app/health-page/health-page.component.ts | 4 ++-- .../health-page/{health-data.service.ts => health.service.ts} | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/app/health-page/{health-data.service.ts => health.service.ts} (97%) diff --git a/src/app/health-page/health-page.component.spec.ts b/src/app/health-page/health-page.component.spec.ts index 205af8036a..f3847ab092 100644 --- a/src/app/health-page/health-page.component.spec.ts +++ b/src/app/health-page/health-page.component.spec.ts @@ -7,7 +7,7 @@ import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { HealthPageComponent } from './health-page.component'; -import { HealthDataService } from './health-data.service'; +import { HealthService } from './health.service'; import { HealthInfoResponseObj, HealthResponseObj } from '../shared/mocks/health-endpoint.mocks'; import { RawRestResponse } from '../core/dspace-rest/raw-rest-response.model'; import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; @@ -47,7 +47,7 @@ describe('HealthPageComponent', () => { ], declarations: [ HealthPageComponent ], providers: [ - { provide: HealthDataService, useValue: healthService } + { provide: HealthService, useValue: healthService } ] }) .compileComponents(); diff --git a/src/app/health-page/health-page.component.ts b/src/app/health-page/health-page.component.ts index e4f4be7a03..eb07b63add 100644 --- a/src/app/health-page/health-page.component.ts +++ b/src/app/health-page/health-page.component.ts @@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { take } from 'rxjs/operators'; -import { HealthDataService } from './health-data.service'; +import { HealthService } from './health.service'; import { HealthInfoResponse, HealthResponse } from './models/health-component.model'; @Component({ @@ -23,7 +23,7 @@ export class HealthPageComponent implements OnInit { */ healthResponse: BehaviorSubject = new BehaviorSubject(null); - constructor(private healthDataService: HealthDataService) { + constructor(private healthDataService: HealthService) { } /** diff --git a/src/app/health-page/health-data.service.ts b/src/app/health-page/health.service.ts similarity index 97% rename from src/app/health-page/health-data.service.ts rename to src/app/health-page/health.service.ts index bd905006aa..7c238769a1 100644 --- a/src/app/health-page/health-data.service.ts +++ b/src/app/health-page/health.service.ts @@ -8,7 +8,7 @@ import { HALEndpointService } from '../core/shared/hal-endpoint.service'; @Injectable({ providedIn: 'root' }) -export class HealthDataService { +export class HealthService { constructor(protected halService: HALEndpointService, protected restService: DspaceRestService) { } From 4f7e37d348dfeccca1cc7e1f8b7efeb526b11207 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 16 May 2022 18:17:35 +0200 Subject: [PATCH 27/75] [CST-5535] Use accordion for health status components --- .../health-info-component.component.html | 38 ++++++++++++++--- .../health-info-component.component.spec.ts | 4 +- .../health-info/health-info.component.html | 30 ++++++++++++- .../health-info/health-info.component.spec.ts | 8 +++- .../health-info/health-info.component.ts | 12 +++++- .../health-component.component.html | 3 +- .../health-panel/health-panel.component.html | 42 ++++++++++--------- .../health-panel.component.spec.ts | 9 ++-- .../health-panel/health-panel.component.ts | 11 +++-- src/app/shared/mocks/health-endpoint.mocks.ts | 24 ++++++++++- 10 files changed, 139 insertions(+), 42 deletions(-) diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.html b/src/app/health-page/health-info/health-info-component/health-info-component.component.html index 8ce7595980..b16e88564f 100644 --- a/src/app/health-page/health-info/health-info-component/health-info-component.component.html +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.html @@ -1,4 +1,34 @@ - +
+
+
+ +
+ + +
+
+
+
+
+ +
+
+
+
+ +

{{ entry.key | titlecase }} : {{entry.value}}

+
+
+ + + + diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts b/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts index 2297007cd5..437d53a953 100644 --- a/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts @@ -46,7 +46,9 @@ describe('HealthInfoComponentComponent', () => { }); it('should display property', () => { - const components = fixture.debugElement.queryAll(By.css('[data-test="component"]')); + const properties = fixture.debugElement.queryAll(By.css('[data-test="property"]')); + expect(properties.length).toBe(14); + const components = fixture.debugElement.queryAll(By.css('[data-test="info-component"]')); expect(components.length).toBe(4); }); diff --git a/src/app/health-page/health-info/health-info.component.html b/src/app/health-page/health-info/health-info.component.html index e4d29adf54..be69df23b4 100644 --- a/src/app/health-page/health-info/health-info.component.html +++ b/src/app/health-page/health-info/health-info.component.html @@ -1,7 +1,33 @@ -
+ + + +
+ +
+ +
+ + +
+
+
+
+ + + +
+
+ + + + diff --git a/src/app/health-page/health-info/health-info.component.spec.ts b/src/app/health-page/health-info/health-info.component.spec.ts index 3af1d71db3..a7f319b88b 100644 --- a/src/app/health-page/health-info/health-info.component.spec.ts +++ b/src/app/health-page/health-info/health-info.component.spec.ts @@ -4,6 +4,8 @@ import { HealthInfoComponent } from './health-info.component'; import { HealthInfoResponseObj } from '../../shared/mocks/health-endpoint.mocks'; import { ObjNgFor } from '../../shared/utils/object-ngfor.pipe'; import { By } from '@angular/platform-browser'; +import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; describe('HealthInfoComponent', () => { let component: HealthInfoComponent; @@ -11,10 +13,14 @@ describe('HealthInfoComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ + imports: [ + NgbAccordionModule, + ], declarations: [ HealthInfoComponent, ObjNgFor - ] + ], + schemas: [NO_ERRORS_SCHEMA] }) .compileComponents(); }); diff --git a/src/app/health-page/health-info/health-info.component.ts b/src/app/health-page/health-info/health-info.component.ts index a5fb0b282b..9fddaeb7e4 100644 --- a/src/app/health-page/health-info/health-info.component.ts +++ b/src/app/health-page/health-info/health-info.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { HealthInfoResponse } from '../models/health-component.model'; @@ -7,8 +7,16 @@ import { HealthInfoResponse } from '../models/health-component.model'; templateUrl: './health-info.component.html', styleUrls: ['./health-info.component.scss'] }) -export class HealthInfoComponent { +export class HealthInfoComponent implements OnInit { @Input() healthInfoResponse: HealthInfoResponse; + /** + * The first active panel id + */ + activeId: string; + + ngOnInit(): void { + this.activeId = Object.keys(this.healthInfoResponse)[0]; + } } diff --git a/src/app/health-page/health-panel/health-component/health-component.component.html b/src/app/health-page/health-panel/health-component/health-component.component.html index 4569d06dad..7089fe25c6 100644 --- a/src/app/health-page/health-panel/health-component/health-component.component.html +++ b/src/app/health-page/health-panel/health-component/health-component.component.html @@ -22,6 +22,7 @@
- {{ item.key | titlecase }} : {{item.value}} +

{{ item.key | titlecase }} : {{item.value}}

+
diff --git a/src/app/health-page/health-panel/health-panel.component.html b/src/app/health-page/health-panel/health-panel.component.html index eebcfe55ec..d47a73d820 100644 --- a/src/app/health-page/health-panel/health-panel.component.html +++ b/src/app/health-page/health-panel/health-panel.component.html @@ -1,21 +1,25 @@

{{'health-page.status' | translate}} :

-
-
- -
- - - -
-
-
-
-
- + + + +
+ +
+ +
+ + +
+
-
-
-
+ + + + + + + + diff --git a/src/app/health-page/health-panel/health-panel.component.spec.ts b/src/app/health-page/health-panel/health-panel.component.spec.ts index da392f7ba8..1d9c856ddb 100644 --- a/src/app/health-page/health-panel/health-panel.component.spec.ts +++ b/src/app/health-page/health-panel/health-panel.component.spec.ts @@ -5,14 +5,14 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { NgbCollapseModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbAccordionModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; import { HealthPanelComponent } from './health-panel.component'; import { HealthResponseObj } from '../../shared/mocks/health-endpoint.mocks'; import { ObjNgFor } from '../../shared/utils/object-ngfor.pipe'; -describe('HealthComponent', () => { +describe('HealthPanelComponent', () => { let component: HealthPanelComponent; let fixture: ComponentFixture; @@ -20,7 +20,7 @@ describe('HealthComponent', () => { await TestBed.configureTestingModule({ imports: [ NgbNavModule, - NgbCollapseModule, + NgbAccordionModule, CommonModule, BrowserAnimationsModule, TranslateModule.forRoot({ @@ -42,7 +42,6 @@ describe('HealthComponent', () => { fixture = TestBed.createComponent(HealthPanelComponent); component = fixture.componentInstance; component.healthResponse = HealthResponseObj; - component.isCollapsed = false; fixture.detectChanges(); }); @@ -50,7 +49,7 @@ describe('HealthComponent', () => { expect(component).toBeTruthy(); }); - it('should render a card for each component', () => { + it('should render a panel for each component', () => { const components = fixture.debugElement.queryAll(By.css('[data-test="component"]')); expect(components.length).toBe(5); }); diff --git a/src/app/health-page/health-panel/health-panel.component.ts b/src/app/health-page/health-panel/health-panel.component.ts index 549544c370..8bb670e67f 100644 --- a/src/app/health-page/health-panel/health-panel.component.ts +++ b/src/app/health-page/health-panel/health-panel.component.ts @@ -1,4 +1,4 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { HealthResponse } from '../models/health-component.model'; @Component({ @@ -6,7 +6,7 @@ import { HealthResponse } from '../models/health-component.model'; templateUrl: './health-panel.component.html', styleUrls: ['./health-panel.component.scss'] }) -export class HealthPanelComponent { +export class HealthPanelComponent implements OnInit { /** * Health endpoint response @@ -14,8 +14,11 @@ export class HealthPanelComponent { @Input() healthResponse: HealthResponse; /** - * A boolean representing if div should start collapsed + * The first active panel id */ - public isCollapsed = true; + activeId: string; + ngOnInit(): void { + this.activeId = Object.keys(this.healthResponse.components)[0]; + } } diff --git a/src/app/shared/mocks/health-endpoint.mocks.ts b/src/app/shared/mocks/health-endpoint.mocks.ts index 9bd3956139..a9246d91a1 100644 --- a/src/app/shared/mocks/health-endpoint.mocks.ts +++ b/src/app/shared/mocks/health-endpoint.mocks.ts @@ -134,7 +134,27 @@ export const HealthInfoComponentOne: HealthInfoComponent = { 'name': 'DSpace at My University', 'dir': '/home/giuseppe/development/java/install/dspace7-review', 'url': 'http://localhost:8080/server', - 'db': 'jdbc:postgresql://localhost:5432/dspace7' + 'db': 'jdbc:postgresql://localhost:5432/dspace7', + 'solr': { + 'server': 'http://localhost:8983/solr', + 'prefix': '' + }, + 'mail': { + 'server': 'smtp.example.com', + 'from-address': 'dspace-noreply@myu.edu', + 'feedback-recipient': 'dspace-help@myu.edu', + 'mail-admin': 'dspace-help@myu.edu', + 'mail-helpdesk': 'dspace-help@myu.edu', + 'alert-recipient': 'dspace-help@myu.edu' + }, + 'cors': { + 'allowed-origins': 'http://localhost:4000' + }, + 'ui': { + 'url': 'http://localhost:4000' + } }; -export const HealthInfoComponentTwo = '7.3-SNAPSHOT'; +export const HealthInfoComponentTwo = { + 'version': '7.3-SNAPSHOT' +}; From c508e0035e46c977bb7b0fad2163eff5109c3d81 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 16 May 2022 18:49:11 +0200 Subject: [PATCH 28/75] [CST-5535] Add tooltip for status icons --- .../health-status/health-status.component.html | 12 +++++++++--- src/assets/i18n/en.json5 | 8 ++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/app/health-page/health-panel/health-status/health-status.component.html b/src/app/health-page/health-panel/health-status/health-status.component.html index fdd726cddf..38a6f72601 100644 --- a/src/app/health-page/health-panel/health-status/health-status.component.html +++ b/src/app/health-page/health-panel/health-status/health-status.component.html @@ -1,6 +1,12 @@ - - - + + + diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c129187023..78661ced76 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1567,6 +1567,14 @@ + "health-page.status.ok.info": "Operational", + + "health-page.status.error.info": "Problems detected", + + "health-page.status.warning.info": "Possible issues detected", + + + "home.description": "", "home.breadcrumbs": "Home", From 73573793a0eacc52789c4df6b7a653040393f407 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 16 May 2022 18:50:07 +0200 Subject: [PATCH 29/75] [CST-5535] Add i18n keys for section's headers --- .../health-info/health-info.component.html | 2 +- .../health-panel/health-panel.component.html | 2 +- src/assets/i18n/en.json5 | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/app/health-page/health-info/health-info.component.html b/src/app/health-page/health-info/health-info.component.html index be69df23b4..12764ead45 100644 --- a/src/app/health-page/health-info/health-info.component.html +++ b/src/app/health-page/health-info/health-info.component.html @@ -5,7 +5,7 @@
diff --git a/src/app/health-page/health-panel/health-panel.component.html b/src/app/health-page/health-panel/health-panel.component.html index d47a73d820..d095ce2f6d 100644 --- a/src/app/health-page/health-panel/health-panel.component.html +++ b/src/app/health-page/health-panel/health-panel.component.html @@ -5,7 +5,7 @@
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 78661ced76..c22ec1c3ee 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1566,6 +1566,21 @@ "grant-request-copy.success": "Successfully granted item request", + "health-page.section.db.title": "Database", + + "health-page.section.geoIp.title": "GeoIp", + + "health-page.section.solrAuthorityCore.title": "Sor: authority core", + + "health-page.section.solrOaiCore.title": "Sor: oai core", + + "health-page.section.solrSearchCore.title": "Sor: search core", + + "health-page.section.solrStatisticsCore.title": "Sor: statistics core", + + "health-page.section-info.app.title": "Application Backend", + + "health-page.section-info.java.title": "Java", "health-page.status.ok.info": "Operational", From 74b68a5e154f1285ad3e7ec9d13fcb30590378d4 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 16 May 2022 19:19:00 +0200 Subject: [PATCH 30/75] [CST-5535] Add fallback message on rest request error --- .../health-page/health-page.component.html | 45 ++++++++++--------- src/app/health-page/health-page.component.ts | 11 +++-- src/assets/i18n/en.json5 | 2 + 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/app/health-page/health-page.component.html b/src/app/health-page/health-page.component.html index 6ec9abddcb..0647620c73 100644 --- a/src/app/health-page/health-page.component.html +++ b/src/app/health-page/health-page.component.html @@ -1,21 +1,26 @@ -
- -
+
+ +
+ + diff --git a/src/app/health-page/health-page.component.ts b/src/app/health-page/health-page.component.ts index eb07b63add..a92e72744b 100644 --- a/src/app/health-page/health-page.component.ts +++ b/src/app/health-page/health-page.component.ts @@ -30,12 +30,15 @@ export class HealthPageComponent implements OnInit { * Retrieve responses from rest */ ngOnInit(): void { - this.healthDataService.getHealth().pipe(take(1)).subscribe((data: any) => { - this.healthResponse.next(data.payload); + this.healthDataService.getHealth().pipe(take(1)).subscribe({ + next: (data: any) => { this.healthResponse.next(data.payload); }, + error: () => { this.healthResponse.next(null); } }); - this.healthDataService.getInfo().pipe(take(1)).subscribe((data) => { - this.healthInfoResponse.next(data.payload); + this.healthDataService.getInfo().pipe(take(1)).subscribe({ + next: (data: any) => { this.healthInfoResponse.next(data.payload); }, + error: () => { this.healthInfoResponse.next(null); } }); + } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4fcdbff335..216b29fcd0 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1583,6 +1583,8 @@ "grant-request-copy.success": "Successfully granted item request", + "health-page.error.msg": "The health check service is temporarily unavailable", + "health-page.section.db.title": "Database", "health-page.section.geoIp.title": "GeoIp", From d4dc176870d46506ad72670ba391f9f041e1c495 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 18 May 2022 13:04:04 +0200 Subject: [PATCH 31/75] [CST-5535] Add Possibility to have translation also for properties --- .../health-info-component.component.html | 4 ++-- .../health-info-component.component.scss | 3 +++ .../health-info-component.component.spec.ts | 10 +++++++++- .../health-info-component.component.ts | 12 ++++++++++-- .../health-info/health-info.component.html | 4 ++-- .../health-info/health-info.component.scss | 3 +++ .../health-info/health-info.component.spec.ts | 8 ++++++++ .../health-info/health-info.component.ts | 15 +++++++++++++++ .../health-component.component.html | 4 ++-- .../health-component.component.scss | 3 +++ .../health-component.component.spec.ts | 10 +++++++++- .../health-component.component.ts | 17 ++++++++++++++++- .../health-panel/health-panel.component.html | 4 ++-- .../health-panel/health-panel.component.scss | 3 +++ .../health-panel/health-panel.component.ts | 16 ++++++++++++++++ .../health-status.component.spec.ts | 12 ++++++++++++ src/assets/i18n/en.json5 | 2 ++ 17 files changed, 117 insertions(+), 13 deletions(-) diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.html b/src/app/health-page/health-info/health-info-component/health-info-component.component.html index b16e88564f..b607d95f45 100644 --- a/src/app/health-page/health-info/health-info-component/health-info-component.component.html +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.html @@ -1,6 +1,6 @@
-
+
-

{{ entry.key | titlecase }} : {{entry.value}}

+

{{ getPropertyLabel(entry.key) | titlecase }} : {{entry.value}}

diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.scss b/src/app/health-page/health-info/health-info-component/health-info-component.component.scss index e69de29bb2..a6f0e73413 100644 --- a/src/app/health-page/health-info/health-info-component/health-info-component.component.scss +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.scss @@ -0,0 +1,3 @@ +.collapse-toggle { + cursor: pointer; +} diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts b/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts index 437d53a953..b4532415b8 100644 --- a/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.spec.ts @@ -8,6 +8,8 @@ import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'; import { HealthInfoComponentComponent } from './health-info-component.component'; import { HealthInfoComponentOne, HealthInfoComponentTwo } from '../../../shared/mocks/health-endpoint.mocks'; import { ObjNgFor } from '../../../shared/utils/object-ngfor.pipe'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; describe('HealthInfoComponentComponent', () => { let component: HealthInfoComponentComponent; @@ -18,7 +20,13 @@ describe('HealthInfoComponentComponent', () => { imports: [ CommonModule, NgbCollapseModule, - NoopAnimationsModule + NoopAnimationsModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) ], declarations: [ HealthInfoComponentComponent, diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.ts b/src/app/health-page/health-info/health-info-component/health-info-component.component.ts index b6c31214c8..159462cd6d 100644 --- a/src/app/health-page/health-info/health-info-component/health-info-component.component.ts +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.ts @@ -1,13 +1,14 @@ import { Component, Input } from '@angular/core'; import { HealthInfoComponent } from '../../models/health-component.model'; +import { HealthComponentComponent } from '../../health-panel/health-component/health-component.component'; @Component({ selector: 'ds-health-info-component', templateUrl: './health-info-component.component.html', styleUrls: ['./health-info-component.component.scss'] }) -export class HealthInfoComponentComponent { +export class HealthInfoComponentComponent extends HealthComponentComponent { /** * The HealthInfoComponent object to display @@ -27,9 +28,16 @@ export class HealthInfoComponentComponent { /** * A boolean representing if div should start collapsed */ - public isCollapsed = true; + public isCollapsed = false; + /** + * Check if the HealthInfoComponent is has only string property or contains object + * + * @param entry The HealthInfoComponent to check + * @return boolean + */ isPlainProperty(entry: HealthInfoComponent | string): boolean { return typeof entry === 'string'; } + } diff --git a/src/app/health-page/health-info/health-info.component.html b/src/app/health-page/health-info/health-info.component.html index 12764ead45..47e4cfb4d2 100644 --- a/src/app/health-page/health-info/health-info.component.html +++ b/src/app/health-page/health-info/health-info.component.html @@ -2,10 +2,10 @@ -
+
diff --git a/src/app/health-page/health-info/health-info.component.scss b/src/app/health-page/health-info/health-info.component.scss index e69de29bb2..a6f0e73413 100644 --- a/src/app/health-page/health-info/health-info.component.scss +++ b/src/app/health-page/health-info/health-info.component.scss @@ -0,0 +1,3 @@ +.collapse-toggle { + cursor: pointer; +} diff --git a/src/app/health-page/health-info/health-info.component.spec.ts b/src/app/health-page/health-info/health-info.component.spec.ts index a7f319b88b..5a9b8bf0aa 100644 --- a/src/app/health-page/health-info/health-info.component.spec.ts +++ b/src/app/health-page/health-info/health-info.component.spec.ts @@ -6,6 +6,8 @@ import { ObjNgFor } from '../../shared/utils/object-ngfor.pipe'; import { By } from '@angular/platform-browser'; import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap'; import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; describe('HealthInfoComponent', () => { let component: HealthInfoComponent; @@ -15,6 +17,12 @@ describe('HealthInfoComponent', () => { await TestBed.configureTestingModule({ imports: [ NgbAccordionModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) ], declarations: [ HealthInfoComponent, diff --git a/src/app/health-page/health-info/health-info.component.ts b/src/app/health-page/health-info/health-info.component.ts index 9fddaeb7e4..d8c629636b 100644 --- a/src/app/health-page/health-info/health-info.component.ts +++ b/src/app/health-page/health-info/health-info.component.ts @@ -1,6 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { HealthInfoResponse } from '../models/health-component.model'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'ds-health-info', @@ -16,7 +17,21 @@ export class HealthInfoComponent implements OnInit { */ activeId: string; + constructor(private translate: TranslateService) { + } + ngOnInit(): void { this.activeId = Object.keys(this.healthInfoResponse)[0]; } + /** + * Return translated label if exist for the given property + * + * @param property + */ + public getPanelLabel(panelKey: string): string { + const translationKey = `health-page.section-info.${panelKey}.title`; + const translation = this.translate.instant(translationKey); + + return (translation === translationKey) ? panelKey : translation; + } } diff --git a/src/app/health-page/health-panel/health-component/health-component.component.html b/src/app/health-page/health-panel/health-component/health-component.component.html index 7089fe25c6..c254f128d9 100644 --- a/src/app/health-page/health-panel/health-component/health-component.component.html +++ b/src/app/health-page/health-panel/health-component/health-component.component.html @@ -1,6 +1,6 @@
-
+
diff --git a/src/app/health-page/health-panel/health-panel.component.scss b/src/app/health-page/health-panel/health-panel.component.scss index e69de29bb2..a6f0e73413 100644 --- a/src/app/health-page/health-panel/health-panel.component.scss +++ b/src/app/health-page/health-panel/health-panel.component.scss @@ -0,0 +1,3 @@ +.collapse-toggle { + cursor: pointer; +} diff --git a/src/app/health-page/health-panel/health-panel.component.ts b/src/app/health-page/health-panel/health-panel.component.ts index 8bb670e67f..3137334d6f 100644 --- a/src/app/health-page/health-panel/health-panel.component.ts +++ b/src/app/health-page/health-panel/health-panel.component.ts @@ -1,5 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { HealthResponse } from '../models/health-component.model'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'ds-health-panel', @@ -18,7 +19,22 @@ export class HealthPanelComponent implements OnInit { */ activeId: string; + constructor(private translate: TranslateService) { + } + ngOnInit(): void { this.activeId = Object.keys(this.healthResponse.components)[0]; } + + /** + * Return translated label if exist for the given property + * + * @param property + */ + public getPanelLabel(panelKey: string): string { + const translationKey = `health-page.section.${panelKey}.title`; + const translation = this.translate.instant(translationKey); + + return (translation === translationKey) ? panelKey : translation; + } } diff --git a/src/app/health-page/health-panel/health-status/health-status.component.spec.ts b/src/app/health-page/health-panel/health-status/health-status.component.spec.ts index 13df9c23e3..f0f61ebdbb 100644 --- a/src/app/health-page/health-panel/health-status/health-status.component.spec.ts +++ b/src/app/health-page/health-panel/health-status/health-status.component.spec.ts @@ -3,6 +3,9 @@ import { By } from '@angular/platform-browser'; import { HealthStatusComponent } from './health-status.component'; import { HealthStatus } from '../../models/health-component.model'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; +import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; describe('HealthStatusComponent', () => { let component: HealthStatusComponent; @@ -10,6 +13,15 @@ describe('HealthStatusComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ + imports: [ + NgbTooltipModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) + ], declarations: [ HealthStatusComponent ] }) .compileComponents(); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 216b29fcd0..18a406b77b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1585,6 +1585,8 @@ "health-page.error.msg": "The health check service is temporarily unavailable", + "health-page.property.status": "Status code", + "health-page.section.db.title": "Database", "health-page.section.geoIp.title": "GeoIp", From ef332af17ea62afce19ef83763ac30b356e63289 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 18 May 2022 13:31:09 +0200 Subject: [PATCH 32/75] [CST-5535] Fix issue with error notice message on health page loading --- .../health-page/health-page.component.html | 2 +- src/app/health-page/health-page.component.ts | 30 ++++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/app/health-page/health-page.component.html b/src/app/health-page/health-page.component.html index 0647620c73..605927dc55 100644 --- a/src/app/health-page/health-page.component.html +++ b/src/app/health-page/health-page.component.html @@ -1,4 +1,4 @@ -
+
- +
@@ -59,4 +59,9 @@
+ + + + \ No newline at end of file diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts index 3861bd1217..e4bbebc617 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts @@ -1,3 +1,4 @@ +import { AlertType } from './../../../shared/alert/aletr-type'; import { Component, Inject, QueryList, ViewChildren } from '@angular/core'; import { BehaviorSubject, interval, Observable, of, Subscription } from 'rxjs'; @@ -49,6 +50,13 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon */ public isCollapsed = false; + + /** + * The AlertType enumeration + * @type {AlertType} + */ + public AlertTypeEnum = AlertType; + /** * Initialize instance variables * @@ -71,6 +79,7 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon * Unsubscribe from all subscriptions */ onSectionDestroy() { + this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); @@ -81,7 +90,6 @@ export class SubmissionSectionSherpaPoliciesComponent extends SectionModelCompon * Initialize all instance variables and retrieve collection default access conditions */ protected onSectionInit(): void { - this.pathCombiner = new JsonPatchOperationPathCombiner('sections', this.sectionData.id); this.subs.push( this.sectionService.getSectionData(this.submissionId, this.sectionData.id, this.sectionData.sectionType) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 9f5ba5c8b1..447af4dcf5 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4046,6 +4046,8 @@ "submission.sections.sherpa.record.information.uri": "URI", + "submission.sections.sherpa.error.message": "There was an error retrieving sherpa informations", + "submission.submit.breadcrumbs": "New submission", From 87d015371cc476ce93ff88aaff57f445e13d69a0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 24 May 2022 09:19:02 +0200 Subject: [PATCH 42/75] [CST-5270] use bootstrap classes --- .../sherpa-policies/section-sherpa-policies.component.html | 4 ++-- .../sherpa-policies/section-sherpa-policies.component.scss | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index c135e0941a..a7e024ee15 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -1,7 +1,7 @@ -
+
@@ -64,4 +64,4 @@ [content]="!!sherpaData.message ? sherpaData.message : 'submission.sections.sherpa.error.message'| translate"> - \ No newline at end of file + diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss index c68df78fca..e69de29bb2 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss @@ -1,4 +0,0 @@ -.refresh-container { - display: flex; - justify-content: right; -} \ No newline at end of file From 53851ba18ee92f76cdbe07ae6904abf211c92ffa Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Tue, 24 May 2022 12:28:21 +0200 Subject: [PATCH 43/75] [CST-5270] Fix unit testing --- .../section-sherpa-policies.service.mock.ts | 180 +++++++++--------- .../shared/testing/sections-service.stub.ts | 1 + .../content-accordion.component.spec.ts | 9 + 3 files changed, 100 insertions(+), 90 deletions(-) diff --git a/src/app/shared/mocks/section-sherpa-policies.service.mock.ts b/src/app/shared/mocks/section-sherpa-policies.service.mock.ts index b4947ea5b4..9308325682 100644 --- a/src/app/shared/mocks/section-sherpa-policies.service.mock.ts +++ b/src/app/shared/mocks/section-sherpa-policies.service.mock.ts @@ -6,96 +6,96 @@ export const SherpaDataResponse = { 'id': 'sherpaPolicies', 'retrievalTime': '2022-04-20T09:44:39.870+00:00', 'sherpaResponse': - { - 'error': false, - 'message': null, - 'metadata': { - 'id': 23803, - 'uri': 'http://v2.sherpa.ac.uk/id/publication/23803', - 'dateCreated': '2012-11-20 14:51:52', - 'dateModified': '2020-03-06 11:25:54', - 'inDOAJ': false, - 'publiclyVisible': true + { + 'error': false, + 'message': null, + 'metadata': { + 'id': 23803, + 'uri': 'http://v2.sherpa.ac.uk/id/publication/23803', + 'dateCreated': '2012-11-20 14:51:52', + 'dateModified': '2020-03-06 11:25:54', + 'inDOAJ': false, + 'publiclyVisible': true + }, + 'journals': [{ + 'titles': ['The Lancet', 'Lancet'], + 'url': 'http://www.thelancet.com/journals/lancet/issue/current', + 'issns': ['0140-6736', '1474-547X'], + 'romeoPub': 'Elsevier: The Lancet', + 'zetoPub': 'Elsevier: The Lancet', + 'publisher': { + 'name': 'Elsevier', + 'relationshipType': null, + 'country': null, + 'uri': 'http://www.elsevier.com/', + 'identifier': null, + 'publicationCount': 0, + 'paidAccessDescription': 'Open access', + 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' }, - 'journals': [{ - 'titles': ['The Lancet', 'Lancet'], - 'url': 'http://www.thelancet.com/journals/lancet/issue/current', - 'issns': ['0140-6736', '1474-547X'], - 'romeoPub': 'Elsevier: The Lancet', - 'zetoPub': 'Elsevier: The Lancet', - 'publisher': { - 'name': 'Elsevier', - 'relationshipType': null, - 'country': null, - 'uri': 'http://www.elsevier.com/', - 'identifier': null, - 'publicationCount': 0, - 'paidAccessDescription': 'Open access', - 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' + 'publishers': [{ + 'name': 'Elsevier', + 'relationshipType': null, + 'country': null, + 'uri': 'http://www.elsevier.com/', + 'identifier': null, + 'publicationCount': 0, + 'paidAccessDescription': 'Open access', + 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' + }], + 'policies': [{ + 'id': 0, + 'openAccessPermitted': false, + 'uri': null, + 'internalMoniker': 'Lancet', + 'permittedVersions': [{ + 'articleVersion': 'submitted', + 'option': 1, + 'conditions': ['Upon publication publisher copyright and source must be acknowledged', 'Upon publication must link to publisher version'], + 'prerequisites': [], + 'locations': ['Author\'s Homepage', 'Preprint Repository'], + 'licenses': [], + 'embargo': null + }, { + 'articleVersion': 'accepted', + 'option': 1, + 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], + 'prerequisites': [], + 'locations': ['Author\'s Homepage', 'Institutional Website'], + 'licenses': ['CC BY-NC-ND'], + 'embargo': null + }, { + 'articleVersion': 'accepted', + 'option': 2, + 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], + 'prerequisites': ['If Required by Funder'], + 'locations': ['Non-Commercial Repository'], + 'licenses': ['CC BY-NC-ND'], + 'embargo': { amount: 6, units: 'Months' } + }, { + 'articleVersion': 'accepted', + 'option': 3, + 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], + 'prerequisites': [], + 'locations': ['Non-Commercial Repository'], + 'licenses': [], + 'embargo': null + }], + 'urls': { + 'http://download.thelancet.com/flatcontentassets/authors/lancet-information-for-authors.pdf': 'Guidelines for Authors', + 'http://www.thelancet.com/journals/lancet/article/PIIS0140-6736%2813%2960720-5/fulltext': 'The Lancet journals welcome a new open access policy', + 'http://www.thelancet.com/lancet-information-for-authors/after-publication': 'What happens after publication?', + 'http://www.thelancet.com/lancet/information-for-authors/disclosure-of-results': 'Disclosure of results before publication', + 'https://www.elsevier.com/__data/assets/pdf_file/0005/78476/external-embargo-list.pdf': 'Journal Embargo Period List', + 'https://www.elsevier.com/__data/assets/pdf_file/0011/78473/UK-Embargo-Periods.pdf': 'Journal Embargo List for UK Authors' }, - 'publishers': [{ - 'name': 'Elsevier', - 'relationshipType': null, - 'country': null, - 'uri': 'http://www.elsevier.com/', - 'identifier': null, - 'publicationCount': 0, - 'paidAccessDescription': 'Open access', - 'paidAccessUrl': 'https://www.elsevier.com/about/open-science/open-access' - }], - 'policies': [{ - 'id': 0, - 'openAccessPermitted': false, - 'uri': null, - 'internalMoniker': 'Lancet', - 'permittedVersions': [{ - 'articleVersion': 'submitted', - 'option': 1, - 'conditions': ['Upon publication publisher copyright and source must be acknowledged', 'Upon publication must link to publisher version'], - 'prerequisites': [], - 'locations': ['Author\'s Homepage', 'Preprint Repository'], - 'licenses': [], - 'embargo': null - }, { - 'articleVersion': 'accepted', - 'option': 1, - 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], - 'prerequisites': [], - 'locations': ['Author\'s Homepage', 'Institutional Website'], - 'licenses': ['CC BY-NC-ND'], - 'embargo': null - }, { - 'articleVersion': 'accepted', - 'option': 2, - 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], - 'prerequisites': ['If Required by Funder'], - 'locations': ['Non-Commercial Repository'], - 'licenses': ['CC BY-NC-ND'], - 'embargo': null - }, { - 'articleVersion': 'accepted', - 'option': 3, - 'conditions': ['Publisher copyright and source must be acknowledged', 'Must link to publisher version'], - 'prerequisites': [], - 'locations': ['Non-Commercial Repository'], - 'licenses': [], - 'embargo': null - }], - 'urls': { - 'http://download.thelancet.com/flatcontentassets/authors/lancet-information-for-authors.pdf': 'Guidelines for Authors', - 'http://www.thelancet.com/journals/lancet/article/PIIS0140-6736%2813%2960720-5/fulltext': 'The Lancet journals welcome a new open access policy', - 'http://www.thelancet.com/lancet-information-for-authors/after-publication': 'What happens after publication?', - 'http://www.thelancet.com/lancet/information-for-authors/disclosure-of-results': 'Disclosure of results before publication', - 'https://www.elsevier.com/__data/assets/pdf_file/0005/78476/external-embargo-list.pdf': 'Journal Embargo Period List', - 'https://www.elsevier.com/__data/assets/pdf_file/0011/78473/UK-Embargo-Periods.pdf': 'Journal Embargo List for UK Authors' - }, - 'openAccessProhibited': false, - 'publicationCount': 0, - 'preArchiving': 'can', - 'postArchiving': 'can', - 'pubArchiving': 'cannot' - }], - 'inDOAJ': false - }] - } + 'openAccessProhibited': false, + 'publicationCount': 0, + 'preArchiving': 'can', + 'postArchiving': 'can', + 'pubArchiving': 'cannot' + }], + 'inDOAJ': false + }] + } } as WorkspaceitemSectionSherpaPoliciesObject; diff --git a/src/app/shared/testing/sections-service.stub.ts b/src/app/shared/testing/sections-service.stub.ts index 1628453bc8..b687c512c2 100644 --- a/src/app/shared/testing/sections-service.stub.ts +++ b/src/app/shared/testing/sections-service.stub.ts @@ -20,4 +20,5 @@ export class SectionsServiceStub { computeSectionConfiguredMetadata = jasmine.createSpy('computeSectionConfiguredMetadata'); getShownSectionErrors = jasmine.createSpy('getShownSectionErrors'); getSectionServerErrors = jasmine.createSpy('getSectionServerErrors'); + getIsInformational = jasmine.createSpy('getIsInformational'); } diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts index b07674c40d..b65cb5e00f 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.spec.ts @@ -34,6 +34,7 @@ describe('ContentAccordionComponent', () => { fixture = TestBed.createComponent(ContentAccordionComponent); component = fixture.componentInstance; de = fixture.debugElement; + component.isCollapsed = false; component.version = SherpaDataResponse.sherpaResponse.journals[0].policies[0].permittedVersions[0]; fixture.detectChanges(); }); @@ -42,7 +43,15 @@ describe('ContentAccordionComponent', () => { expect(component).toBeTruthy(); }); + it('should show 2 rows', () => { + component.version = SherpaDataResponse.sherpaResponse.journals[0].policies[0].permittedVersions[0]; + fixture.detectChanges(); + expect(de.queryAll(By.css('.row')).length).toEqual(2); + }); + it('should show 5 rows', () => { + component.version = SherpaDataResponse.sherpaResponse.journals[0].policies[0].permittedVersions[2]; + fixture.detectChanges(); expect(de.queryAll(By.css('.row')).length).toEqual(5); }); }); From 5842d83e915997048d023ab701764d003e74b4f7 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Tue, 24 May 2022 12:34:44 +0200 Subject: [PATCH 44/75] [CST-5270] add class needed for unit testing --- .../sherpa-policies/section-sherpa-policies.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index a7e024ee15..3372fe3005 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -1,7 +1,7 @@ -
+
@@ -64,4 +64,4 @@ [content]="!!sherpaData.message ? sherpaData.message : 'submission.sections.sherpa.error.message'| translate"> - + \ No newline at end of file From d1f8bb6d7b04035729875c5053de47ea0904fbf2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 24 May 2022 16:05:21 +0200 Subject: [PATCH 45/75] [CST-5270] use data-test as css selector for unit test --- .../section-sherpa-policies.component.html | 6 +++--- .../section-sherpa-policies.component.spec.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index 3372fe3005..b7a42df702 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -1,8 +1,8 @@ -
-
@@ -64,4 +64,4 @@ [content]="!!sherpaData.message ? sherpaData.message : 'submission.sections.sherpa.error.message'| translate"> - \ No newline at end of file + diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts index 30a50743b7..fe70f3e068 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts @@ -1,8 +1,8 @@ -import { SharedModule } from './../../../shared/shared.module'; +import { SharedModule } from '../../../shared/shared.module'; import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'; import { SubmissionServiceStub } from '../../../shared/testing/submission-service.stub'; import { SherpaDataResponse } from '../../../shared/mocks/section-sherpa-policies.service.mock'; -import { ComponentFixture, inject, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { ComponentFixture, inject, TestBed } from '@angular/core/testing'; import { SectionsService } from '../sections.service'; import { SectionsServiceStub } from '../../../shared/testing/sections-service.stub'; @@ -89,7 +89,7 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { }); it('should show refresh button', () => { - expect(de.query(By.css('.refresh-container > button'))).toBeTruthy(); + expect(de.query(By.css('[data-test="refresh-btn"]'))).toBeTruthy(); }); it('should show publisher information', () => { @@ -105,7 +105,7 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { }); it('when refresh button click operationsBuilder.remove should have been called', () => { - de.query(By.css('.refresh-container > button')).nativeElement.click(); + de.query(By.css('[data-test="refresh-btn"]')).nativeElement.click(); expect(operationsBuilder.remove).toHaveBeenCalled(); }); From 98b1667ca7ff5965dd5fa8889f32806aec8eed20 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 25 May 2022 09:24:14 +0200 Subject: [PATCH 46/75] [CST-5270] Fix LGTM alerts --- .../publisher-policy/publisher-policy.component.ts | 2 +- .../sherpa-policies/section-sherpa-policies.component.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts index 65d044f30c..96ada3904c 100644 --- a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.ts @@ -1,7 +1,7 @@ import { Component, Input } from '@angular/core'; import { Policy } from '../../../../core/submission/models/sherpa-policies-details.model'; -import { AlertType } from './../../../../shared/alert/aletr-type'; +import { AlertType } from '../../../../shared/alert/aletr-type'; /** * This component represents a section that contains the publisher policy informations. diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts index e4bbebc617..e55b75146f 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.ts @@ -1,7 +1,7 @@ -import { AlertType } from './../../../shared/alert/aletr-type'; -import { Component, Inject, QueryList, ViewChildren } from '@angular/core'; +import { AlertType } from '../../../shared/alert/aletr-type'; +import { Component, Inject } from '@angular/core'; -import { BehaviorSubject, interval, Observable, of, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, of, Subscription } from 'rxjs'; import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder'; From d297eb708d247939f404d39ca660f4e30ce83679 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Wed, 25 May 2022 18:20:13 +0200 Subject: [PATCH 47/75] [CST-5270] fix issue when no info --- .../sherpa-policies/section-sherpa-policies.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index b7a42df702..2ec72f7935 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -8,7 +8,7 @@
- +
@@ -59,9 +59,9 @@
- + - + \ No newline at end of file From be3fceb185450c9d7cb19bfb72670dbdb0c570bb Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 26 May 2022 12:48:27 +0200 Subject: [PATCH 48/75] [CST-5307] Address feedback --- .../researcher-profile.service.spec.ts | 18 +++++--- .../profile/researcher-profile.service.ts | 44 +++++++------------ ...on-search-result-list-element.component.ts | 22 +++++----- .../profile-claim-item-modal.component.ts | 5 ++- .../profile-claim.service.spec.ts | 4 +- .../profile-claim/profile-claim.service.ts | 10 ++--- .../profile-page-researcher-form.component.ts | 22 ++++++---- .../person-page-claim-button.component.ts | 1 - 8 files changed, 62 insertions(+), 64 deletions(-) diff --git a/src/app/core/profile/researcher-profile.service.spec.ts b/src/app/core/profile/researcher-profile.service.spec.ts index 103bae2719..3a8d7b6017 100644 --- a/src/app/core/profile/researcher-profile.service.spec.ts +++ b/src/app/core/profile/researcher-profile.service.spec.ts @@ -11,7 +11,11 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestService } from '../data/request.service'; import { PageInfo } from '../shared/page-info.model'; import { buildPaginatedList } from '../data/paginated-list.model'; -import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { + createNoContentRemoteDataObject$, + createSuccessfulRemoteDataObject, + createSuccessfulRemoteDataObject$ +} from '../../shared/remote-data.utils'; import { RestResponse } from '../cache/response.models'; import { RequestEntry } from '../data/request-entry.model'; import { ResearcherProfileService } from './researcher-profile.service'; @@ -223,7 +227,7 @@ describe('ResearcherProfileService', () => { describe('without a related item', () => { beforeEach(() => { - (service as any).itemService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(null)); + (service as any).itemService.findByHref.and.returnValue(createNoContentRemoteDataObject$()); }); it('should proxy the call to dataservice.findById with eperson UUID', () => { @@ -233,10 +237,10 @@ describe('ResearcherProfileService', () => { expect((service as any).itemService.findByHref).toHaveBeenCalledWith(researcherProfile._links.item.href, false); }); - it('should return a ResearcherProfile object with the given id', () => { + it('should not return a ResearcherProfile object with the given id', () => { const result = service.findRelatedItemId(researcherProfile); const expected = cold('(a|)', { - a: undefined + a: null }); expect(result).toBeObservable(expected); }); @@ -246,7 +250,7 @@ describe('ResearcherProfileService', () => { describe('setVisibility', () => { let patchSpy; beforeEach(() => { - spyOn((service as any), 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + spyOn((service as any).dataService, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); }); @@ -260,14 +264,14 @@ describe('ResearcherProfileService', () => { scheduler.schedule(() => service.setVisibility(researcherProfile, true)); scheduler.flush(); - expect((service as any).patch).toHaveBeenCalledWith(researcherProfile, [replaceOperation]); + expect((service as any).dataService.patch).toHaveBeenCalledWith(researcherProfile, [replaceOperation]); }); }); describe('createFromExternalSource', () => { let patchSpy; beforeEach(() => { - spyOn((service as any), 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + spyOn((service as any).dataService, 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); }); diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index 0c39396950..23656f3f71 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -4,9 +4,9 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; -import { Operation, ReplaceOperation } from 'fast-json-patch'; -import { Observable, of as observableOf } from 'rxjs'; -import { catchError, find, map, tap } from 'rxjs/operators'; +import { ReplaceOperation } from 'fast-json-patch'; +import { Observable } from 'rxjs'; +import { find, map, tap } from 'rxjs/operators'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { dataService } from '../cache/builders/build-decorators'; @@ -19,11 +19,7 @@ import { RemoteData } from '../data/remote-data'; import { RequestService } from '../data/request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NoContent } from '../shared/NoContent.model'; -import { - getAllCompletedRemoteData, - getFirstCompletedRemoteData, - getFirstSucceededRemoteDataPayload -} from '../shared/operators'; +import { getAllCompletedRemoteData, getFirstCompletedRemoteData } from '../shared/operators'; import { ResearcherProfile } from './model/researcher-profile.model'; import { RESEARCHER_PROFILE } from './model/researcher-profile.resource-type'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; @@ -31,6 +27,7 @@ import { PostRequest } from '../data/request.models'; import { hasValue } from '../../shared/empty.util'; import { CoreState } from '../core-state.model'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; +import { Item } from '../shared/item.model'; /** * A private DataService implementation to delegate specific methods to. @@ -126,14 +123,10 @@ export class ResearcherProfileService { * @param researcherProfile the profile to find for */ public findRelatedItemId(researcherProfile: ResearcherProfile): Observable { - return this.itemService.findByHref(researcherProfile._links.item.href, false) - .pipe(getFirstSucceededRemoteDataPayload(), - catchError((error) => { - console.debug(error); - return observableOf(null); - }), - map((item) => item?.id) - ); + return this.itemService.findByHref(researcherProfile._links.item.href, false).pipe( + getFirstCompletedRemoteData(), + map((itemRD: RemoteData) => (itemRD.hasSucceeded && itemRD.payload) ? itemRD.payload.id : null) + ); } /** @@ -149,7 +142,7 @@ export class ResearcherProfileService { value: visible }; - return this.patch(researcherProfile, [replaceOperation]); + return this.dataService.patch(researcherProfile, [replaceOperation]); } /** @@ -166,19 +159,12 @@ export class ResearcherProfileService { const href$ = this.halService.getEndpoint(this.dataService.getLinkPath()); href$.pipe( - find((href: string) => hasValue(href)), - map((href: string) => { - const request = new PostRequest(requestId, href, sourceUri, options); - this.requestService.send(request); - }) - ).subscribe(); + find((href: string) => hasValue(href)) + ).subscribe((endpoint: string) => { + const request = new PostRequest(requestId, endpoint, sourceUri, options); + this.requestService.send(request); + }); return this.rdbService.buildFromRequestUUID(requestId); } - - private patch(researcherProfile: ResearcherProfile, operations: Operation[]): Observable> { - return this.dataService.patch(researcherProfile, operations).pipe( - getFirstCompletedRemoteData() - ); - } } diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts index 32785d7b8a..3b222ce27c 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts @@ -1,10 +1,13 @@ import { Component } from '@angular/core'; -import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { + listableObjectComponent +} from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; -import { ItemSearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; -import {TruncatableService} from '../../../../../shared/truncatable/truncatable.service'; -import {DSONameService} from '../../../../../core/breadcrumbs/dso-name.service'; -import {isNotEmpty} from '../../../../../shared/empty.util'; +import { + ItemSearchResultListElementComponent +} from '../../../../../shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; +import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; +import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; @listableObjectComponent('PersonSearchResult', ViewMode.ListElement) @Component({ @@ -21,11 +24,10 @@ export class PersonSearchResultListElementComponent extends ItemSearchResultList super(truncatableService, dsoNameService); } + /** + * Return the person name + */ get name() { - let personName = this.dsoNameService.getName(this.dso); - if (isNotEmpty(this.firstMetadataValue('person.familyName')) && isNotEmpty(this.firstMetadataValue('person.givenName'))) { - personName = this.firstMetadataValue('person.familyName') + ', ' + this.firstMetadataValue('person.givenName'); - } - return personName; + return this.dsoNameService.getName(this.dso); } } diff --git a/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.ts b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.ts index 01138bb07f..6838704560 100644 --- a/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.ts +++ b/src/app/profile-page/profile-claim-item-modal/profile-claim-item-modal.component.ts @@ -14,6 +14,7 @@ import { ViewMode } from '../../core/shared/view-mode.model'; import { ProfileClaimService } from '../profile-claim/profile-claim.service'; import { CollectionElementLinkType } from '../../shared/object-collection/collection-element-link.type'; import { SearchObjects } from '../../shared/search/models/search-objects.model'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; /** * Component representing a modal that show a list of suggested profile item to claim @@ -63,7 +64,9 @@ export class ProfileClaimItemModalComponent extends DSOSelectorModalWrapperCompo * Retrieve suggested profiles, if any */ ngOnInit(): void { - this.profileClaimService.searchForSuggestions(this.dso as EPerson).subscribe( + this.profileClaimService.searchForSuggestions(this.dso as EPerson).pipe( + getFirstCompletedRemoteData(), + ).subscribe( (result: RemoteData>) => this.listEntries$.next(result) ); } diff --git a/src/app/profile-page/profile-claim/profile-claim.service.spec.ts b/src/app/profile-page/profile-claim/profile-claim.service.spec.ts index a06934231b..e2a9ff1461 100644 --- a/src/app/profile-page/profile-claim/profile-claim.service.spec.ts +++ b/src/app/profile-page/profile-claim/profile-claim.service.spec.ts @@ -8,7 +8,7 @@ import { SearchService } from '../../core/shared/search/search.service'; import { ItemSearchResult } from '../../shared/object-collection/shared/item-search-result.model'; import { SearchObjects } from '../../shared/search/models/search-objects.model'; import { Item } from '../../core/shared/item.model'; -import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { createNoContentRemoteDataObject, createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; import { EPerson } from '../../core/eperson/models/eperson.model'; describe('ProfileClaimService', () => { @@ -204,7 +204,7 @@ describe('ProfileClaimService', () => { it('should return null', () => { const result = service.searchForSuggestions(null); const expected = cold('(a|)', { - a: null + a: createNoContentRemoteDataObject() }); expect(result).toBeObservable(expected); }); diff --git a/src/app/profile-page/profile-claim/profile-claim.service.ts b/src/app/profile-page/profile-claim/profile-claim.service.ts index 806f48a2f6..ee54d0c3be 100644 --- a/src/app/profile-page/profile-claim/profile-claim.service.ts +++ b/src/app/profile-page/profile-claim/profile-claim.service.ts @@ -11,6 +11,7 @@ import { isEmpty, isNotEmpty } from '../../shared/empty.util'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import { SearchObjects } from '../../shared/search/models/search-objects.model'; +import { createNoContentRemoteDataObject } from '../../shared/remote-data.utils'; /** * Service that handle profiles claim. @@ -28,6 +29,7 @@ export class ProfileClaimService { */ hasProfilesToSuggest(eperson: EPerson): Observable { return this.searchForSuggestions(eperson).pipe( + getFirstCompletedRemoteData(), map((rd: RemoteData>) => { return isNotEmpty(rd) && rd.hasSucceeded && rd.payload?.page?.length > 0; }) @@ -42,7 +44,7 @@ export class ProfileClaimService { searchForSuggestions(eperson: EPerson): Observable>> { const query = this.personQueryData(eperson); if (isEmpty(query)) { - return of(null); + return of(createNoContentRemoteDataObject() as RemoteData>); } return this.lookup(query); } @@ -54,14 +56,12 @@ export class ProfileClaimService { */ private lookup(query: string): Observable>> { if (isEmpty(query)) { - return of(null); + return of(createNoContentRemoteDataObject() as RemoteData>); } return this.searchService.search(new PaginatedSearchOptions({ configuration: 'eperson_claims', query: query - })).pipe( - getFirstCompletedRemoteData() - ); + })); } /** diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts index b0a2c90de4..bffec746d3 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts @@ -15,6 +15,7 @@ import { ResearcherProfile } from '../../core/profile/model/researcher-profile.m import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; import { ProfileClaimService } from '../profile-claim/profile-claim.service'; import { RemoteData } from '../../core/data/remote-data'; +import { isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-profile-page-researcher-form', @@ -128,14 +129,15 @@ export class ProfilePageResearcherFormComponent implements OnInit { * @param researcherProfile the profile to update */ toggleProfileVisibility(researcherProfile: ResearcherProfile): void { - this.researcherProfileService.setVisibility(researcherProfile, !researcherProfile.visible) - .subscribe((rd: RemoteData) => { - if (rd.hasSucceeded) { - this.researcherProfile$.next(rd.payload); - } else { - this.notificationService.error(null, this.translationService.get('researcher.profile.change-visibility.fail')); - } - }); + this.researcherProfileService.setVisibility(researcherProfile, !researcherProfile.visible).pipe( + getFirstCompletedRemoteData() + ).subscribe((rd: RemoteData) => { + if (rd.hasSucceeded) { + this.researcherProfile$.next(rd.payload); + } else { + this.notificationService.error(null, this.translationService.get('researcher.profile.change-visibility.fail')); + } + }); } /** @@ -183,7 +185,9 @@ export class ProfilePageResearcherFormComponent implements OnInit { tap((researcherProfile) => this.researcherProfile$.next(researcherProfile)), mergeMap((researcherProfile) => this.researcherProfileService.findRelatedItemId(researcherProfile)), ).subscribe((itemId: string) => { - this.researcherProfileItemId = itemId; + if (isNotEmpty(itemId)) { + this.researcherProfileItemId = itemId; + } }); } diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts index d2e8e888e1..f317b88c8d 100644 --- a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts +++ b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts @@ -55,7 +55,6 @@ export class PersonPageClaimButtonComponent implements OnInit { this.researcherProfileService.createFromExternalSource(this.object._links.self.href).pipe( getFirstCompletedRemoteData(), mergeMap((rd: RemoteData) => { - console.log(rd); if (rd.hasSucceeded) { return this.researcherProfileService.findRelatedItemId(rd.payload); } else { From e6f6bc96f354efe68fe2de41213175be2f532cdf Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 26 May 2022 15:32:18 +0200 Subject: [PATCH 49/75] [CST-5307] use followlink for retrieving profile item on create and find --- src/app/core/data/data.service.ts | 16 +++++----------- .../profile/model/researcher-profile.model.ts | 16 ++++++++++++++-- .../profile/researcher-profile.service.spec.ts | 6 ++++-- .../core/profile/researcher-profile.service.ts | 10 ++++++---- ...rofile-page-researcher-form.component.spec.ts | 3 ++- .../profile-page-researcher-form.component.ts | 3 ++- 6 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 310ad704ec..1c14366057 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -8,11 +8,11 @@ import { find, map, mergeMap, + skipWhile, + switchMap, take, takeWhile, - switchMap, tap, - skipWhile, } from 'rxjs/operators'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; @@ -25,18 +25,12 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { DSpaceSerializer } from '../dspace-rest/dspace.serializer'; import { DSpaceObject } from '../shared/dspace-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { getRemoteDataPayload, getFirstSucceededRemoteData, } from '../shared/operators'; +import { getFirstSucceededRemoteData, getRemoteDataPayload, } from '../shared/operators'; import { URLCombiner } from '../url-combiner/url-combiner'; import { ChangeAnalyzer } from './change-analyzer'; import { PaginatedList } from './paginated-list.model'; import { RemoteData } from './remote-data'; -import { - CreateRequest, - GetRequest, - PatchRequest, - PutRequest, - DeleteRequest -} from './request.models'; +import { CreateRequest, DeleteRequest, GetRequest, PatchRequest, PutRequest } from './request.models'; import { RequestService } from './request.service'; import { RestRequestMethod } from './rest-request-method'; import { UpdateDataService } from './update-data.service'; @@ -168,7 +162,7 @@ export abstract class DataService implements UpdateDa * @return {Observable} * Return an observable that emits created HREF */ - protected buildHrefWithParams(href: string, params: RequestParam[], ...linksToFollow: FollowLinkConfig[]): string { + buildHrefWithParams(href: string, params: RequestParam[], ...linksToFollow: FollowLinkConfig[]): string { let args = []; if (hasValue(params)) { diff --git a/src/app/core/profile/model/researcher-profile.model.ts b/src/app/core/profile/model/researcher-profile.model.ts index a07467476e..6c8b19db40 100644 --- a/src/app/core/profile/model/researcher-profile.model.ts +++ b/src/app/core/profile/model/researcher-profile.model.ts @@ -1,10 +1,15 @@ +import { Observable } from 'rxjs'; import { autoserialize, deserialize, deserializeAs } from 'cerialize'; -import { typedObject } from '../../cache/builders/build-decorators'; + +import { link, typedObject } from '../../cache/builders/build-decorators'; import { HALLink } from '../../shared/hal-link.model'; import { ResourceType } from '../../shared/resource-type'; import { excludeFromEquals } from '../../utilities/equals.decorators'; import { RESEARCHER_PROFILE } from './researcher-profile.resource-type'; -import {CacheableObject} from '../../cache/cacheable-object.model'; +import { CacheableObject } from '../../cache/cacheable-object.model'; +import { RemoteData } from '../../data/remote-data'; +import { ITEM } from '../../shared/item.resource-type'; +import { Item } from '../../shared/item.model'; /** * Class the represents a Researcher Profile. @@ -46,4 +51,11 @@ export class ResearcherProfile extends CacheableObject { eperson: HALLink }; + /** + * The related person Item + * Will be undefined unless the item {@link HALLink} has been resolved. + */ + @link(ITEM) + item?: Observable>; + } diff --git a/src/app/core/profile/researcher-profile.service.spec.ts b/src/app/core/profile/researcher-profile.service.spec.ts index 3a8d7b6017..eb5ff776fe 100644 --- a/src/app/core/profile/researcher-profile.service.spec.ts +++ b/src/app/core/profile/researcher-profile.service.spec.ts @@ -25,6 +25,7 @@ import { Item } from '../shared/item.model'; import { ReplaceOperation } from 'fast-json-patch'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { PostRequest } from '../data/request.models'; +import { followLink } from '../../shared/utils/follow-link-config.model'; describe('ResearcherProfileService', () => { let scheduler: TestScheduler; @@ -86,6 +87,7 @@ describe('ResearcherProfileService', () => { } }); const endpointURL = `https://rest.api/rest/api/profiles`; + const endpointURLWithEmbed = 'https://rest.api/rest/api/profiles?embed=item'; const sourceUri = `https://rest.api/rest/api/external-source/profile`; const requestURL = `https://rest.api/rest/api/profiles/${researcherProfileId}`; const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a'; @@ -280,13 +282,13 @@ describe('ResearcherProfileService', () => { let headers = new HttpHeaders(); headers = headers.append('Content-Type', 'text/uri-list'); options.headers = headers; - const request = new PostRequest(requestUUID, endpointURL, sourceUri, options); + const request = new PostRequest(requestUUID, endpointURLWithEmbed, sourceUri, options); scheduler.schedule(() => service.createFromExternalSource(sourceUri)); scheduler.flush(); expect((service as any).requestService.send).toHaveBeenCalledWith(request); - expect((service as any).rdbService.buildFromRequestUUID).toHaveBeenCalledWith(requestUUID); + expect((service as any).rdbService.buildFromRequestUUID).toHaveBeenCalledWith(requestUUID, followLink('item')); }); }); diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index 23656f3f71..705b3ebc65 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -26,7 +26,7 @@ import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { PostRequest } from '../data/request.models'; import { hasValue } from '../../shared/empty.util'; import { CoreState } from '../core-state.model'; -import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; +import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { Item } from '../shared/item.model'; /** @@ -123,7 +123,8 @@ export class ResearcherProfileService { * @param researcherProfile the profile to find for */ public findRelatedItemId(researcherProfile: ResearcherProfile): Observable { - return this.itemService.findByHref(researcherProfile._links.item.href, false).pipe( + const relatedItem$ = researcherProfile.item ? researcherProfile.item : this.itemService.findByHref(researcherProfile._links.item.href, false); + return relatedItem$.pipe( getFirstCompletedRemoteData(), map((itemRD: RemoteData) => (itemRD.hasSucceeded && itemRD.payload) ? itemRD.payload.id : null) ); @@ -159,12 +160,13 @@ export class ResearcherProfileService { const href$ = this.halService.getEndpoint(this.dataService.getLinkPath()); href$.pipe( - find((href: string) => hasValue(href)) + find((href: string) => hasValue(href)), + map((href: string) => this.dataService.buildHrefWithParams(href, [], followLink('item'))) ).subscribe((endpoint: string) => { const request = new PostRequest(requestId, endpoint, sourceUri, options); this.requestService.send(request); }); - return this.rdbService.buildFromRequestUUID(requestId); + return this.rdbService.buildFromRequestUUID(requestId, followLink('item')); } } diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts index 0b096b99b7..00a3f4bc94 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts @@ -17,6 +17,7 @@ import { ProfilePageResearcherFormComponent } from './profile-page-researcher-fo import { ProfileClaimService } from '../profile-claim/profile-claim.service'; import { AuthService } from '../../core/auth/auth.service'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { followLink } from '../../shared/utils/follow-link-config.model'; describe('ProfilePageResearcherFormComponent', () => { @@ -92,7 +93,7 @@ describe('ProfilePageResearcherFormComponent', () => { }); it('should search the researcher profile for the current user', () => { - expect(researcherProfileService.findById).toHaveBeenCalledWith(user.id, false); + expect(researcherProfileService.findById).toHaveBeenCalledWith(user.id, false, true, followLink('item')); }); describe('createProfile', () => { diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts index bffec746d3..f0235c6423 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts @@ -16,6 +16,7 @@ import { ResearcherProfileService } from '../../core/profile/researcher-profile. import { ProfileClaimService } from '../profile-claim/profile-claim.service'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; +import { followLink } from '../../shared/utils/follow-link-config.model'; @Component({ selector: 'ds-profile-page-researcher-form', @@ -180,7 +181,7 @@ export class ProfilePageResearcherFormComponent implements OnInit { * Initializes the researcherProfile and researcherProfileItemId attributes using the profile of the current user. */ private initResearchProfile(): void { - this.researcherProfileService.findById(this.user.id, false).pipe( + this.researcherProfileService.findById(this.user.id, false, true, followLink('item')).pipe( getFirstSucceededRemoteDataPayload(), tap((researcherProfile) => this.researcherProfile$.next(researcherProfile)), mergeMap((researcherProfile) => this.researcherProfileService.findRelatedItemId(researcherProfile)), From bc37372f0549580d3994f022ab233e1bf3f25386 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 26 May 2022 15:47:57 +0200 Subject: [PATCH 50/75] [CST-5307] add confirmation modal when deleting the profile --- ...ile-page-researcher-form.component.spec.ts | 8 ++++- .../profile-page-researcher-form.component.ts | 30 +++++++++++++------ src/assets/i18n/en.json5 | 10 ++++++- 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts index 00a3f4bc94..aa0d1d187d 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts @@ -156,9 +156,15 @@ describe('ProfilePageResearcherFormComponent', () => { }); describe('deleteProfile', () => { + beforeEach(() => { + const modalService = (component as any).modalService; + spyOn(modalService, 'open').and.returnValue(Object.assign({ componentInstance: Object.assign({ response: observableOf(true) }) })); + component.deleteProfile(profile); + fixture.detectChanges(); + }); it('should delete the profile', () => { - component.deleteProfile(profile); + expect(researcherProfileService.delete).toHaveBeenCalledWith(profile); }); diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts index f0235c6423..1e8b5fc432 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts @@ -17,6 +17,7 @@ import { ProfileClaimService } from '../profile-claim/profile-claim.service'; import { RemoteData } from '../../core/data/remote-data'; import { isNotEmpty } from '../../shared/empty.util'; import { followLink } from '../../shared/utils/follow-link-config.model'; +import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component'; @Component({ selector: 'ds-profile-page-researcher-form', @@ -113,15 +114,26 @@ export class ProfilePageResearcherFormComponent implements OnInit { * @param researcherProfile the profile to delete */ deleteProfile(researcherProfile: ResearcherProfile): void { - this.processingDelete$.next(true); - this.researcherProfileService.delete(researcherProfile) - .subscribe((deleted) => { - if (deleted) { - this.researcherProfile$.next(null); - this.researcherProfileItemId = null; - } - this.processingDelete$.next(false); - }); + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-profile.header'; + modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-profile.info'; + modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-profile.cancel'; + modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-profile.confirm'; + modalRef.componentInstance.brandColor = 'danger'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { + if (confirm) { + this.processingDelete$.next(true); + this.researcherProfileService.delete(researcherProfile) + .subscribe((deleted) => { + if (deleted) { + this.researcherProfile$.next(null); + this.researcherProfileItemId = null; + } + this.processingDelete$.next(false); + }); + } + }); } /** diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c630c46b55..637754ccde 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1361,6 +1361,14 @@ "confirmation-modal.delete-eperson.confirm": "Delete", + "confirmation-modal.delete-profile.header": "Delete Profile", + + "confirmation-modal.delete-profile.info": "Are you sure you want to delete your profile", + + "confirmation-modal.delete-profile.cancel": "Cancel", + + "confirmation-modal.delete-profile.confirm": "Delete", + "error.bitstream": "Error fetching bitstream", @@ -3666,7 +3674,7 @@ "submission.import-external.preview.title.OrgUnit": "Organizational Unit Preview", "submission.import-external.preview.title.Person": "Person Preview", - + "submission.import-external.preview.title.Project": "Project Preview", "submission.import-external.preview.subtitle": "The metadata below was imported from an external source. It will be pre-filled when you start the submission.", From 87064761b552ce54cf28fe705bd1648f7fe21e21 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 26 May 2022 16:36:03 +0200 Subject: [PATCH 51/75] [CST-5307] fix issue with unsubscribed subscription --- .../profile-page-researcher-form.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts index 1e8b5fc432..f7662e0522 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts @@ -80,6 +80,7 @@ export class ProfilePageResearcherFormComponent implements OnInit { this.processingCreate$.next(true); this.authService.getAuthenticatedUserFromStore().pipe( + take(1), switchMap((currentUser) => this.profileClaimService.hasProfilesToSuggest(currentUser))) .subscribe((hasProfilesToSuggest) => { From 2c5da265e1ab6cbe7f97d2db2d2152b2f0cbbdf0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 26 May 2022 16:36:21 +0200 Subject: [PATCH 52/75] [CST-5307] fix LGTM alerts --- src/app/item-page/simple/item-page.component.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/app/item-page/simple/item-page.component.ts b/src/app/item-page/simple/item-page.component.ts index b3660eb9d7..6e0db386db 100644 --- a/src/app/item-page/simple/item-page.component.ts +++ b/src/app/item-page/simple/item-page.component.ts @@ -1,27 +1,20 @@ -import { map, mergeMap, take, tap } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + import { ItemDataService } from '../../core/data/item-data.service'; import { RemoteData } from '../../core/data/remote-data'; - import { Item } from '../../core/shared/item.model'; - import { fadeInOut } from '../../shared/animations/fade'; -import { getAllSucceededRemoteDataPayload, getFirstSucceededRemoteData } from '../../core/shared/operators'; +import { getAllSucceededRemoteDataPayload } from '../../core/shared/operators'; import { ViewMode } from '../../core/shared/view-mode.model'; import { AuthService } from '../../core/auth/auth.service'; import { getItemPageRoute } from '../item-page-routing-paths'; import { redirectOn4xx } from '../../core/shared/authorized.operators'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { TranslateService } from '@ngx-translate/core'; -import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; -import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model'; -import { isNotUndefined } from '../../shared/empty.util'; -import { NotificationsService } from '../../shared/notifications/notifications.service'; - /** * This component renders a simple item page. From a830c99f908cfa0b451e1987fb8644d6b85c8d53 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 26 May 2022 16:58:08 +0200 Subject: [PATCH 53/75] [CST-5307] Remove setStaleByHrefSubstring --- src/app/core/profile/researcher-profile.service.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index 705b3ebc65..006b0b554b 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -6,7 +6,7 @@ import { Router } from '@angular/router'; import { Store } from '@ngrx/store'; import { ReplaceOperation } from 'fast-json-patch'; import { Observable } from 'rxjs'; -import { find, map, tap } from 'rxjs/operators'; +import { find, map } from 'rxjs/operators'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { dataService } from '../cache/builders/build-decorators'; @@ -108,11 +108,6 @@ export class ResearcherProfileService { public delete(researcherProfile: ResearcherProfile): Observable { return this.dataService.delete(researcherProfile.id).pipe( getFirstCompletedRemoteData(), - tap((response: RemoteData) => { - if (response.isSuccess) { - this.requestService.setStaleByHrefSubstring(researcherProfile._links.self.href); - } - }), map((response: RemoteData) => response.isSuccess) ); } From 03e1e468bd09d309012024167bb60ba09a57d3b4 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Fri, 27 May 2022 17:11:14 +0200 Subject: [PATCH 54/75] 91770: Fix route-matching themes not resolving on first load/refresh --- src/app/app.component.ts | 57 +++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 6aa569d8e6..818a38d34f 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,6 +12,7 @@ import { } from '@angular/core'; import { ActivatedRouteSnapshot, + ActivationEnd, NavigationCancel, NavigationEnd, NavigationStart, ResolveEnd, @@ -196,30 +197,30 @@ export class AppComponent implements OnInit, AfterViewInit { } ngAfterViewInit() { - let resolveEndFound = false; + let updatingTheme = false; + let snapshot: ActivatedRouteSnapshot; + this.router.events.subscribe((event) => { if (event instanceof NavigationStart) { - resolveEndFound = false; + updatingTheme = false; this.distinctNext(this.isRouteLoading$, true); - } else if (event instanceof ResolveEnd) { - resolveEndFound = true; - const activatedRouteSnapShot: ActivatedRouteSnapshot = event.state.root; - this.themeService.updateThemeOnRouteChange$(event.urlAfterRedirects, activatedRouteSnapShot).pipe( - switchMap((changed) => { - if (changed) { - return this.isThemeCSSLoading$; - } else { - return [false]; - } - }) - ).subscribe((changed) => { - this.distinctNext(this.isThemeLoading$, changed); - }); - } else if ( - event instanceof NavigationEnd || - event instanceof NavigationCancel - ) { - if (!resolveEndFound) { + } else if (event instanceof ResolveEnd) { + // this is the earliest point where we have all the information we need + // to update the theme, but this event is not emitted on first load + this.updateTheme(event.urlAfterRedirects, event.state.root); + updatingTheme = true; + } else if (!updatingTheme && event instanceof ActivationEnd) { + // if there was no ResolveEnd, keep track of the snapshot... + snapshot = event.snapshot; + } else if (event instanceof NavigationEnd) { + if (!updatingTheme) { + // ...and use it to update the theme on NavigationEnd instead + this.updateTheme(event.urlAfterRedirects, snapshot); + updatingTheme = true; + } + this.distinctNext(this.isRouteLoading$, false); + } else if (event instanceof NavigationCancel) { + if (!updatingTheme) { this.distinctNext(this.isThemeLoading$, false); } this.distinctNext(this.isRouteLoading$, false); @@ -227,6 +228,20 @@ export class AppComponent implements OnInit, AfterViewInit { }); } + private updateTheme(urlAfterRedirects: string, snapshot: ActivatedRouteSnapshot): void { + this.themeService.updateThemeOnRouteChange$(urlAfterRedirects, snapshot).pipe( + switchMap((changed) => { + if (changed) { + return this.isThemeCSSLoading$; + } else { + return [false]; + } + }) + ).subscribe((changed) => { + this.distinctNext(this.isThemeLoading$, changed); + }); + } + @HostListener('window:resize', ['$event']) public onResize(event): void { this.dispatchWindowSize(event.target.innerWidth, event.target.innerHeight); From d75136408b2cb831caadfc2225b66abd07f70da9 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Thu, 2 Jun 2022 12:49:53 +0200 Subject: [PATCH 55/75] [CST-5270] small fix for unit selection --- .../content-accordion/content-accordion.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html index 0b52c8341d..3d4fba5cea 100644 --- a/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html +++ b/src/app/submission/sections/sherpa-policies/content-accordion/content-accordion.component.html @@ -10,7 +10,7 @@
{{version.embargo.amount}} - {{version.embargo.units[1]}} + {{version.embargo?.units[0]}} {{ 'submission.sections.sherpa.publisher.policy.noembargo' | translate }} From 3c6a41f82a273856807f6b51b379a601da661b70 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Fri, 3 Jun 2022 17:49:15 +0200 Subject: [PATCH 56/75] [CST-5270] Improvement requested from pull-1633 --- .../container/section-container.component.html | 7 ++++--- .../container/section-container.component.scss | 10 +++++++++- .../publisher-policy.component.html | 9 --------- .../section-sherpa-policies.component.html | 17 ++++++++++++++--- .../section-sherpa-policies.component.scss | 3 +++ src/assets/i18n/en.json5 | 6 ++++-- 6 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/app/submission/sections/container/section-container.component.html b/src/app/submission/sections/container/section-container.component.html index 95ed9289c9..0d61bb42db 100644 --- a/src/app/submission/sections/container/section-container.component.html +++ b/src/app/submission/sections/container/section-container.component.html @@ -3,9 +3,10 @@ [submissionId]="submissionId" [sectionType]="sectionData.sectionType" [sectionId]="sectionData.id"> - + - {{ 'submission.sections.'+sectionData.header | translate + {{ + 'submission.sections.'+sectionData.header | translate }}
- -
-
-

- - -

-
-
diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index 2ec72f7935..aa6aed8bcf 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -1,7 +1,18 @@ - - + -
+
+ + {{'submission.sections.sherpa-policy.title-empty' | translate}} + +
+ +
+
+ + {{'submission.sections.sherpa.publisher.policy.description' | translate}} + +
diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss index e69de29bb2..7aaa67e700 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.scss @@ -0,0 +1,3 @@ +.text-information-section { + flex: 1; +} \ No newline at end of file diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 1f071ceacc..760724b595 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4001,9 +4001,10 @@ "submission.sections.submit.progressbar.upload": "Upload files", + "submission.sections.submit.progressbar.sherpaPolicies": "Publisher open access policy information", - "submission.sections.sherpa-policy.title-empty": "No information available", + "submission.sections.sherpa-policy.title-empty": "No publisher policy information available. If your work has an associated ISSN, please enter it above to see any related publisher open access policies.", "submission.sections.status.errors.title": "Errors", @@ -4134,9 +4135,10 @@ "submission.sections.sherpa.publication.information.zetoPub": "Zeto Pub", - "submission.sections.sherpa.publisher.policy": "Publisher Policy", + "submission.sections.sherpa.publisher.policy.description": "The below information was found via Sherpa Romeo. Based on the policies of your publisher, it provides advice regarding whether an embargo may be necessary and/or which files you are allowed to upload. If you have questions, please contact your site administrator via the feedback form in the footer.", + "submission.sections.sherpa.publisher.policy.openaccess": "Open Access pathways permitted by this journal's policy are listed below by article version. Click on a pathway for a more detailed view", "submission.sections.sherpa.publisher.policy.more.information": "For more information, please see the following links:", From 6db5cff4262f47c054f2b7435a2a7785088d10b6 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Fri, 3 Jun 2022 17:55:13 +0200 Subject: [PATCH 57/75] [CST-5270] fix testing --- .../publisher-policy/publisher-policy.component.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts index da97f824b1..3e2c33481a 100644 --- a/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/publisher-policy/publisher-policy.component.spec.ts @@ -43,7 +43,7 @@ describe('PublisherPolicyComponent', () => { expect(de.query(By.css('ds-content-accordion'))).toBeTruthy(); }); - it('should show 2 rows', () => { - expect(de.queryAll(By.css('.row')).length).toEqual(2); + it('should show 1 row', () => { + expect(de.queryAll(By.css('.row')).length).toEqual(1); }); }); From 377214c9966995f22905224a6ebd844ff7d20b4f Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Mon, 6 Jun 2022 13:20:16 +0200 Subject: [PATCH 58/75] [CST-5270] Finished improvements --- .../container/section-container.component.html | 9 +++++---- .../container/section-container.component.scss | 8 -------- .../section-sherpa-policies.component.html | 14 ++++---------- 3 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/app/submission/sections/container/section-container.component.html b/src/app/submission/sections/container/section-container.component.html index 0d61bb42db..e6ae9d1b9c 100644 --- a/src/app/submission/sections/container/section-container.component.html +++ b/src/app/submission/sections/container/section-container.component.html @@ -3,9 +3,9 @@ [submissionId]="submissionId" [sectionType]="sectionData.sectionType" [sectionId]="sectionData.id"> - + - {{ + {{ 'submission.sections.'+sectionData.header | translate }}
@@ -21,13 +21,14 @@ class="fas fa-check-circle text-success mr-3" title="{{'submission.sections.status.valid.title' | translate}}" role="img" [attr.aria-label]="'submission.sections.status.valid.aria' | translate"> - - + - --> + + -
- - {{'submission.sections.sherpa-policy.title-empty' | translate}} - -
- -
+
{{'submission.sections.sherpa.publisher.policy.description' | translate}}
-
From 692ab040dc7ff471e1839673b04121780f787224 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 6 Jun 2022 15:25:58 +0200 Subject: [PATCH 59/75] [CST-5535] hide empty panels --- .../health-panel/health-panel.component.html | 38 ++++++++++--------- .../health-panel/health-panel.component.ts | 11 +++++- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/app/health-page/health-panel/health-panel.component.html b/src/app/health-page/health-panel/health-panel.component.html index 646f9f98f1..a421357b38 100644 --- a/src/app/health-page/health-panel/health-panel.component.html +++ b/src/app/health-page/health-panel/health-panel.component.html @@ -1,25 +1,27 @@

{{'health-page.status' | translate}} :

- - -
- -
- -
- - + + + +
+ +
+ +
+ + +
-
- - - - - + + + + + + diff --git a/src/app/health-page/health-panel/health-panel.component.ts b/src/app/health-page/health-panel/health-panel.component.ts index 3137334d6f..7a263e6a9c 100644 --- a/src/app/health-page/health-panel/health-panel.component.ts +++ b/src/app/health-page/health-panel/health-panel.component.ts @@ -1,6 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { HealthResponse } from '../models/health-component.model'; import { TranslateService } from '@ngx-translate/core'; +import {isNotEmpty} from '../../shared/empty.util'; @Component({ selector: 'ds-health-panel', @@ -29,7 +30,7 @@ export class HealthPanelComponent implements OnInit { /** * Return translated label if exist for the given property * - * @param property + * @param panelKey */ public getPanelLabel(panelKey: string): string { const translationKey = `health-page.section.${panelKey}.title`; @@ -37,4 +38,12 @@ export class HealthPanelComponent implements OnInit { return (translation === translationKey) ? panelKey : translation; } + + /** + * Check if the current entry has data to be shown + * @param entryValue + */ + showEntryData(entryValue) { + return isNotEmpty(entryValue?.components) || isNotEmpty(entryValue?.details); + } } From 37492f2f82117267e409484d08b7f8691e568ff6 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 6 Jun 2022 15:39:44 +0200 Subject: [PATCH 60/75] [CST-5677] code formatted --- src/app/health-page/health-panel/health-panel.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/health-page/health-panel/health-panel.component.ts b/src/app/health-page/health-panel/health-panel.component.ts index 7a263e6a9c..b4fd6f10ee 100644 --- a/src/app/health-page/health-panel/health-panel.component.ts +++ b/src/app/health-page/health-panel/health-panel.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { HealthResponse } from '../models/health-component.model'; import { TranslateService } from '@ngx-translate/core'; -import {isNotEmpty} from '../../shared/empty.util'; +import { isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-health-panel', From dd049270a7e654098104c07f7ab454bb590707c6 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 6 Jun 2022 15:58:06 +0200 Subject: [PATCH 61/75] [CST-5270] add info alert for section info message --- .../section-sherpa-policies.component.html | 24 +++++++++---------- .../section-sherpa-policies.component.scss | 3 --- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html index 56b28d5eda..4b636ee46e 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.html @@ -1,22 +1,22 @@ -
-
- - {{'submission.sections.sherpa.publisher.policy.description' | translate}} - -
- +
-
+
-
+
-
- -
- - -
+ + +
+ +
+ +
+ +
- - - - - - +
+
+ + + +
diff --git a/src/app/health-page/health-panel/health-panel.component.ts b/src/app/health-page/health-panel/health-panel.component.ts index 7a263e6a9c..3137334d6f 100644 --- a/src/app/health-page/health-panel/health-panel.component.ts +++ b/src/app/health-page/health-panel/health-panel.component.ts @@ -1,7 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { HealthResponse } from '../models/health-component.model'; import { TranslateService } from '@ngx-translate/core'; -import {isNotEmpty} from '../../shared/empty.util'; @Component({ selector: 'ds-health-panel', @@ -30,7 +29,7 @@ export class HealthPanelComponent implements OnInit { /** * Return translated label if exist for the given property * - * @param panelKey + * @param property */ public getPanelLabel(panelKey: string): string { const translationKey = `health-page.section.${panelKey}.title`; @@ -38,12 +37,4 @@ export class HealthPanelComponent implements OnInit { return (translation === translationKey) ? panelKey : translation; } - - /** - * Check if the current entry has data to be shown - * @param entryValue - */ - showEntryData(entryValue) { - return isNotEmpty(entryValue?.components) || isNotEmpty(entryValue?.details); - } } From 09b7d2e52f139575c187581c3ff6ffdd0929406a Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 6 Jun 2022 16:48:31 +0200 Subject: [PATCH 64/75] [CST-5270] fix test --- .../sherpa-policies/section-sherpa-policies.component.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts index fe70f3e068..76a980ed3c 100644 --- a/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts +++ b/src/app/submission/sections/sherpa-policies/section-sherpa-policies.component.spec.ts @@ -17,6 +17,7 @@ import { SubmissionService } from '../../submission.service'; import { DebugElement } from '@angular/core'; import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock'; import { of as observableOf } from 'rxjs'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; describe('SubmissionSectionSherpaPoliciesComponent', () => { let component: SubmissionSectionSherpaPoliciesComponent; @@ -53,6 +54,7 @@ describe('SubmissionSectionSherpaPoliciesComponent', () => { await TestBed.configureTestingModule({ imports: [ BrowserModule, + NoopAnimationsModule, TranslateModule.forRoot({ loader: { provide: TranslateLoader, From 8ac9425db37c95be6980f19cf5c49e74a3737e48 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 6 Jun 2022 16:50:44 +0200 Subject: [PATCH 65/75] [CST-5677] restored empty panels; show message if panel is empty --- .../health-component/health-component.component.html | 3 +++ .../health-component/health-component.component.ts | 3 +++ src/assets/i18n/en.json5 | 1 + 3 files changed, 7 insertions(+) diff --git a/src/app/health-page/health-panel/health-component/health-component.component.html b/src/app/health-page/health-panel/health-component/health-component.component.html index c254f128d9..8879623282 100644 --- a/src/app/health-page/health-panel/health-component/health-component.component.html +++ b/src/app/health-page/health-panel/health-component/health-component.component.html @@ -26,3 +26,6 @@
+ + + diff --git a/src/app/health-page/health-panel/health-component/health-component.component.ts b/src/app/health-page/health-panel/health-component/health-component.component.ts index 54cd3c32b9..758665c5fd 100644 --- a/src/app/health-page/health-panel/health-component/health-component.component.ts +++ b/src/app/health-page/health-panel/health-component/health-component.component.ts @@ -2,6 +2,7 @@ import { Component, Input } from '@angular/core'; import { HealthComponent } from '../../models/health-component.model'; import { TranslateService } from '@ngx-translate/core'; +import { AlertType } from '../../../shared/alert/aletr-type'; @Component({ selector: 'ds-health-component', @@ -20,6 +21,8 @@ export class HealthComponentComponent { */ @Input() healthComponentName: string; + public AlertTypeEnum = AlertType; + /** * A boolean representing if div should start collapsed */ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index b08db095db..6fc9b6e7b3 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1621,6 +1621,7 @@ "health-page.title": "Health", + "health-page.section.no-issues": "No issues detected", "home.description": "", From 73c60e3eef1ff54fd8be71079450b645bf118a87 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 7 Jun 2022 13:05:52 +0200 Subject: [PATCH 66/75] [CST-5307] disable cache when lookup for profile to claim --- src/app/profile-page/profile-claim/profile-claim.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/profile-page/profile-claim/profile-claim.service.ts b/src/app/profile-page/profile-claim/profile-claim.service.ts index ee54d0c3be..ea907ef629 100644 --- a/src/app/profile-page/profile-claim/profile-claim.service.ts +++ b/src/app/profile-page/profile-claim/profile-claim.service.ts @@ -61,7 +61,7 @@ export class ProfileClaimService { return this.searchService.search(new PaginatedSearchOptions({ configuration: 'eperson_claims', query: query - })); + }), null, false, true); } /** From 83fce87792f9fb00e557e00c26909e3cfc73c8c6 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Tue, 7 Jun 2022 15:17:29 +0200 Subject: [PATCH 67/75] [CST-5535] commented code removed --- .../health-info-component.component.html | 29 ------------------- .../health-info/health-info.component.html | 8 ----- 2 files changed, 37 deletions(-) diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.html b/src/app/health-page/health-info/health-info-component/health-info-component.component.html index b607d95f45..dbaaa7a6b6 100644 --- a/src/app/health-page/health-info/health-info-component/health-info-component.component.html +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.html @@ -25,32 +25,3 @@

{{ getPropertyLabel(entry.key) | titlecase }} : {{entry.value}}

- - - - diff --git a/src/app/health-page/health-info/health-info.component.html b/src/app/health-page/health-info/health-info.component.html index 47e4cfb4d2..bc81a17439 100644 --- a/src/app/health-page/health-info/health-info.component.html +++ b/src/app/health-page/health-info/health-info.component.html @@ -22,12 +22,4 @@ - - - - From 9d5aba74997302c1b9d3d7866b973c30b6fa983f Mon Sep 17 00:00:00 2001 From: Giuseppe Date: Tue, 7 Jun 2022 16:02:01 +0200 Subject: [PATCH 68/75] Update alert.component.ts remove union type --- src/app/shared/alert/alert.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/alert/alert.component.ts b/src/app/shared/alert/alert.component.ts index 0fcce39d38..93535d2057 100644 --- a/src/app/shared/alert/alert.component.ts +++ b/src/app/shared/alert/alert.component.ts @@ -33,7 +33,7 @@ export class AlertComponent { /** * The alert type */ - @Input() type: AlertType|string; + @Input() type: AlertType; /** * An event fired when alert is dismissed. From 9ff075b23ec92a778300364fc54dcd4753051614 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Tue, 7 Jun 2022 16:19:52 +0200 Subject: [PATCH 69/75] [CST-5535] TypeDoc added --- .../health-info-component/health-info-component.component.ts | 3 +++ src/app/health-page/health-info/health-info.component.ts | 3 +++ .../health-component/health-component.component.ts | 3 +++ src/app/health-page/health-panel/health-panel.component.ts | 3 +++ .../health-panel/health-status/health-status.component.ts | 3 +++ 5 files changed, 15 insertions(+) diff --git a/src/app/health-page/health-info/health-info-component/health-info-component.component.ts b/src/app/health-page/health-info/health-info-component/health-info-component.component.ts index 159462cd6d..d2cb393f09 100644 --- a/src/app/health-page/health-info/health-info-component/health-info-component.component.ts +++ b/src/app/health-page/health-info/health-info-component/health-info-component.component.ts @@ -3,6 +3,9 @@ import { Component, Input } from '@angular/core'; import { HealthInfoComponent } from '../../models/health-component.model'; import { HealthComponentComponent } from '../../health-panel/health-component/health-component.component'; +/** + * Shows a health info object + */ @Component({ selector: 'ds-health-info-component', templateUrl: './health-info-component.component.html', diff --git a/src/app/health-page/health-info/health-info.component.ts b/src/app/health-page/health-info/health-info.component.ts index d8c629636b..f1aa287474 100644 --- a/src/app/health-page/health-info/health-info.component.ts +++ b/src/app/health-page/health-info/health-info.component.ts @@ -3,6 +3,9 @@ import { Component, Input, OnInit } from '@angular/core'; import { HealthInfoResponse } from '../models/health-component.model'; import { TranslateService } from '@ngx-translate/core'; +/** + * Show the health info + */ @Component({ selector: 'ds-health-info', templateUrl: './health-info.component.html', diff --git a/src/app/health-page/health-panel/health-component/health-component.component.ts b/src/app/health-page/health-panel/health-component/health-component.component.ts index 758665c5fd..4a347f11d5 100644 --- a/src/app/health-page/health-panel/health-component/health-component.component.ts +++ b/src/app/health-page/health-panel/health-component/health-component.component.ts @@ -4,6 +4,9 @@ import { HealthComponent } from '../../models/health-component.model'; import { TranslateService } from '@ngx-translate/core'; import { AlertType } from '../../../shared/alert/aletr-type'; +/** + * Show a health component object + */ @Component({ selector: 'ds-health-component', templateUrl: './health-component.component.html', diff --git a/src/app/health-page/health-panel/health-panel.component.ts b/src/app/health-page/health-panel/health-panel.component.ts index 3137334d6f..4c74a9b6db 100644 --- a/src/app/health-page/health-panel/health-panel.component.ts +++ b/src/app/health-page/health-panel/health-panel.component.ts @@ -2,6 +2,9 @@ import { Component, Input, OnInit } from '@angular/core'; import { HealthResponse } from '../models/health-component.model'; import { TranslateService } from '@ngx-translate/core'; +/** + * Show the health panel + */ @Component({ selector: 'ds-health-panel', templateUrl: './health-panel.component.html', diff --git a/src/app/health-page/health-panel/health-status/health-status.component.ts b/src/app/health-page/health-panel/health-status/health-status.component.ts index 9285483a97..19f83713fc 100644 --- a/src/app/health-page/health-panel/health-status/health-status.component.ts +++ b/src/app/health-page/health-panel/health-status/health-status.component.ts @@ -1,6 +1,9 @@ import { Component, Input } from '@angular/core'; import { HealthStatus } from '../../models/health-component.model'; +/** + * Show a health status object + */ @Component({ selector: 'ds-health-status', templateUrl: './health-status.component.html', From 12f073bdbe0642adb4a6d11241e4b01d4404c6ae Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 8 Jun 2022 11:20:32 +0200 Subject: [PATCH 70/75] [CST-5307] Fix issue with person-search-result-list-element.component that sometimes shown undefined name --- .../person/person-search-result-list-element.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.html index c8a9ea9e28..6d9cfe10c4 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.html +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.html @@ -5,7 +5,7 @@ [innerHTML]="name">
+ [innerHTML]="name"> Date: Wed, 8 Jun 2022 11:52:01 +0200 Subject: [PATCH 71/75] [CST-5535] Add component typedoc and optimize code --- .../health-info/health-info.component.html | 2 +- .../health-page/health-info/health-info.component.ts | 12 +++++++++--- .../health-component/health-component.component.html | 1 - .../health-component/health-component.component.ts | 9 +++++++-- .../health-panel/health-panel.component.html | 2 +- .../health-panel/health-panel.component.ts | 6 ++++-- 6 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/app/health-page/health-info/health-info.component.html b/src/app/health-page/health-info/health-info.component.html index bc81a17439..4bafcaa2d8 100644 --- a/src/app/health-page/health-info/health-info.component.html +++ b/src/app/health-page/health-info/health-info.component.html @@ -4,7 +4,7 @@
diff --git a/src/app/health-page/health-info/health-info.component.ts b/src/app/health-page/health-info/health-info.component.ts index f1aa287474..186d00299c 100644 --- a/src/app/health-page/health-info/health-info.component.ts +++ b/src/app/health-page/health-info/health-info.component.ts @@ -1,10 +1,15 @@ import { Component, Input, OnInit } from '@angular/core'; -import { HealthInfoResponse } from '../models/health-component.model'; import { TranslateService } from '@ngx-translate/core'; +import { HealthInfoResponse } from '../models/health-component.model'; + /** - * Show the health info + * A component to render a "health-info component" object. + * + * Note that the word "component" in "health-info component" doesn't refer to Angular use of the term + * but rather to the components used in the response of the health endpoint of Spring's Actuator + * API. */ @Component({ selector: 'ds-health-info', @@ -26,10 +31,11 @@ export class HealthInfoComponent implements OnInit { ngOnInit(): void { this.activeId = Object.keys(this.healthInfoResponse)[0]; } + /** * Return translated label if exist for the given property * - * @param property + * @param panelKey */ public getPanelLabel(panelKey: string): string { const translationKey = `health-page.section-info.${panelKey}.title`; diff --git a/src/app/health-page/health-panel/health-component/health-component.component.html b/src/app/health-page/health-panel/health-component/health-component.component.html index 8879623282..1f29c8c9fc 100644 --- a/src/app/health-page/health-panel/health-component/health-component.component.html +++ b/src/app/health-page/health-panel/health-component/health-component.component.html @@ -23,7 +23,6 @@

{{ getPropertyLabel(item.key) | titlecase }} : {{item.value}}

-
diff --git a/src/app/health-page/health-panel/health-component/health-component.component.ts b/src/app/health-page/health-panel/health-component/health-component.component.ts index 4a347f11d5..e212a07289 100644 --- a/src/app/health-page/health-panel/health-component/health-component.component.ts +++ b/src/app/health-page/health-panel/health-component/health-component.component.ts @@ -1,11 +1,16 @@ import { Component, Input } from '@angular/core'; -import { HealthComponent } from '../../models/health-component.model'; import { TranslateService } from '@ngx-translate/core'; + +import { HealthComponent } from '../../models/health-component.model'; import { AlertType } from '../../../shared/alert/aletr-type'; /** - * Show a health component object + * A component to render a "health component" object. + * + * Note that the word "component" in "health component" doesn't refer to Angular use of the term + * but rather to the components used in the response of the health endpoint of Spring's Actuator + * API. */ @Component({ selector: 'ds-health-component', diff --git a/src/app/health-page/health-panel/health-panel.component.html b/src/app/health-page/health-panel/health-panel.component.html index 646f9f98f1..2d67fa537b 100644 --- a/src/app/health-page/health-panel/health-panel.component.html +++ b/src/app/health-page/health-panel/health-panel.component.html @@ -4,7 +4,7 @@
diff --git a/src/app/health-page/health-panel/health-panel.component.ts b/src/app/health-page/health-panel/health-panel.component.ts index 4c74a9b6db..1c056daf20 100644 --- a/src/app/health-page/health-panel/health-panel.component.ts +++ b/src/app/health-page/health-panel/health-panel.component.ts @@ -1,7 +1,9 @@ import { Component, Input, OnInit } from '@angular/core'; -import { HealthResponse } from '../models/health-component.model'; + import { TranslateService } from '@ngx-translate/core'; +import { HealthResponse } from '../models/health-component.model'; + /** * Show the health panel */ @@ -32,7 +34,7 @@ export class HealthPanelComponent implements OnInit { /** * Return translated label if exist for the given property * - * @param property + * @param panelKey */ public getPanelLabel(panelKey: string): string { const translationKey = `health-page.section.${panelKey}.title`; From e2abea7373081c78cd0c64ff15a85612dc92074f Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 8 Jun 2022 12:56:57 +0200 Subject: [PATCH 72/75] [CST-5307] Add useCachedVersionIfAvailable and reRequestOnStale param to isAuthorized method --- .../authorization-data.service.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/app/core/data/feature-authorization/authorization-data.service.ts b/src/app/core/data/feature-authorization/authorization-data.service.ts index 1f8c8b2284..7b4e451d2e 100644 --- a/src/app/core/data/feature-authorization/authorization-data.service.ts +++ b/src/app/core/data/feature-authorization/authorization-data.service.ts @@ -60,14 +60,18 @@ export class AuthorizationDataService extends DataService { /** * Checks if an {@link EPerson} (or anonymous) has access to a specific object within a {@link Feature} - * @param objectUrl URL to the object to search {@link Authorization}s for. - * If not provided, the repository's {@link Site} will be used. - * @param ePersonUuid UUID of the {@link EPerson} to search {@link Authorization}s for. - * If not provided, the UUID of the currently authenticated {@link EPerson} will be used. - * @param featureId ID of the {@link Feature} to check {@link Authorization} for + * @param objectUrl URL to the object to search {@link Authorization}s for. + * If not provided, the repository's {@link Site} will be used. + * @param ePersonUuid UUID of the {@link EPerson} to search {@link Authorization}s for. + * If not provided, the UUID of the currently authenticated {@link EPerson} will be used. + * @param featureId ID of the {@link Feature} to check {@link Authorization} for + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale */ - isAuthorized(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string): Observable { - return this.searchByObject(featureId, objectUrl, ePersonUuid, {}, true, true, followLink('feature')).pipe( + isAuthorized(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string, useCachedVersionIfAvailable = true, reRequestOnStale = true): Observable { + return this.searchByObject(featureId, objectUrl, ePersonUuid, {}, useCachedVersionIfAvailable, useCachedVersionIfAvailable, followLink('feature')).pipe( getFirstCompletedRemoteData(), map((authorizationRD) => { if (authorizationRD.statusCode !== 401 && hasValue(authorizationRD.payload) && isNotEmpty(authorizationRD.payload.page)) { From 8bdb4b7be3cbaa5594115b49110a08afd1ff5d8b Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 8 Jun 2022 12:58:19 +0200 Subject: [PATCH 73/75] [CST-5307] disable cache when checking CanClaimItem authorization feature --- .../person-page-claim-button.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts index f317b88c8d..903b9d3679 100644 --- a/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts +++ b/src/app/shared/dso-page/person-page-claim-button/person-page-claim-button.component.ts @@ -40,7 +40,7 @@ export class PersonPageClaimButtonComponent implements OnInit { } ngOnInit(): void { - this.authorizationService.isAuthorized(FeatureID.CanClaimItem, this.object._links.self.href).pipe( + this.authorizationService.isAuthorized(FeatureID.CanClaimItem, this.object._links.self.href, null, false).pipe( take(1) ).subscribe((isAuthorized: boolean) => { this.claimable$.next(isAuthorized); From 485cc2bd31d8b360f13f25e9f1a08b58313eaee1 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Wed, 8 Jun 2022 15:34:58 +0200 Subject: [PATCH 74/75] Document AppComponent.updateTheme --- src/app/app.component.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 818a38d34f..41e09f22ed 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -228,6 +228,12 @@ export class AppComponent implements OnInit, AfterViewInit { }); } + /** + * Update the theme according to the current route, if applicable. + * @param urlAfterRedirects the current URL after redirects + * @param snapshot the current route snapshot + * @private + */ private updateTheme(urlAfterRedirects: string, snapshot: ActivatedRouteSnapshot): void { this.themeService.updateThemeOnRouteChange$(urlAfterRedirects, snapshot).pipe( switchMap((changed) => { From ce9e81152655e8a98306864c8f171b1f3cba11cb Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 9 Jun 2022 09:40:48 +0200 Subject: [PATCH 75/75] [CST-5307] fix use of wrong param --- .../data/feature-authorization/authorization-data.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/data/feature-authorization/authorization-data.service.ts b/src/app/core/data/feature-authorization/authorization-data.service.ts index 7b4e451d2e..f27919844d 100644 --- a/src/app/core/data/feature-authorization/authorization-data.service.ts +++ b/src/app/core/data/feature-authorization/authorization-data.service.ts @@ -71,7 +71,7 @@ export class AuthorizationDataService extends DataService { * requested after the response becomes stale */ isAuthorized(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string, useCachedVersionIfAvailable = true, reRequestOnStale = true): Observable { - return this.searchByObject(featureId, objectUrl, ePersonUuid, {}, useCachedVersionIfAvailable, useCachedVersionIfAvailable, followLink('feature')).pipe( + return this.searchByObject(featureId, objectUrl, ePersonUuid, {}, useCachedVersionIfAvailable, reRequestOnStale, followLink('feature')).pipe( getFirstCompletedRemoteData(), map((authorizationRD) => { if (authorizationRD.statusCode !== 401 && hasValue(authorizationRD.payload) && isNotEmpty(authorizationRD.payload.page)) {