From c5110f89bc17290b6e224f9366cfa14dfa431d58 Mon Sep 17 00:00:00 2001 From: reetagithub <51482276+reetagithub@users.noreply.github.com> Date: Thu, 24 Mar 2022 11:18:18 +0200 Subject: [PATCH 001/151] Update fi.json5 Modified some keys to harmonize the translations of term 'administrative'. Also, translated "Administer Workflow" to a verb instead of a noun. --- src/assets/i18n/fi.json5 | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/assets/i18n/fi.json5 b/src/assets/i18n/fi.json5 index 02f020a45d..860062fa67 100644 --- a/src/assets/i18n/fi.json5 +++ b/src/assets/i18n/fi.json5 @@ -107,7 +107,7 @@ "admin.registries.bitstream-formats.edit.head": "Tiedostoformaatti: {{ format }}", // "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are hidden from the user, and used for administrative purposes.", - "admin.registries.bitstream-formats.edit.internal.hint": "Sisäisiksi merkittyjä formaatteja käytetään hallinnollisiin tarkoituksiin, ja ne on piilotettu käyttäjiltä.", + "admin.registries.bitstream-formats.edit.internal.hint": "Sisäisiksi merkittyjä formaatteja käytetään ylläpitotarkoituksiin, ja ne on piilotettu käyttäjiltä.", // "admin.registries.bitstream-formats.edit.internal.label": "Internal", "admin.registries.bitstream-formats.edit.internal.label": "Sisäinen", @@ -662,7 +662,7 @@ // "admin.search.breadcrumbs": "Administrative Search", - "admin.search.breadcrumbs": "Hallinnollinen haku", + "admin.search.breadcrumbs": "Ylläpitäjän haku", // "admin.search.collection.edit": "Edit", "admin.search.collection.edit": "Muokkaa", @@ -692,19 +692,19 @@ "admin.search.item.withdraw": "Poista käytöstä", // "admin.search.title": "Administrative Search", - "admin.search.title": "Hallinnollinen haku", + "admin.search.title": "Ylläpitäjän haku", // "administrativeView.search.results.head": "Administrative Search", - "administrativeView.search.results.head": "Hallinnollinen haku", + "administrativeView.search.results.head": "Ylläpitäjän haku", // "admin.workflow.breadcrumbs": "Administer Workflow", - "admin.workflow.breadcrumbs": "Hallinnointityönkulku", + "admin.workflow.breadcrumbs": "Hallinnoi työnkulkua", // "admin.workflow.title": "Administer Workflow", - "admin.workflow.title": "Hallinnointityönkulku", + "admin.workflow.title": "Hallinnoi työnkulkua", // "admin.workflow.item.workflow": "Workflow", "admin.workflow.item.workflow": "Työnkulku", @@ -2954,7 +2954,7 @@ // "menu.section.admin_search": "Admin Search", - "menu.section.admin_search": "Admin-haku", + "menu.section.admin_search": "Ylläpitäjän haku", @@ -3033,7 +3033,7 @@ "menu.section.icon.access_control": "Pääsyoikeudet", // "menu.section.icon.admin_search": "Admin search menu section", - "menu.section.icon.admin_search": "Admin-haku", + "menu.section.icon.admin_search": "Ylläpitäjän haku", // "menu.section.icon.control_panel": "Control Panel menu section", "menu.section.icon.control_panel": "Hallintapaneeli", @@ -3168,7 +3168,7 @@ // "menu.section.workflow": "Administer Workflow", - "menu.section.workflow": "Hallinnointityönkulku", + "menu.section.workflow": "Hallinnoi työnkulkua", // "mydspace.description": "", @@ -5079,7 +5079,7 @@ // "workflowAdmin.search.results.head": "Administer Workflow", - "workflowAdmin.search.results.head": "Hallinnointityönkulku", + "workflowAdmin.search.results.head": "Hallinnoi työnkulkua", From 46a0ea10f40539463abb05fbcbbe1aa8da62f444 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Fri, 25 Mar 2022 18:28:26 +0530 Subject: [PATCH 002/151] [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 003/151] [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 004/151] [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 005/151] [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 88c324cb5a57af424a113b6780ea1ef3f0397b22 Mon Sep 17 00:00:00 2001 From: Pratik Rajkotiya Date: Tue, 12 Apr 2022 17:04:32 +0530 Subject: [PATCH 006/151] [CST-5307] Migrate Researcher Profile (Angular). --- angular.json | 1 + package.json | 3 +- src/app/core/breadcrumbs/dso-name.service.ts | 10 +- src/app/core/core.module.ts | 8 +- .../profile/model/researcher-profile.model.ts | 49 ++++ .../model/researcher-profile.resource-type.ts | 9 + .../profile/researcher-profile.service.ts | 267 ++++++++++++++++++ .../profile-claim/profile-claim.service.ts | 76 +++++ ...rofile-page-researcher-form.component.html | 35 +++ ...ile-page-researcher-form.component.spec.ts | 162 +++++++++++ .../profile-page-researcher-form.component.ts | 181 ++++++++++++ .../profile-page-security-form.component.html | 2 +- .../profile-page/profile-page.component.html | 9 + src/app/profile-page/profile-page.module.ts | 14 +- .../claim-item-selector.component.html | 37 +++ .../claim-item-selector.component.spec.ts | 45 +++ .../claim-item-selector.component.ts | 68 +++++ src/app/shared/shared.module.ts | 4 + src/assets/i18n/en.json5 | 46 ++- src/styles/_global-styles.scss | 6 + yarn.lock | 5 + 21 files changed, 1027 insertions(+), 10 deletions(-) create mode 100644 src/app/core/profile/model/researcher-profile.model.ts create mode 100644 src/app/core/profile/model/researcher-profile.resource-type.ts create mode 100644 src/app/core/profile/researcher-profile.service.ts create mode 100644 src/app/profile-page/profile-claim/profile-claim.service.ts create mode 100644 src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html create mode 100644 src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts create mode 100644 src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.html create mode 100644 src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.spec.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.ts diff --git a/angular.json b/angular.json index a0a4cd8ea1..aaa7cdb199 100644 --- a/angular.json +++ b/angular.json @@ -48,6 +48,7 @@ ], "styles": [ "src/styles/startup.scss", + "./node_modules/ngx-ui-switch/ui-switch.component.css", { "input": "src/styles/base-theme.scss", "inject": false, diff --git a/package.json b/package.json index 5e98af53dd..4ba231de9f 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,8 @@ "url-parse": "^1.5.6", "uuid": "^8.3.2", "webfontloader": "1.6.28", - "zone.js": "^0.10.3" + "zone.js": "^0.10.3", + "ngx-ui-switch": "^11.0.1" }, "devDependencies": { "@angular-builders/custom-webpack": "10.0.1", diff --git a/src/app/core/breadcrumbs/dso-name.service.ts b/src/app/core/breadcrumbs/dso-name.service.ts index 38363d1989..02ead1615c 100644 --- a/src/app/core/breadcrumbs/dso-name.service.ts +++ b/src/app/core/breadcrumbs/dso-name.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { hasValue } from '../../shared/empty.util'; +import { hasValue, isEmpty } from '../../shared/empty.util'; import { DSpaceObject } from '../shared/dspace-object.model'; import { TranslateService } from '@ngx-translate/core'; @@ -27,7 +27,13 @@ export class DSONameService { */ private readonly factories = { Person: (dso: DSpaceObject): string => { - return `${dso.firstMetadataValue('person.familyName')}, ${dso.firstMetadataValue('person.givenName')}`; + const familyName = dso.firstMetadataValue('person.familyName'); + const givenName = dso.firstMetadataValue('person.givenName'); + if (isEmpty(familyName) && isEmpty(givenName)) { + return dso.firstMetadataValue('dc.title') || dso.name; + } else { + return `${familyName}, ${givenName}`; + } }, OrgUnit: (dso: DSpaceObject): string => { return dso.firstMetadataValue('organization.legalName'); diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 8d8a614a89..7e97c78b3b 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -162,6 +162,9 @@ import { SearchConfig } from './shared/search/search-filters/search-config.model import { SequenceService } from './shared/sequence.service'; import { GroupDataService } from './eperson/group-data.service'; import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model'; +import { ResearcherProfileService } from './profile/researcher-profile.service'; +import { ProfileClaimService } from '../profile-page/profile-claim/profile-claim.service'; +import { ResearcherProfile } from './profile/model/researcher-profile.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -286,6 +289,8 @@ const PROVIDERS = [ SequenceService, GroupDataService, FeedbackDataService, + ResearcherProfileService, + ProfileClaimService ]; /** @@ -345,7 +350,8 @@ export const models = UsageReport, Root, SearchConfig, - SubmissionAccessesModel + SubmissionAccessesModel, + ResearcherProfile ]; @NgModule({ diff --git a/src/app/core/profile/model/researcher-profile.model.ts b/src/app/core/profile/model/researcher-profile.model.ts new file mode 100644 index 0000000000..1a9e75cbf6 --- /dev/null +++ b/src/app/core/profile/model/researcher-profile.model.ts @@ -0,0 +1,49 @@ +import { autoserialize, deserialize, deserializeAs } from 'cerialize'; +import { typedObject } from '../../cache/builders/build-decorators'; +import { CacheableObject } from '../../cache/object-cache.reducer'; +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'; + +/** + * Class the represents a Researcher Profile. + */ +@typedObject +export class ResearcherProfile extends CacheableObject { + + static type = RESEARCHER_PROFILE; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The identifier of this Researcher Profile + */ + @autoserialize + id: string; + + @deserializeAs('id') + uuid: string; + + /** + * The visibility of this Researcher Profile + */ + @autoserialize + visible: boolean; + + /** + * The {@link HALLink}s for this Researcher Profile + */ + @deserialize + _links: { + self: HALLink, + item: HALLink, + eperson: HALLink + }; + +} diff --git a/src/app/core/profile/model/researcher-profile.resource-type.ts b/src/app/core/profile/model/researcher-profile.resource-type.ts new file mode 100644 index 0000000000..bfed441b0d --- /dev/null +++ b/src/app/core/profile/model/researcher-profile.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../../shared/resource-type'; + +/** + * The resource type for ResearcherProfile + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const RESEARCHER_PROFILE = new ResourceType('profile'); diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts new file mode 100644 index 0000000000..c1f8952d39 --- /dev/null +++ b/src/app/core/profile/researcher-profile.service.ts @@ -0,0 +1,267 @@ +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 { 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 { CoreState } from '../core.reducers'; +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, + getFirstCompletedRemoteData, + getFirstSucceededRemoteDataPayload +} 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'; +import { PostRequest } from '../data/request.models'; +import { hasValue } from '../../shared/empty.util'; + +/* tslint:disable:max-classes-per-file */ + +/** + * A private DataService implementation to delegate specific methods to. + */ +class ResearcherProfileServiceImpl extends DataService { + 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(); + } + +} + +/** + * A service that provides methods to make REST requests with researcher profile endpoint. + */ +@Injectable() +@dataService(RESEARCHER_PROFILE) +export class ResearcherProfileService { + + dataService: ResearcherProfileServiceImpl; + + 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 ) { + + this.dataService = new ResearcherProfileServiceImpl(requestService, rdbService, store, 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)); + } + + /** + * Create a new researcher profile for the current user. + */ + 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); + } + }), + map((response: RemoteData) => response.isSuccess) + ); + } + + /** + * 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 + */ + setVisibility(researcherProfile: ResearcherProfile, visible: boolean): Observable { + + 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); + } + + /** + * Check if the given item is linked to an ORCID profile. + * + * @param item the item to check + * @returns the check result + */ + isLinkedToOrcid(item: Item): boolean { + return item.hasMetadata('cris.orcid.authenticated'); + } + + /** + * Returns true if only the admin users can disconnect a researcher profile from ORCID. + * + * @returns the check result + */ + onlyAdminCanDisconnectProfileFromOrcid(): Observable { + return this.getOrcidDisconnectionAllowedUsersConfiguration().pipe( + map((property) => property.values.map( (value) => value.toLowerCase()).includes('only_admin')) + ); + } + + /** + * Returns true if the profile's owner can disconnect that profile from ORCID. + * + * @returns the check result + */ + ownerCanDisconnectProfileFromOrcid(): Observable { + return this.getOrcidDisconnectionAllowedUsersConfiguration().pipe( + map((property) => { + const values = property.values.map( (value) => value.toLowerCase()); + return values.includes('only_owner') || values.includes('admin_and_owner'); + }) + ); + } + + /** + * Returns true if the admin users can disconnect a researcher profile from ORCID. + * + * @returns the check result + */ + adminCanDisconnectProfileFromOrcid(): Observable { + return this.getOrcidDisconnectionAllowedUsersConfiguration().pipe( + map((property) => { + const values = property.values.map( (value) => value.toLowerCase()); + return values.includes('only_admin') || values.includes('admin_and_owner'); + }) + ); + } + + /** + * If the given item represents a profile unlink it from ORCID. + */ + unlinkOrcid(item: Item): Observable> { + + const operations: RemoveOperation[] = [{ + path:'/orcid', + op:'remove' + }]; + + return this.findById(item.firstMetadata('cris.owner').authority).pipe( + switchMap((profile) => this.patch(profile, operations)), + getFinishedRemoteData() + ); + } + + getOrcidAuthorizeUrl(profile: Item): Observable { + return combineLatest([ + this.configurationService.findByPropertyName('orcid.authorize-url').pipe(getFirstSucceededRemoteDataPayload()), + this.configurationService.findByPropertyName('orcid.application-client-id').pipe(getFirstSucceededRemoteDataPayload()), + this.configurationService.findByPropertyName('orcid.scope').pipe(getFirstSucceededRemoteDataPayload())] + ).pipe( + map(([authorizeUrl, clientId, scopes]) => { + const redirectUri = environment.rest.baseUrl + '/api/cris/orcid/' + profile.id + '/?url=' + encodeURIComponent(this.router.url); + return authorizeUrl.values[0] + '?client_id=' + clientId.values[0] + '&redirect_uri=' + redirectUri + '&response_type=code&scope=' + + scopes.values.join(' '); + })); + } + + /** + * Creates a researcher profile starting from an external source URI + * @param sourceUri URI of source item of researcher profile. + */ + public createFromExternalSource(sourceUri: string): Observable> { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + + const requestId = this.requestService.generateRequestId(); + 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(); + + return this.rdbService.buildFromRequestUUID(requestId); + } + + private getOrcidDisconnectionAllowedUsersConfiguration(): Observable { + return this.configurationService.findByPropertyName('orcid.disconnection.allowed-users').pipe( + getFirstSucceededRemoteDataPayload() + ); + } + +} diff --git a/src/app/profile-page/profile-claim/profile-claim.service.ts b/src/app/profile-page/profile-claim/profile-claim.service.ts new file mode 100644 index 0000000000..f1841ac0b5 --- /dev/null +++ b/src/app/profile-page/profile-claim/profile-claim.service.ts @@ -0,0 +1,76 @@ +import { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { mergeMap, switchMap, take } from 'rxjs/operators'; +import { ConfigurationDataService } from '../../core/data/configuration-data.service'; +import { PaginatedList } from '../../core/data/paginated-list.model'; +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 { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; +import { SearchResult } from '../../shared/search/models/search-result.model'; +import { getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from './../../core/shared/operators'; + +@Injectable() +export class ProfileClaimService { + + constructor(private searchService: SearchService, + private configurationService: ConfigurationDataService) { + } + + canClaimProfiles(eperson: EPerson): Observable { + + const query = this.personQueryData(eperson); + + if (!hasValue(query) || query.length === 0) { + return of(false); + } + + return this.configurationService.findByPropertyName('claimable.entityType').pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((claimableTypes) => { + if (!claimableTypes.values || claimableTypes.values.length === 0) { + return of(false); + } else { + return this.lookup(query).pipe( + mergeMap((rd: RemoteData>>) => of(rd.payload.totalElements > 0)) + ); + } + }) + ); + } + + search(eperson: EPerson): Observable>>> { + const query = this.personQueryData(eperson); + if (!hasValue(query) || query.length === 0) { + return of(null); + } + return this.lookup(query); + } + + private lookup(query: string): Observable>>> { + if (!hasValue(query)) { + return of(null); + } + return this.searchService.search(new PaginatedSearchOptions({ + configuration: 'eperson_claims', + query: query + })) + .pipe( + getFirstSucceededRemoteData(), + take(1)); + } + + private personQueryData(eperson: EPerson): string { + const querySections = []; + this.queryParam(querySections, 'dc.title', eperson.name); + this.queryParam(querySections, 'crisrp.name', eperson.name); + return querySections.join(' OR '); + } + + private queryParam(query: string[], metadata: string, value: string) { + if (!hasValue(value)) {return;} + query.push(metadata + ':' + value); + } +} diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html new file mode 100644 index 0000000000..b2d53ea0e3 --- /dev/null +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html @@ -0,0 +1,35 @@ +
+
+

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

+

+ {{'researcher.profile.status' | translate}} + +

+
+
+

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

+
+ + + + + +
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 new file mode 100644 index 0000000000..bacb3469ad --- /dev/null +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts @@ -0,0 +1,162 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { TranslateModule } from '@ngx-translate/core'; + +import { of as observableOf } from 'rxjs'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; + +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 { VarDirective } from '../../shared/utils/var.directive'; +import { ProfilePageResearcherFormComponent } from './profile-page-researcher-form.component'; +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 { EditItemDataService } from '../../core/submission/edititem-data.service'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { EditItemMode } from '../../core/submission/models/edititem-mode.model'; +import { EditItem } from '../../core/submission/models/edititem.model'; +import { createPaginatedList } from '../../shared/testing/utils.test'; + +describe('ProfilePageResearcherFormComponent', () => { + + let component: ProfilePageResearcherFormComponent; + let fixture: ComponentFixture; + let router: Router; + + let user: EPerson; + let profile: ResearcherProfile; + + let researcherProfileService: ResearcherProfileService; + + let notificationsServiceStub: NotificationsServiceStub; + + let profileClaimService: ProfileClaimService; + + let authService: AuthService; + + let editItemDataService: any; + + const editItemMode: EditItemMode = Object.assign(new EditItemMode(), { + name: 'test', + label: 'test' + }); + + const editItem: EditItem = Object.assign(new EditItem(), { + modes: createSuccessfulRemoteDataObject$(createPaginatedList([editItemMode])) + }); + + function init() { + + user = Object.assign(new EPerson(), { + id: 'beef9946-f4ce-479e-8f11-b90cbe9f7241' + }); + + profile = Object.assign(new ResearcherProfile(), { + id: 'beef9946-f4ce-479e-8f11-b90cbe9f7241', + visible: false, + type: 'profile' + }); + + authService = jasmine.createSpyObj('authService', { + getAuthenticatedUserFromStore: observableOf(user) + }); + + researcherProfileService = jasmine.createSpyObj('researcherProfileService', { + findById: observableOf(profile), + create: observableOf(profile), + setVisibility: observableOf(profile), + delete: observableOf(true), + findRelatedItemId: observableOf('a42557ca-cbb8-4442-af9c-3bb5cad2d075') + }); + + notificationsServiceStub = new NotificationsServiceStub(); + + profileClaimService = jasmine.createSpyObj('profileClaimService', { + canClaimProfiles: observableOf(false), + }); + + editItemDataService = jasmine.createSpyObj('EditItemDataService', { + findById: createSuccessfulRemoteDataObject$(editItem) + }); + + } + + beforeEach(waitForAsync(() => { + init(); + TestBed.configureTestingModule({ + declarations: [ProfilePageResearcherFormComponent, VarDirective], + imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], + providers: [ + NgbModal, + { provide: ResearcherProfileService, useValue: researcherProfileService }, + { provide: NotificationsService, useValue: notificationsServiceStub }, + { provide: ProfileClaimService, useValue: profileClaimService }, + { provide: AuthService, useValue: authService }, + { provide: EditItemDataService, useValue: editItemDataService } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProfilePageResearcherFormComponent); + component = fixture.componentInstance; + component.user = user; + router = TestBed.inject(Router); + fixture.detectChanges(); + }); + + it('should search the researcher profile for the current user', () => { + expect(researcherProfileService.findById).toHaveBeenCalledWith(user.id); + }); + + describe('createProfile', () => { + + it('should create the profile', () => { + component.createProfile(); + expect(researcherProfileService.create).toHaveBeenCalledWith(); + }); + + }); + + describe('toggleProfileVisibility', () => { + + it('should set the profile visibility to true', () => { + profile.visible = false; + component.toggleProfileVisibility(profile); + expect(researcherProfileService.setVisibility).toHaveBeenCalledWith(profile, true); + }); + + it('should set the profile visibility to false', () => { + profile.visible = true; + component.toggleProfileVisibility(profile); + expect(researcherProfileService.setVisibility).toHaveBeenCalledWith(profile, false); + }); + + }); + + describe('deleteProfile', () => { + + it('should delete the profile', () => { + component.deleteProfile(profile); + expect(researcherProfileService.delete).toHaveBeenCalledWith(profile); + }); + + }); + + describe('viewProfile', () => { + + it('should open the item details page', () => { + spyOn(router, 'navigate'); + component.viewProfile(profile); + expect(router.navigate).toHaveBeenCalledWith(['items', 'a42557ca-cbb8-4442-af9c-3bb5cad2d075']); + }); + + }); + +}); 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 new file mode 100644 index 0000000000..6a0b687afa --- /dev/null +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts @@ -0,0 +1,181 @@ +import { Component, Input, OnInit } from '@angular/core'; +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 { getFirstCompletedRemoteData } 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', + templateUrl: './profile-page-researcher-form.component.html', +}) +/** + * Component for a user to create/delete or change his researcher profile. + */ +export class ProfilePageResearcherFormComponent implements OnInit { + + /** + * The user to display the form for. + */ + @Input() user: EPerson; + + /** + * The researcher profile to show. + */ + researcherProfile$: BehaviorSubject = new BehaviorSubject(null); + + /** + * A boolean representing if a delete operation is pending + * @type {BehaviorSubject} + */ + processingDelete$: BehaviorSubject = new BehaviorSubject(false); + + /** + * A boolean representing if a create delete operation is pending + * @type {BehaviorSubject} + */ + processingCreate$: BehaviorSubject = new BehaviorSubject(false); + + /** + * If exists The uuid of the item associated to the researcher profile + */ + researcherProfileItemId: string; + + constructor(protected researcherProfileService: ResearcherProfileService, + protected profileClaimService: ProfileClaimService, + protected translationService: TranslateService, + protected notificationService: NotificationsService, + protected authService: AuthService, + protected router: Router, + protected modalService: NgbModal) { + + } + + /** + * Initialize the component searching the current user researcher profile. + */ + ngOnInit(): void { + // Retrieve researcherProfile if exists + this.initResearchProfile(); + } + + /** + * Create a new profile for the current user. + */ + createProfile(): void { + this.processingCreate$.next(true); + + this.authService.getAuthenticatedUserFromStore().pipe( + switchMap((currentUser) => this.profileClaimService.canClaimProfiles(currentUser))) + .subscribe((canClaimProfiles) => { + + if (canClaimProfiles) { + this.processingCreate$.next(false); + const modal = this.modalService.open(ClaimItemSelectorComponent); + modal.componentInstance.dso = this.user; + modal.componentInstance.create.pipe(take(1)).subscribe(() => { + this.createProfileFromScratch(); + }); + } else { + this.createProfileFromScratch(); + } + + }); + } + + /** + * Navigate to the items section to show the profile item details. + * + * @param researcherProfile the current researcher profile + */ + viewProfile(researcherProfile: ResearcherProfile): void { + if (this.researcherProfileItemId != null) { + this.router.navigate(['items', this.researcherProfileItemId]); + } + } + + /** + * Delete the given researcher profile. + * + * @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); + }); + } + + /** + * Toggle the visibility of the given researcher profile. + * + * @param researcherProfile the profile to update + */ + toggleProfileVisibility(researcherProfile: ResearcherProfile): void { + /* tslint:disable:no-empty */ + this.researcherProfileService.setVisibility(researcherProfile, !researcherProfile.visible) + .subscribe((updatedProfile) => {}); // this.researcherProfile$.next(updatedProfile); + /* tslint:enable:no-empty */ + } + + /** + * Return a boolean representing if a delete operation is pending. + * + * @return {Observable} + */ + isProcessingDelete(): Observable { + return this.processingDelete$.asObservable(); + } + + /** + * Return a boolean representing if a create operation is pending. + * + * @return {Observable} + */ + isProcessingCreate(): Observable { + return this.processingCreate$.asObservable(); + } + + createProfileFromScratch() { + this.processingCreate$.next(true); + this.researcherProfileService.create().pipe( + getFirstCompletedRemoteData() + ).subscribe((remoteData) => { + this.processingCreate$.next(false); + if (remoteData.isSuccess) { + this.initResearchProfile(); + this.notificationService.success(this.translationService.get('researcher.profile.create.success')); + } else { + this.notificationService.error(this.translationService.get('researcher.profile.create.fail')); + } + }); + } + + private initResearchProfile(): void { + this.researcherProfileService.findById(this.user.id).pipe( + take(1), + filter((researcherProfile) => isNotEmpty(researcherProfile)), + tap((researcherProfile) => this.researcherProfile$.next(researcherProfile)), + mergeMap((researcherProfile) => this.researcherProfileService.findRelatedItemId(researcherProfile)), + ).subscribe((itemId: string) => { + this.researcherProfileItemId = itemId; + }); + } + +} diff --git a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.html b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.html index cdaa3ce31c..7c1dff5bdf 100644 --- a/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.html +++ b/src/app/profile-page/profile-page-security-form/profile-page-security-form.component.html @@ -1,4 +1,4 @@ -
{{FORM_PREFIX + 'info' | translate}}
+{{FORM_PREFIX + 'info' | translate}}

{{'profile.head' | translate}}

+
+
{{'profile.card.researcher' | translate}}
+
+
+ +
+ +
+
{{'profile.card.identify' | translate}}
diff --git a/src/app/profile-page/profile-page.module.ts b/src/app/profile-page/profile-page.module.ts index dc9595140b..83baec12dc 100644 --- a/src/app/profile-page/profile-page.module.ts +++ b/src/app/profile-page/profile-page.module.ts @@ -5,25 +5,33 @@ import { ProfilePageRoutingModule } from './profile-page-routing.module'; import { ProfilePageComponent } from './profile-page.component'; import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form/profile-page-metadata-form.component'; import { ProfilePageSecurityFormComponent } from './profile-page-security-form/profile-page-security-form.component'; +import { ProfilePageResearcherFormComponent } from './profile-page-researcher-form/profile-page-researcher-form.component'; import { ThemedProfilePageComponent } from './themed-profile-page.component'; import { FormModule } from '../shared/form/form.module'; +import { UiSwitchModule } from 'ngx-ui-switch'; + @NgModule({ imports: [ ProfilePageRoutingModule, CommonModule, SharedModule, - FormModule + FormModule, + UiSwitchModule ], exports: [ + ProfilePageComponent, + ThemedProfilePageComponent, + ProfilePageMetadataFormComponent, ProfilePageSecurityFormComponent, - ProfilePageMetadataFormComponent + ProfilePageResearcherFormComponent ], declarations: [ ProfilePageComponent, ThemedProfilePageComponent, ProfilePageMetadataFormComponent, - ProfilePageSecurityFormComponent + ProfilePageSecurityFormComponent, + ProfilePageResearcherFormComponent ] }) export class ProfilePageModule { diff --git a/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.html new file mode 100644 index 0000000000..9df49ba24b --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.html @@ -0,0 +1,37 @@ +
+ + + +
diff --git a/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.spec.ts new file mode 100644 index 0000000000..464f2518ca --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.spec.ts @@ -0,0 +1,45 @@ +import { ActivatedRoute, Router } from '@angular/router'; +/* tslint:disable:no-unused-variable */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TranslateModule } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ClaimItemSelectorComponent } from './claim-item-selector.component'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { ProfileClaimService } from '../../../../profile-page/profile-claim/profile-claim.service'; +import { of } from 'rxjs'; + +describe('ClaimItemSelectorComponent', () => { + let component: ClaimItemSelectorComponent; + let fixture: ComponentFixture; + + const profileClaimService = jasmine.createSpyObj('profileClaimService', { + search: of({ payload: {page: []}}) + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [ ClaimItemSelectorComponent ], + providers: [ + { provide: NgbActiveModal, useValue: {} }, + { provide: ActivatedRoute, useValue: {} }, + { provide: Router, useValue: {} }, + { provide: ProfileClaimService, useValue: profileClaimService } + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ClaimItemSelectorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + +}); diff --git a/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.ts new file mode 100644 index 0000000000..5c5ee42037 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component.ts @@ -0,0 +1,68 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { BehaviorSubject } from 'rxjs'; +import { PaginatedList } from '../../../../core/data/paginated-list.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { Item } from '../../../../core/shared/item.model'; +import { SearchResult } from '../../../search/models/search-result.model'; +import { DSOSelectorModalWrapperComponent } from '../dso-selector-modal-wrapper.component'; +import { getItemPageRoute } from '../../../../item-page/item-page-routing-paths'; +import { EPerson } from '../../../../core/eperson/models/eperson.model'; +import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { ViewMode } from '../../../../core/shared/view-mode.model'; +import { ProfileClaimService } from '../../../../profile-page/profile-claim/profile-claim.service'; +import { CollectionElementLinkType } from '../../../object-collection/collection-element-link.type'; + + + +@Component({ + selector: 'ds-claim-item-selector', + templateUrl: './claim-item-selector.component.html' +}) +export class ClaimItemSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { + + @Input() dso: DSpaceObject; + + listEntries$: BehaviorSubject>>> = new BehaviorSubject(null); + + viewMode = ViewMode.ListElement; + + // enum to be exposed + linkTypes = CollectionElementLinkType; + + checked = false; + + @Output() create: EventEmitter = new EventEmitter(); + + constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router, + private profileClaimService: ProfileClaimService) { + super(activeModal, route); + } + + ngOnInit(): void { + this.profileClaimService.search(this.dso as EPerson).subscribe( + (result) => this.listEntries$.next(result) + ); + } + + // triggered when an item is selected + selectItem(dso: DSpaceObject): void { + this.close(); + this.navigate(dso); + } + + navigate(dso: DSpaceObject) { + this.router.navigate([getItemPageRoute(dso as Item)]); + } + + toggleCheckbox() { + this.checked = !this.checked; + } + + createFromScratch() { + this.create.emit(); + this.close(); + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 7b799bfaea..01649ee947 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -173,6 +173,7 @@ import { BitstreamRequestACopyPageComponent } from './bitstream-request-a-copy-p 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'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -343,6 +344,8 @@ const COMPONENTS = [ CommunitySidebarSearchListElementComponent, SearchNavbarComponent, ScopeSelectorModalComponent, + + ClaimItemSelectorComponent ]; const ENTRY_COMPONENTS = [ @@ -399,6 +402,7 @@ const ENTRY_COMPONENTS = [ OnClickMenuItemComponent, TextMenuItemComponent, ScopeSelectorModalComponent, + ClaimItemSelectorComponent ]; const SHARED_ITEM_PAGE_COMPONENTS = [ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c3c68a6882..d0ff85ba51 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1311,7 +1311,13 @@ "dso-selector.set-scope.community.input-header": "Search for a community or collection", + "dso-selector.claim.item.head": "Profile tips", + "dso-selector.claim.item.body": "These are existing profiles that may be related to you. If you recognize yourself in one of these profiles, select it and on the detail page, among the options, choose to claim it. Otherwise you can create a new profile from scratch using the button below.", + + "dso-selector.claim.item.not-mine-label": "None of these are mine", + + "dso-selector.claim.item.create-from-scratch": "Create a new one", "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", @@ -2907,7 +2913,7 @@ "profile.title": "Update Profile", - + "profile.card.researcher": "Researcher Profile", "project.listelement.badge": "Research Project", @@ -4165,5 +4171,41 @@ "idle-modal.log-out": "Log out", - "idle-modal.extend-session": "Extend session" + "idle-modal.extend-session": "Extend session", + + "researcher.profile.action.processing" : "Processing...", + + "researcher.profile.associated": "Researcher profile associated", + + "researcher.profile.create.new": "Create new", + + "researcher.profile.create.success": "Researcher profile created successfully", + + "researcher.profile.create.fail": "An error occurs during the researcher profile creation", + + "researcher.profile.delete": "Delete", + + "researcher.profile.expose": "Expose", + + "researcher.profile.hide": "Hide", + + "researcher.profile.not.associated": "Researcher profile not yet associated", + + "researcher.profile.view": "View", + + "researcher.profile.private.visibility" : "PRIVATE", + + "researcher.profile.public.visibility" : "PUBLIC", + + "researcher.profile.status": "Status:", + + "researcherprofile.claim.not-authorized": "You are not authorized to claim this item. For more details contact the administrator(s).", + + "researcherprofile.error.claim.body" : "An error occurred while claiming the profile, please try again later", + + "researcherprofile.error.claim.title" : "Error", + + "researcherprofile.success.claim.body" : "Profile claimed with success", + + "researcherprofile.success.claim.title" : "Success", } diff --git a/src/styles/_global-styles.scss b/src/styles/_global-styles.scss index e337539c15..cf251204e2 100644 --- a/src/styles/_global-styles.scss +++ b/src/styles/_global-styles.scss @@ -92,3 +92,9 @@ ngb-modal-backdrop { hyphens: auto; } +.researcher-profile-switch button:focus{ + outline: none !important; +} +.researcher-profile-switch .switch.checked{ + color: #fff; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c1903163dd..a4d0aff64b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9434,6 +9434,11 @@ ngx-sortablejs@^11.1.0: dependencies: tslib "^2.0.0" +ngx-ui-switch@^11.0.1: + version "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" From 004f95b6dc6d6e260fb944dec7e865dd14aa209e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Apr 2022 14:16:56 +0000 Subject: [PATCH 007/151] Bump minimist from 1.2.5 to 1.2.6 Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: minimist dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 00c93618d9..a2dd9dbc2c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8555,9 +8555,9 @@ minimatch@^3.0.2, minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== minipass-collect@^1.0.2: version "1.0.2" From 5dd7efcbab6811f4220f912e203945ad48896878 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Apr 2022 14:17:06 +0000 Subject: [PATCH 008/151] Bump moment from 2.29.1 to 2.29.2 Bumps [moment](https://github.com/moment/moment) from 2.29.1 to 2.29.2. - [Release notes](https://github.com/moment/moment/releases) - [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md) - [Commits](https://github.com/moment/moment/compare/2.29.1...2.29.2) --- updated-dependencies: - dependency-name: moment dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9cb6195c57..4b513beca3 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "mirador": "^3.3.0", "mirador-dl-plugin": "^0.13.0", "mirador-share-plugin": "^0.11.0", - "moment": "^2.29.1", + "moment": "^2.29.2", "morgan": "^1.10.0", "ng-mocks": "^13.1.1", "ng2-file-upload": "1.4.0", diff --git a/yarn.lock b/yarn.lock index 00c93618d9..beca9a8577 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8716,10 +8716,10 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -moment@^2.29.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== +moment@^2.29.2: + version "2.29.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" + integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== morgan@^1.10.0: version "1.10.0" From 728bce56a44b2ec09593bea3b8d602256b6ae302 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Apr 2022 14:18:00 +0000 Subject: [PATCH 009/151] Bump node-forge from 1.2.1 to 1.3.1 Bumps [node-forge](https://github.com/digitalbazaar/forge) from 1.2.1 to 1.3.1. - [Release notes](https://github.com/digitalbazaar/forge/releases) - [Changelog](https://github.com/digitalbazaar/forge/blob/main/CHANGELOG.md) - [Commits](https://github.com/digitalbazaar/forge/compare/v1.2.1...v1.3.1) --- updated-dependencies: - dependency-name: node-forge dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 00c93618d9..d85955dccf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8908,9 +8908,9 @@ node-fetch@^2.6.1: whatwg-url "^5.0.0" node-forge@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" - integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== node-gyp-build@^4.2.2: version "4.3.0" From 5add939a1d5989f59f0eb35d96e83472f6e0c5f5 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Thu, 21 Apr 2022 12:33:38 +0200 Subject: [PATCH 010/151] [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 011/151] [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 c19d12c5c00df7301b5edafa16fd5c38323068a9 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 7 Apr 2022 17:15:07 +0200 Subject: [PATCH 012/151] 90252: Invalidate requests containing DSO on DataService.delete Keep track of a list of request UUIDs in the object cache (most recent in front) When deleting a DSO, mark all of these as stale --- .../core/cache/object-cache.reducer.spec.ts | 4 +- src/app/core/cache/object-cache.reducer.ts | 10 +- src/app/core/cache/object-cache.service.ts | 4 +- .../bitstream-format-data.service.spec.ts | 17 ++- src/app/core/data/data.service.spec.ts | 137 +++++++++++++++++- src/app/core/data/data.service.ts | 59 +++++++- src/app/core/data/request.service.spec.ts | 35 ++++- src/app/core/data/request.service.ts | 17 ++- src/app/shared/mocks/request.service.mock.ts | 1 + 9 files changed, 266 insertions(+), 18 deletions(-) diff --git a/src/app/core/cache/object-cache.reducer.spec.ts b/src/app/core/cache/object-cache.reducer.spec.ts index 61d587b6de..82e2da58b1 100644 --- a/src/app/core/cache/object-cache.reducer.spec.ts +++ b/src/app/core/cache/object-cache.reducer.spec.ts @@ -41,7 +41,7 @@ describe('objectCacheReducer', () => { alternativeLinks: [altLink1, altLink2], timeCompleted: new Date().getTime(), msToLive: 900000, - requestUUID: requestUUID1, + requestUUIDs: [requestUUID1], patches: [], isDirty: false, }, @@ -55,7 +55,7 @@ describe('objectCacheReducer', () => { alternativeLinks: [altLink3, altLink4], timeCompleted: new Date().getTime(), msToLive: 900000, - requestUUID: selfLink2, + requestUUIDs: [selfLink2], patches: [], isDirty: false } diff --git a/src/app/core/cache/object-cache.reducer.ts b/src/app/core/cache/object-cache.reducer.ts index 9001d334ce..1a42408f72 100644 --- a/src/app/core/cache/object-cache.reducer.ts +++ b/src/app/core/cache/object-cache.reducer.ts @@ -63,9 +63,11 @@ export class ObjectCacheEntry implements CacheEntry { msToLive: number; /** - * The UUID of the request that caused this entry to be added + * The UUIDs of the requests that caused this entry to be added + * New UUIDs should be added to the front of the array + * to make retrieving the latest UUID easier. */ - requestUUID: string; + requestUUIDs: string[]; /** * An array of patches that were made on the client side to this entry, but haven't been sent to the server yet @@ -156,11 +158,11 @@ function addToObjectCache(state: ObjectCacheState, action: AddToObjectCacheActio data: action.payload.objectToCache, timeCompleted: action.payload.timeCompleted, msToLive: action.payload.msToLive, - requestUUID: action.payload.requestUUID, + requestUUIDs: [action.payload.requestUUID, ...(existing.requestUUIDs || [])], isDirty: isNotEmpty(existing.patches), patches: existing.patches || [], alternativeLinks: [...(existing.alternativeLinks || []), ...newAltLinks] - } + } as ObjectCacheEntry }); } diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts index 6d48242178..00f124b2f6 100644 --- a/src/app/core/cache/object-cache.service.ts +++ b/src/app/core/cache/object-cache.service.ts @@ -197,7 +197,7 @@ export class ObjectCacheService { */ getRequestUUIDBySelfLink(selfLink: string): Observable { return this.getByHref(selfLink).pipe( - map((entry: ObjectCacheEntry) => entry.requestUUID), + map((entry: ObjectCacheEntry) => entry.requestUUIDs[0]), distinctUntilChanged()); } @@ -282,7 +282,7 @@ export class ObjectCacheService { let result = false; this.getByHref(href).subscribe((entry: ObjectCacheEntry) => { if (isNotEmpty(requestUUID)) { - result = entry.requestUUID === requestUUID; + result = entry.requestUUIDs[0] === requestUUID; // todo: may make more sense to do entry.requestUUIDs.includes(requestUUID) instead } else { result = true; } diff --git a/src/app/core/data/bitstream-format-data.service.spec.ts b/src/app/core/data/bitstream-format-data.service.spec.ts index c1ebf90a47..30ef79ee6d 100644 --- a/src/app/core/data/bitstream-format-data.service.spec.ts +++ b/src/app/core/data/bitstream-format-data.service.spec.ts @@ -37,7 +37,12 @@ describe('BitstreamFormatDataService', () => { } } as Store; - const objectCache = {} as ObjectCacheService; + const requestUUIDs = ['some', 'uuid']; + + const objectCache = jasmine.createSpyObj('objectCache', { + getByHref: observableOf({ requestUUIDs }) + }) as ObjectCacheService; + const halEndpointService = { getEndpoint(linkPath: string): Observable { return cold('a', { a: bitstreamFormatsEndpoint }); @@ -76,6 +81,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: cold('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); @@ -96,6 +102,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: cold('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); @@ -118,6 +125,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: cold('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); @@ -139,6 +147,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: cold('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); @@ -163,6 +172,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: cold('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); @@ -186,6 +196,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: cold('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); @@ -209,6 +220,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: cold('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); @@ -231,6 +243,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: cold('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); @@ -253,6 +266,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: cold('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); @@ -273,6 +287,7 @@ describe('BitstreamFormatDataService', () => { send: {}, getByHref: observableOf(responseCacheEntry), getByUUID: hot('a', { a: responseCacheEntry }), + setStaleByUUID: observableOf(true), generateRequestId: 'request-id', removeByHrefSubstring: {} }); diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index f680fed6a4..7022196c1a 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -11,7 +11,11 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { DSpaceObject } from '../shared/dspace-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Item } from '../shared/item.model'; -import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { + createFailedRemoteDataObject, + createSuccessfulRemoteDataObject, + createSuccessfulRemoteDataObject$, +} from '../../shared/remote-data.utils'; import { ChangeAnalyzer } from './change-analyzer'; import { DataService } from './data.service'; import { PatchRequest } from './request.models'; @@ -28,6 +32,8 @@ import { FindListOptions } from './find-list-options.model'; const endpoint = 'https://rest.api/core'; +const BOOLEAN = { f: false, t: true }; + class TestService extends DataService { constructor( @@ -86,6 +92,9 @@ describe('DataService', () => { }, getObjectBySelfLink: () => { /* empty */ + }, + getByHref: () => { + /* empty */ } } as any; store = {} as Store; @@ -833,4 +842,130 @@ describe('DataService', () => { }); }); + + describe('invalidateByHref', () => { + let getByHrefSpy: jasmine.Spy; + + beforeEach(() => { + getByHrefSpy = spyOn(objectCache, 'getByHref').and.returnValue(observableOf({ + requestUUIDs: ['request1', 'request2', 'request3'] + })); + + }); + + it('should call setStaleByUUID for every request associated with this DSO', (done) => { + service.invalidateByHref('some-href').subscribe((ok) => { + expect(ok).toBeTrue(); + expect(getByHrefSpy).toHaveBeenCalledWith('some-href'); + expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request1'); + expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request2'); + expect(requestService.setStaleByUUID).toHaveBeenCalledWith('request3'); + done(); + }); + }); + + it('should return an Observable that only emits true once all requests are stale', () => { + testScheduler.run(({ cold, expectObservable }) => { + requestService.setStaleByUUID.and.callFake((uuid) => { + switch (uuid) { // fake requests becoming stale at different times + case 'request1': + return cold('--(t|)', BOOLEAN); + case 'request2': + return cold('----(t|)', BOOLEAN); + case 'request3': + return cold('------(t|)', BOOLEAN); + } + }); + + const done$ = service.invalidateByHref('some-href'); + + // emit true as soon as the final request is stale + expectObservable(done$).toBe('------(t|)', BOOLEAN); + }); + }); + }); + + describe('delete', () => { + let MOCK_SUCCEEDED_RD; + let MOCK_FAILED_RD; + + let invalidateByHrefSpy: jasmine.Spy; + let buildFromRequestUUIDSpy: jasmine.Spy; + let getIDHrefObsSpy: jasmine.Spy; + let deleteByHrefSpy: jasmine.Spy; + + beforeEach(() => { + invalidateByHrefSpy = spyOn(service, 'invalidateByHref').and.returnValue(observableOf(true)); + buildFromRequestUUIDSpy = spyOn(rdbService, 'buildFromRequestUUID').and.callThrough(); + getIDHrefObsSpy = spyOn(service, 'getIDHrefObs').and.callThrough(); + deleteByHrefSpy = spyOn(service, 'deleteByHref').and.callThrough(); + + MOCK_SUCCEEDED_RD = createSuccessfulRemoteDataObject({}); + MOCK_FAILED_RD = createFailedRemoteDataObject('something went wrong'); + }); + + it('should retrieve href by ID and call deleteByHref', () => { + getIDHrefObsSpy.and.returnValue(observableOf('some-href')); + buildFromRequestUUIDSpy.and.returnValue(null); + + service.delete('some-id').subscribe(rd => { + expect(getIDHrefObsSpy).toHaveBeenCalledWith('some-id'); + expect(deleteByHrefSpy).toHaveBeenCalledWith('some-href'); + }); + }); + + describe('deleteByHref', () => { + it('should call invalidateByHref if the DELETE request succeeds', (done) => { + buildFromRequestUUIDSpy.and.returnValue(observableOf(MOCK_SUCCEEDED_RD)); + + service.deleteByHref('some-href').subscribe(rd => { + expect(rd).toBe(MOCK_SUCCEEDED_RD); + expect(invalidateByHrefSpy).toHaveBeenCalled(); + done(); + }); + }); + + it('should not call invalidateByHref if the DELETE request fails', (done) => { + buildFromRequestUUIDSpy.and.returnValue(observableOf(MOCK_FAILED_RD)); + + service.deleteByHref('some-href').subscribe(rd => { + expect(rd).toBe(MOCK_FAILED_RD); + expect(invalidateByHrefSpy).not.toHaveBeenCalled(); + done(); + }); + }); + + it('should wait for invalidateByHref before emitting', () => { + testScheduler.run(({ cold, expectObservable }) => { + buildFromRequestUUIDSpy.and.returnValue( + cold('(r|)', { r: MOCK_SUCCEEDED_RD}) // RD emits right away + ); + invalidateByHrefSpy.and.returnValue( + cold('----(t|)', BOOLEAN) // but we pretend that setting requests to stale takes longer + ); + + const done$ = service.deleteByHref('some-href'); + expectObservable(done$).toBe( + '----(r|)', { r: MOCK_SUCCEEDED_RD} // ...and expect the returned Observable to wait until that's done + ); + }); + }); + + it('should wait for the DELETE request to resolve before emitting', () => { + testScheduler.run(({ cold, expectObservable }) => { + buildFromRequestUUIDSpy.and.returnValue( + cold('----(r|)', { r: MOCK_SUCCEEDED_RD}) // the request takes a while + ); + invalidateByHrefSpy.and.returnValue( + cold('(t|)', BOOLEAN) // but we pretend that setting to stale happens sooner + ); // e.g.: maybe already stale before this call? + + const done$ = service.deleteByHref('some-href'); + expectObservable(done$).toBe( + '----(r|)', { r: MOCK_SUCCEEDED_RD} // ...and expect the returned Observable to wait for the request + ); + }); + }); + }); + }); }); diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 310ad704ec..ca4d7e42de 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -1,7 +1,7 @@ import { HttpClient } from '@angular/common/http'; import { Store } from '@ngrx/store'; import { Operation } from 'fast-json-patch'; -import { Observable, of as observableOf } from 'rxjs'; +import { combineLatest, from, Observable, of as observableOf } from 'rxjs'; import { distinctUntilChanged, filter, @@ -12,7 +12,7 @@ import { takeWhile, switchMap, tap, - skipWhile, + skipWhile, toArray } from 'rxjs/operators'; import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; @@ -25,7 +25,7 @@ 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 { getRemoteDataPayload, getFirstSucceededRemoteData, getFirstCompletedRemoteData } from '../shared/operators'; import { URLCombiner } from '../url-combiner/url-combiner'; import { ChangeAnalyzer } from './change-analyzer'; import { PaginatedList } from './paginated-list.model'; @@ -579,6 +579,37 @@ export abstract class DataService implements UpdateDa return result$; } + /** + * Invalidate an existing DSpaceObject by marking all requests it is included in as stale + * @param objectId The id of the object to be invalidated + * @return An Observable that will emit `true` once all requests are stale + */ + invalidate(objectId: string): Observable { + return this.getIDHrefObs(objectId).pipe( + switchMap((href: string) => this.invalidateByHref(href)) + ); + } + + /** + * Invalidate an existing DSpaceObject by marking all requests it is included in as stale + * @param href The self link of the object to be invalidated + * @return An Observable that will emit `true` once all requests are stale + */ + invalidateByHref(href: string): Observable { + return this.objectCache.getByHref(href).pipe( + map(oce => oce.requestUUIDs), + switchMap(requestUUIDs => { + return from(requestUUIDs).pipe( + mergeMap(requestUUID => this.requestService.setStaleByUUID(requestUUID)), + toArray(), + ); + }), + map(areRequestsStale => areRequestsStale.every(Boolean)), + distinctUntilChanged(), + takeWhile(allStale => allStale === false, true), + ); + } + /** * Delete an existing DSpace Object on the server * @param objectId The id of the object to be removed @@ -600,6 +631,7 @@ export abstract class DataService implements UpdateDa * metadata should be saved as real metadata * @return A RemoteData observable with an empty payload, but still representing the state of the request: statusCode, * errorMessage, timeCompleted, etc + * Only emits once all request related to the DSO has been invalidated. */ deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable> { const requestId = this.requestService.generateRequestId(); @@ -618,7 +650,26 @@ export abstract class DataService implements UpdateDa } this.requestService.send(request); - return this.rdbService.buildFromRequestUUID(requestId); + const response$ = this.rdbService.buildFromRequestUUID(requestId); + + const invalidated$ = response$.pipe( + getFirstCompletedRemoteData(), + switchMap(rd => { + if (rd.hasSucceeded) { + return this.invalidateByHref(href); + } else { + return [true]; + } + }) + ); + + return combineLatest([response$, invalidated$]).pipe( + filter(([_, invalidated]) => invalidated), + tap(() => { + console.log(`DataService.deleteByHref() href=${href} done.`); + }), + map(([response, _]) => response), + ); } /** diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index a49761ae5d..fe35d840d7 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -8,7 +8,7 @@ import { defaultUUID, getMockUUIDService } from '../../shared/mocks/uuid.service import { ObjectCacheService } from '../cache/object-cache.service'; import { coreReducers} from '../core.reducers'; import { UUIDService } from '../shared/uuid.service'; -import { RequestConfigureAction, RequestExecuteAction } from './request.actions'; +import { RequestConfigureAction, RequestExecuteAction, RequestStaleAction } from './request.actions'; import { DeleteRequest, GetRequest, @@ -19,7 +19,7 @@ import { PutRequest } from './request.models'; import { RequestService } from './request.service'; -import { TestBed, waitForAsync } from '@angular/core/testing'; +import { fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; import { storeModuleConfig } from '../../app.reducer'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { RequestEntryState } from './request-entry-state.model'; @@ -426,7 +426,7 @@ describe('RequestService', () => { describe('and it is cached', () => { describe('in the ObjectCache', () => { beforeEach(() => { - (objectCache.getByHref as any).and.returnValue(observableOf({ requestUUID: 'some-uuid' })); + (objectCache.getByHref as any).and.returnValue(observableOf({ requestUUIDs: ['some-uuid'] })); spyOn(serviceAsAny, 'hasByHref').and.returnValue(false); spyOn(serviceAsAny, 'hasByUUID').and.returnValue(true); }); @@ -596,4 +596,33 @@ describe('RequestService', () => { }); }); + describe('setStaleByUUID', () => { + let dispatchSpy: jasmine.Spy; + let getByUUIDSpy: jasmine.Spy; + + beforeEach(() => { + dispatchSpy = spyOn(store, 'dispatch'); + getByUUIDSpy = spyOn(service, 'getByUUID').and.callThrough(); + }); + + it('should dispatch a RequestStaleAction', () => { + service.setStaleByUUID('something'); + const firstAction = dispatchSpy.calls.argsFor(0)[0]; + expect(firstAction).toBeInstanceOf(RequestStaleAction); + expect(firstAction.payload).toEqual({ uuid: 'something' }); + }); + + it('should return an Observable that emits true as soon as the request is stale', fakeAsync(() => { + dispatchSpy.and.callFake(() => { /* empty */ }); // don't actually set as stale + getByUUIDSpy.and.returnValue(cold('a-b--c--d-', { // but fake the state in the cache + a: { state: RequestEntryState.ResponsePending }, + b: { state: RequestEntryState.Success }, + c: { state: RequestEntryState.SuccessStale }, + d: { state: RequestEntryState.Error }, + })); + + const done$ = service.setStaleByUUID('something'); + expect(done$).toBeObservable(cold('-----(t|)', { t: true })); + })); + }); }); diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 3903bcfc99..b8b1d1eba9 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -311,6 +311,21 @@ export class RequestService { ); } + /** + * Mark a request as stale + * @param uuid the UUID of the request + * @return an Observable that will emit true once the Request becomes stale + */ + setStaleByUUID(uuid: string): Observable { + this.store.dispatch(new RequestStaleAction(uuid)); + + return this.getByUUID(uuid).pipe( + map(request => isStale(request.state)), + filter(stale => stale === true), + take(1), + ); + } + /** * Check if a GET request is in the cache or if it's still pending * @param {GetRequest} request The request to check @@ -339,7 +354,7 @@ export class RequestService { .subscribe((entry: ObjectCacheEntry) => { // if the object cache has a match, check if the request that the object came with is // still valid - inObjCache = this.hasByUUID(entry.requestUUID); + inObjCache = this.hasByUUID(entry.requestUUIDs[0]); }).unsubscribe(); // we should send the request if it isn't cached diff --git a/src/app/shared/mocks/request.service.mock.ts b/src/app/shared/mocks/request.service.mock.ts index f07aec587e..bce5b2d466 100644 --- a/src/app/shared/mocks/request.service.mock.ts +++ b/src/app/shared/mocks/request.service.mock.ts @@ -13,6 +13,7 @@ export function getMockRequestService(requestEntry$: Observable = isCachedOrPending: false, removeByHrefSubstring: observableOf(true), setStaleByHrefSubstring: observableOf(true), + setStaleByUUID: observableOf(true), hasByHref$: observableOf(false) }); } From 9699491269f1c47c239b745fea1252eeb316c4a4 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Wed, 13 Apr 2022 12:03:27 +0200 Subject: [PATCH 013/151] 90252: Remove old cleanup code & add tests to confirm that DataService.delete is called --- .../group-form/group-form.component.spec.ts | 60 ++++++++++++++- .../group-form/group-form.component.ts | 12 +-- .../groups-registry.component.html | 2 +- .../groups-registry.component.spec.ts | 31 +++++++- .../groups-registry.component.ts | 12 --- .../metadata-registry.component.ts | 2 - .../metadata-schema.component.ts | 3 - .../delete-collection-page.component.ts | 3 +- .../collection-metadata.component.spec.ts | 26 +++---- .../collection-metadata.component.ts | 23 +----- .../delete-community-page.component.ts | 3 +- src/app/core/data/comcol-data.service.spec.ts | 73 ++++++++++++++++++ .../core/eperson/eperson-data.service.spec.ts | 9 +-- .../core/registry/registry.service.spec.ts | 8 ++ .../item-bitstreams.component.ts | 22 ------ .../comcol-form/comcol-form.component.spec.ts | 22 ++++-- .../comcol-form/comcol-form.component.ts | 1 - .../delete-comcol-page.component.spec.ts | 8 -- .../delete-comcol-page.component.ts | 2 - .../item-versions-delete-modal.component.html | 4 +- .../item-versions-delete-modal.component.ts | 10 ++- .../item-versions.component.spec.ts | 77 +++++++++++++++++-- .../item-versions/item-versions.component.ts | 74 +++++++++--------- .../resource-policies.component.spec.ts | 10 +++ .../resource-policies.component.ts | 1 - 25 files changed, 333 insertions(+), 165 deletions(-) diff --git a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts index 2307f3c6fa..bd27a7a825 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts @@ -3,9 +3,9 @@ import { HttpClient } from '@angular/common/http'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule, FormArray, FormControl, FormGroup,Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms'; -import { BrowserModule } from '@angular/platform-browser'; +import { BrowserModule, By } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbModal, NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { Store } from '@ngrx/store'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; import { Observable, of as observableOf } from 'rxjs'; @@ -27,7 +27,7 @@ import { FormBuilderService } from '../../../shared/form/builder/form-builder.se import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock'; import { GroupFormComponent } from './group-form.component'; -import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock'; import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock'; import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mock'; @@ -35,6 +35,7 @@ import { RouterMock } from '../../../shared/mocks/router.mock'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { Operation } from 'fast-json-patch'; import { ValidateGroupExists } from './validators/group-exists.validator'; +import { NoContent } from '../../../core/shared/NoContent.model'; describe('GroupFormComponent', () => { let component: GroupFormComponent; @@ -87,6 +88,9 @@ describe('GroupFormComponent', () => { patch(group: Group, operations: Operation[]) { return null; }, + delete(objectId: string, copyVirtualMetadata?: string[]): Observable> { + return createSuccessfulRemoteDataObject$({}); + }, cancelEditGroup(): void { this.activeGroup = null; }, @@ -348,4 +352,54 @@ describe('GroupFormComponent', () => { }); }); + describe('delete', () => { + let deleteButton; + let confirmButton; + let cancelButton; + + beforeEach(() => { + component.initialisePage(); + + component.canEdit$ = observableOf(true); + component.groupBeingEdited = { + permanent: false + } as Group; + + fixture.detectChanges(); + deleteButton = fixture.debugElement.query(By.css('.delete-button')).nativeElement; + + spyOn(groupsDataServiceStub, 'delete').and.callThrough(); + spyOn(groupsDataServiceStub, 'getActiveGroup').and.returnValue(observableOf({ id: 'active-group' })); + }); + + describe('if confirmed via modal', () => { + beforeEach(() => { + deleteButton.click(); + fixture.detectChanges(); + confirmButton = (document as any).querySelector('.modal-footer .confirm'); + }); + + it('should call GroupDataService.delete', () => { + confirmButton.click(); + fixture.detectChanges(); + + expect(groupsDataServiceStub.delete).toHaveBeenCalledWith('active-group'); + }); + }); + + describe('if canceled via modal', () => { + beforeEach(() => { + deleteButton.click(); + fixture.detectChanges(); + cancelButton = (document as any).querySelector('.modal-footer .cancel'); + }); + + it('should not call GroupDataService.delete', () => { + cancelButton.click(); + fixture.detectChanges(); + + expect(groupsDataServiceStub.delete).not.toHaveBeenCalled(); + }); + }); + }); }); diff --git a/src/app/access-control/group-registry/group-form/group-form.component.ts b/src/app/access-control/group-registry/group-form/group-form.component.ts index 826b7dbe69..b0178f1294 100644 --- a/src/app/access-control/group-registry/group-form/group-form.component.ts +++ b/src/app/access-control/group-registry/group-form/group-form.component.ts @@ -426,7 +426,7 @@ export class GroupFormComponent implements OnInit, OnDestroy { .subscribe((rd: RemoteData) => { if (rd.hasSucceeded) { this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.deleted.success', { name: group.name })); - this.reset(); + this.onCancel(); } else { this.notificationsService.error( this.translateService.get(this.messagePrefix + '.notification.deleted.failure.title', { name: group.name }), @@ -439,16 +439,6 @@ export class GroupFormComponent implements OnInit, OnDestroy { }); } - /** - * This method will ensure that the page gets reset and that the cache is cleared - */ - reset() { - this.groupDataService.getBrowseEndpoint().pipe(take(1)).subscribe((href: string) => { - this.requestService.removeByHrefSubstring(href); - }); - this.onCancel(); - } - /** * Cancel the current edit when component is destroyed & unsub all subscriptions */ diff --git a/src/app/access-control/group-registry/groups-registry.component.html b/src/app/access-control/group-registry/groups-registry.component.html index e791b7f2a0..237dedde09 100644 --- a/src/app/access-control/group-registry/groups-registry.component.html +++ b/src/app/access-control/group-registry/groups-registry.component.html @@ -79,7 +79,7 @@ diff --git a/src/app/access-control/group-registry/groups-registry.component.spec.ts b/src/app/access-control/group-registry/groups-registry.component.spec.ts index 0b30a551fd..99586ee5f0 100644 --- a/src/app/access-control/group-registry/groups-registry.component.spec.ts +++ b/src/app/access-control/group-registry/groups-registry.component.spec.ts @@ -31,6 +31,7 @@ import { RouterMock } from '../../shared/mocks/router.mock'; import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; +import { NoContent } from '../../core/shared/NoContent.model'; describe('GroupRegistryComponent', () => { let component: GroupsRegistryComponent; @@ -145,7 +146,10 @@ describe('GroupRegistryComponent', () => { totalPages: 1, currentPage: 1 }), [result])); - } + }, + delete(objectId: string, copyVirtualMetadata?: string[]): Observable> { + return createSuccessfulRemoteDataObject$({}); + }, }; dsoDataServiceStub = { findByHref(href: string): Observable> { @@ -301,4 +305,29 @@ describe('GroupRegistryComponent', () => { }); }); }); + + describe('delete', () => { + let deleteButton; + + beforeEach(fakeAsync(() => { + spyOn(groupsDataServiceStub, 'delete').and.callThrough(); + + setIsAuthorized(true, true); + + // force rerender after setup changes + component.search({ query: '' }); + tick(); + fixture.detectChanges(); + + // only mockGroup[0] is deletable, so we should only get one button + deleteButton = fixture.debugElement.query(By.css('.btn-delete')).nativeElement; + })); + + it('should call GroupDataService.delete', () => { + deleteButton.click(); + fixture.detectChanges(); + + expect(groupsDataServiceStub.delete).toHaveBeenCalledWith(mockGroups[0].id); + }); + }); }); diff --git a/src/app/access-control/group-registry/groups-registry.component.ts b/src/app/access-control/group-registry/groups-registry.component.ts index da861518da..29ec1770a7 100644 --- a/src/app/access-control/group-registry/groups-registry.component.ts +++ b/src/app/access-control/group-registry/groups-registry.component.ts @@ -199,7 +199,6 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { if (rd.hasSucceeded) { this.deletedGroupsIds = [...this.deletedGroupsIds, group.group.id]; this.notificationsService.success(this.translateService.get(this.messagePrefix + 'notification.deleted.success', { name: group.group.name })); - this.reset(); } else { this.notificationsService.error( this.translateService.get(this.messagePrefix + 'notification.deleted.failure.title', { name: group.group.name }), @@ -209,17 +208,6 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy { } } - /** - * This method will set everything to stale, which will cause the lists on this page to update. - */ - reset() { - this.groupService.getBrowseEndpoint().pipe( - take(1) - ).subscribe((href: string) => { - this.requestService.setStaleByHrefSubstring(href); - }); - } - /** * Get the members (epersons embedded value of a group) * @param group diff --git a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts index 8574c4678b..857034604e 100644 --- a/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts +++ b/src/app/admin/admin-registries/metadata-registry/metadata-registry.component.ts @@ -128,7 +128,6 @@ export class MetadataRegistryComponent { * Delete all the selected metadata schemas */ deleteSchemas() { - this.registryService.clearMetadataSchemaRequests().subscribe(); this.registryService.getSelectedMetadataSchemas().pipe(take(1)).subscribe( (schemas) => { const tasks$ = []; @@ -148,7 +147,6 @@ export class MetadataRegistryComponent { } this.registryService.deselectAllMetadataSchema(); this.registryService.cancelEditMetadataSchema(); - this.forceUpdateSchemas(); }); } ); diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts index 8a2086d5e2..d0827e6e4d 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts +++ b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.ts @@ -174,15 +174,12 @@ export class MetadataSchemaComponent implements OnInit { const failedResponses = responses.filter((response: RemoteData) => response.hasFailed); if (successResponses.length > 0) { this.showNotification(true, successResponses.length); - this.registryService.clearMetadataFieldRequests(); - } if (failedResponses.length > 0) { this.showNotification(false, failedResponses.length); } this.registryService.deselectAllMetadataField(); this.registryService.cancelEditMetadataField(); - this.forceUpdateFields(); }); } ); diff --git a/src/app/collection-page/delete-collection-page/delete-collection-page.component.ts b/src/app/collection-page/delete-collection-page/delete-collection-page.component.ts index 3e30373070..4c3e003773 100644 --- a/src/app/collection-page/delete-collection-page/delete-collection-page.component.ts +++ b/src/app/collection-page/delete-collection-page/delete-collection-page.component.ts @@ -24,8 +24,7 @@ export class DeleteCollectionPageComponent extends DeleteComColPageComponent { success: {}, error: {} }); - const objectCache = jasmine.createSpyObj('objectCache', { - remove: {} - }); const requestService = jasmine.createSpyObj('requestService', { setStaleByHrefSubstring: {} }); @@ -65,8 +62,7 @@ describe('CollectionMetadataComponent', () => { { provide: ItemTemplateDataService, useValue: itemTemplateServiceStub }, { provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(collection) }) } } }, { provide: NotificationsService, useValue: notificationsService }, - { provide: ObjectCacheService, useValue: objectCache }, - { provide: RequestService, useValue: requestService } + { provide: RequestService, useValue: requestService }, ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -95,21 +91,19 @@ describe('CollectionMetadataComponent', () => { }); describe('deleteItemTemplate', () => { - describe('when delete returns a success', () => { - beforeEach(() => { - (itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(true)); - comp.deleteItemTemplate(); - }); + beforeEach(() => { + (itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(true)); + comp.deleteItemTemplate(); + }); + it('should call ItemTemplateService.deleteByCollectionID', () => { + expect(itemTemplateService.deleteByCollectionID).toHaveBeenCalledWith(template, 'collection-id'); + }); + + describe('when delete returns a success', () => { it('should display a success notification', () => { expect(notificationsService.success).toHaveBeenCalled(); }); - - it('should reset related object and request cache', () => { - expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(collectionTemplateHref); - expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(template.self); - expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(collection.self); - }); }); describe('when delete returns a failure', () => { diff --git a/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts b/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts index cfaad3767e..57248cdaed 100644 --- a/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-metadata/collection-metadata.component.ts @@ -38,8 +38,7 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent this.itemTemplateService.getCollectionEndpoint(collection.id)), - ); - - combineLatestObservable(collection$, template$, templateHref$).pipe( - switchMap(([collection, template, templateHref]) => { - return this.itemTemplateService.deleteByCollectionID(template, collection.uuid).pipe( - tap((success: boolean) => { - if (success) { - this.objectCache.remove(templateHref); - this.objectCache.remove(template.self); - this.requestService.setStaleByHrefSubstring(template.self); - this.requestService.setStaleByHrefSubstring(templateHref); - this.requestService.setStaleByHrefSubstring(collection.self); - } - }) - ); + combineLatestObservable(collection$, template$).pipe( + switchMap(([collection, template]) => { + return this.itemTemplateService.deleteByCollectionID(template, collection.uuid); }) ).subscribe((success: boolean) => { if (success) { diff --git a/src/app/community-page/delete-community-page/delete-community-page.component.ts b/src/app/community-page/delete-community-page/delete-community-page.component.ts index 0cccc503e1..3160d94be9 100644 --- a/src/app/community-page/delete-community-page/delete-community-page.component.ts +++ b/src/app/community-page/delete-community-page/delete-community-page.component.ts @@ -24,9 +24,8 @@ export class DeleteCommunityPageComponent extends DeleteComColPageComponent { }); }); }); + + describe('deleteLogo', () => { + let dso; + + beforeEach(() => { + dso = { + _links: { + logo: { + href: 'logo-href' + } + } + }; + }); + + describe('when DSO has no logo', () => { + beforeEach(() => { + dso.logo = undefined; + }); + + it('should return a failed RD', (done) => { + service.deleteLogo(dso).subscribe(rd => { + expect(rd.hasFailed).toBeTrue(); + expect(bitstreamDataService.deleteByHref).not.toHaveBeenCalled(); + done(); + }); + }); + }); + + describe('when DSO has a logo', () => { + let logo; + + beforeEach(() => { + logo = Object.assign(new Bitstream, { + id: 'logo-id', + _links: { + self: { + href: 'logo-href', + } + } + }); + }); + + describe('that can be retrieved', () => { + beforeEach(() => { + dso.logo = createSuccessfulRemoteDataObject$(logo); + }); + + it('should call BitstreamDataService.deleteByHref', (done) => { + service.deleteLogo(dso).subscribe(rd => { + expect(rd.hasSucceeded).toBeTrue(); + expect(bitstreamDataService.deleteByHref).toHaveBeenCalledWith('logo-href'); + done(); + }); + }); + }); + + describe('that cannot be retrieved', () => { + beforeEach(() => { + dso.logo = createFailedRemoteDataObject$(logo); + }); + + it('should not call BitstreamDataService.deleteByHref', (done) => { + service.deleteLogo(dso).subscribe(rd => { + expect(rd.hasFailed).toBeTrue(); + expect(bitstreamDataService.deleteByHref).not.toHaveBeenCalled(); + done(); + }); + }); + }); + }); + }); }); diff --git a/src/app/core/eperson/eperson-data.service.spec.ts b/src/app/core/eperson/eperson-data.service.spec.ts index e123253e2b..80cb92716a 100644 --- a/src/app/core/eperson/eperson-data.service.spec.ts +++ b/src/app/core/eperson/eperson-data.service.spec.ts @@ -21,7 +21,7 @@ import { EPersonDataService } from './eperson-data.service'; import { EPerson } from './models/eperson.model'; import { EPersonMock, EPersonMock2 } from '../../shared/testing/eperson.mock'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub'; -import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { createNoContentRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { getMockRemoteDataBuildServiceHrefMap } from '../../shared/mocks/remote-data-build.service.mock'; import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; import { getMockRequestService } from '../../shared/mocks/request.service.mock'; @@ -287,13 +287,12 @@ describe('EPersonDataService', () => { describe('deleteEPerson', () => { beforeEach(() => { - spyOn(service, 'findById').and.returnValue(createSuccessfulRemoteDataObject$(EPersonMock)); + spyOn(service, 'delete').and.returnValue(createNoContentRemoteDataObject$()); service.deleteEPerson(EPersonMock).subscribe(); }); - it('should send DeleteRequest', () => { - const expected = new DeleteRequest(requestService.generateRequestId(), epersonsEndpoint + '/' + EPersonMock.uuid); - expect(requestService.send).toHaveBeenCalledWith(expected); + it('should call DataService.delete with the EPerson\'s UUID', () => { + expect(service.delete).toHaveBeenCalledWith(EPersonMock.id); }); }); diff --git a/src/app/core/registry/registry.service.spec.ts b/src/app/core/registry/registry.service.spec.ts index db52a0547e..e9dfbe7e2c 100644 --- a/src/app/core/registry/registry.service.spec.ts +++ b/src/app/core/registry/registry.service.spec.ts @@ -386,6 +386,10 @@ describe('RegistryService', () => { result = registryService.deleteMetadataSchema(mockSchemasList[0].id); }); + it('should defer to MetadataSchemaDataService.delete', () => { + expect(metadataSchemaService.delete).toHaveBeenCalledWith(`${mockSchemasList[0].id}`); + }); + it('should return a successful response', () => { result.subscribe((response: RemoteData) => { expect(response.hasSucceeded).toBe(true); @@ -400,6 +404,10 @@ describe('RegistryService', () => { result = registryService.deleteMetadataField(mockFieldsList[0].id); }); + it('should defer to MetadataFieldDataService.delete', () => { + expect(metadataFieldService.delete).toHaveBeenCalledWith(`${mockFieldsList[0].id}`); + }); + it('should return a successful response', () => { result.subscribe((response: RemoteData) => { expect(response.hasSucceeded).toBe(true); diff --git a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts index 7599f95ed6..0c7dfb1e34 100644 --- a/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts +++ b/src/app/item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts @@ -147,7 +147,6 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme // Perform the setup actions from above in order and display notifications removedResponses$.pipe(take(1)).subscribe((responses: RemoteData[]) => { this.displayNotifications('item.edit.bitstreams.notifications.remove', responses); - this.reset(); this.submitting = false; }); } @@ -242,27 +241,6 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme ); } - /** - * De-cache the current item (it should automatically reload due to itemUpdateSubscription) - */ - reset() { - this.refreshItemCache(); - } - - /** - * Remove the current item's cache from object- and request-cache - */ - refreshItemCache() { - this.bundles$.pipe(take(1)).subscribe((bundles: Bundle[]) => { - bundles.forEach((bundle: Bundle) => { - this.objectCache.remove(bundle.self); - this.requestService.removeByHrefSubstring(bundle.self); - }); - this.objectCache.remove(this.item.self); - this.requestService.removeByHrefSubstring(this.item.self); - }); - } - /** * Unsubscribe from open subscriptions whenever the component gets destroyed */ diff --git a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts index dc146e1fd7..054c161da6 100644 --- a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts +++ b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.spec.ts @@ -55,6 +55,9 @@ describe('ComColFormComponent', () => { }) ]; + const logo = { + id: 'logo' + }; const logoEndpoint = 'rest/api/logo/endpoint'; const dsoService = Object.assign({ getLogoEndpoint: () => observableOf(logoEndpoint), @@ -207,7 +210,7 @@ describe('ComColFormComponent', () => { beforeEach(() => { initComponent(Object.assign(new Community(), { id: 'community-id', - logo: createSuccessfulRemoteDataObject$({}), + logo: createSuccessfulRemoteDataObject$(logo), _links: { self: { href: 'community-self' }, logo: { href: 'community-logo' }, @@ -225,28 +228,31 @@ describe('ComColFormComponent', () => { describe('submit with logo marked for deletion', () => { beforeEach(() => { + spyOn(dsoService, 'deleteLogo').and.callThrough(); comp.markLogoForDeletion = true; }); + it('should call dsoService.deleteLogo on the DSO', () => { + comp.onSubmit(); + fixture.detectChanges(); + + expect(dsoService.deleteLogo).toHaveBeenCalledWith(comp.dso); + }); + describe('when dsoService.deleteLogo returns a successful response', () => { beforeEach(() => { - spyOn(dsoService, 'deleteLogo').and.returnValue(createSuccessfulRemoteDataObject$({})); + dsoService.deleteLogo.and.returnValue(createSuccessfulRemoteDataObject$({})); comp.onSubmit(); }); it('should display a success notification', () => { expect(notificationsService.success).toHaveBeenCalled(); }); - - it('should remove the object\'s cache', () => { - expect(requestServiceStub.removeByHrefSubstring).toHaveBeenCalled(); - expect(objectCacheStub.remove).toHaveBeenCalled(); - }); }); describe('when dsoService.deleteLogo returns an error response', () => { beforeEach(() => { - spyOn(dsoService, 'deleteLogo').and.returnValue(createFailedRemoteDataObject$('Error', 500)); + dsoService.deleteLogo.and.returnValue(createFailedRemoteDataObject$('Error', 500)); comp.onSubmit(); }); diff --git a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts index c3ba9dcaee..29be240753 100644 --- a/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts +++ b/src/app/shared/comcol/comcol-forms/comcol-form/comcol-form.component.ts @@ -184,7 +184,6 @@ export class ComColFormComponent implements On } this.dso.logo = undefined; this.uploadFilesOptions.method = RestRequestMethod.POST; - this.refreshCache(); this.finish.emit(); }); } diff --git a/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts b/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts index bdc2637886..bc73e4134b 100644 --- a/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts +++ b/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.spec.ts @@ -68,7 +68,6 @@ describe('DeleteComColPageComponent', () => { { delete: createNoContentRemoteDataObject$(), findByHref: jasmine.createSpy('findByHref'), - refreshCache: jasmine.createSpy('refreshCache') }); routerStub = { @@ -79,10 +78,6 @@ describe('DeleteComColPageComponent', () => { data: observableOf(community) }; - requestServiceStub = jasmine.createSpyObj('RequestService', { - removeByHrefSubstring: jasmine.createSpy('removeByHrefSubstring') - }); - translateServiceStub = jasmine.createSpyObj('TranslateService', { instant: jasmine.createSpy('instant') }); @@ -99,7 +94,6 @@ describe('DeleteComColPageComponent', () => { { provide: ActivatedRoute, useValue: routeStub }, { provide: NotificationsService, useValue: new NotificationsServiceStub() }, { provide: TranslateService, useValue: translateServiceStub }, - { provide: RequestService, useValue: requestServiceStub } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -159,7 +153,6 @@ describe('DeleteComColPageComponent', () => { scheduler.flush(); fixture.detectChanges(); expect(notificationsService.error).toHaveBeenCalled(); - expect(dsoDataService.refreshCache).not.toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalled(); }); @@ -169,7 +162,6 @@ describe('DeleteComColPageComponent', () => { scheduler.flush(); fixture.detectChanges(); expect(notificationsService.success).toHaveBeenCalled(); - expect(dsoDataService.refreshCache).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalled(); }); diff --git a/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts b/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts index f781b71f3f..46b8f63bc1 100644 --- a/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts +++ b/src/app/shared/comcol/comcol-forms/delete-comcol-page/delete-comcol-page.component.ts @@ -41,7 +41,6 @@ export class DeleteComColPageComponent i protected route: ActivatedRoute, protected notifications: NotificationsService, protected translate: TranslateService, - protected requestService: RequestService ) { } @@ -61,7 +60,6 @@ export class DeleteComColPageComponent i if (response.hasSucceeded) { const successMessage = this.translate.instant((dso as any).type + '.delete.notification.success'); this.notifications.success(successMessage); - this.dsoDataService.refreshCache(dso); } else { const errorMessage = this.translate.instant((dso as any).type + '.delete.notification.fail'); this.notifications.error(errorMessage); diff --git a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html index 0c0b72272f..6c54a15162 100644 --- a/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html +++ b/src/app/shared/item/item-versions/item-versions-delete-modal/item-versions-delete-modal.component.html @@ -8,12 +8,12 @@

{{ "item.version.delete.modal.text" | translate : {version: versionNumber} }}

- + \ No newline at end of file diff --git a/src/app/item-page/full/full-item-page.component.ts b/src/app/item-page/full/full-item-page.component.ts index 369769c77d..99cf083bc5 100644 --- a/src/app/item-page/full/full-item-page.component.ts +++ b/src/app/item-page/full/full-item-page.component.ts @@ -16,6 +16,10 @@ import { hasValue } from '../../shared/empty.util'; import { AuthService } from '../../core/auth/auth.service'; import { Location } from '@angular/common'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { TranslateService } from '@ngx-translate/core'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; +import { CollectionDataService } from '../../core/data/collection-data.service'; /** @@ -48,8 +52,11 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, items: ItemDataService, authService: AuthService, authorizationService: AuthorizationDataService, + translate: TranslateService, + notificationsService: NotificationsService, + researcherProfileService: ResearcherProfileService, private _location: Location) { - super(route, router, items, authService, authorizationService); + super(route, router, items, authService, authorizationService, translate, notificationsService, researcherProfileService); } /*** AoT inheritance fix, will hopefully be resolved in the near future **/ diff --git a/src/app/item-page/simple/item-page.component.ts b/src/app/item-page/simple/item-page.component.ts index 95fbd7a2e0..8da5186270 100644 --- a/src/app/item-page/simple/item-page.component.ts +++ b/src/app/item-page/simple/item-page.component.ts @@ -1,20 +1,26 @@ -import { map } from 'rxjs/operators'; +import { map, mergeMap, take, tap } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; 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, redirectOn4xx } from '../../core/shared/operators'; +import { getAllSucceededRemoteDataPayload, getFirstSucceededRemoteData, redirectOn4xx } 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 { 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. @@ -55,13 +61,27 @@ export class ItemPageComponent implements OnInit { */ isAdmin$: Observable; + itemUrl: string; + + public claimable$: BehaviorSubject = new BehaviorSubject(false); + public isProcessing$: BehaviorSubject = new BehaviorSubject(false); + constructor( protected route: ActivatedRoute, private router: Router, private items: ItemDataService, private authService: AuthService, - private authorizationService: AuthorizationDataService - ) { } + private authorizationService: AuthorizationDataService, + private translate: TranslateService, + private notificationsService: NotificationsService, + private researcherProfileService: ResearcherProfileService + ) { + this.route.data.pipe( + map((data) => data.dso as RemoteData) + ).subscribe((data: RemoteData) => { + this.itemUrl = data?.payload?.self + }); + } /** * Initialize instance variables @@ -77,5 +97,56 @@ export class ItemPageComponent implements OnInit { ); this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf); + + this.authorizationService.isAuthorized(FeatureID.ShowClaimItem, this.itemUrl).pipe( + take(1) + ).subscribe((isAuthorized: boolean) => { + this.claimable$.next(isAuthorized) + }); + } + + claim() { + this.isProcessing$.next(true); + + this.authorizationService.isAuthorized(FeatureID.CanClaimItem, this.itemUrl).pipe( + take(1) + ).subscribe((isAuthorized: boolean) => { + if (!isAuthorized) { + this.notificationsService.warning(this.translate.get('researcherprofile.claim.not-authorized')); + this.isProcessing$.next(false); + } else { + this.createFromExternalSource(); + } + }); + + } + + createFromExternalSource() { + this.researcherProfileService.createFromExternalSource(this.itemUrl).pipe( + tap((rd: any) => { + if (!rd.hasSucceeded) { + this.isProcessing$.next(false); + } + }), + 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); + this.isProcessing$.next(false); + } else { + this.notificationsService.error( + this.translate.get('researcherprofile.error.claim.title'), + this.translate.get('researcherprofile.error.claim.body')); + } + }); + } + + isClaimable(): Observable { + return this.claimable$; } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index d0ff85ba51..05047cefa7 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2130,6 +2130,8 @@ "item.page.version.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history", + "item.page.claim.button": "Claim", + "item.preview.dc.identifier.uri": "Identifier:", "item.preview.dc.contributor.author": "Authors:", From cb84bc758e026d0f42739622af7413358d6dd62b Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Tue, 26 Apr 2022 17:52:28 +0200 Subject: [PATCH 024/151] [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 0812377b58c88f552677d3e4868fa21382a5d680 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh <“sufiyan.shaikh@4science.com”> Date: Wed, 27 Apr 2022 11:50:53 +0530 Subject: [PATCH 025/151] [DSC-516] Test cases changes --- ...item-admin-search-result-grid-element.component.spec.ts | 2 ++ .../truncatable-part/truncatable-part.component.spec.ts | 7 +++++-- .../truncatable-part/truncatable-part.component.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts index dedada5f5f..148cfb13cb 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts @@ -18,6 +18,7 @@ import { ItemAdminSearchResultGridElementComponent } from './item-admin-search-r import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../../../../shared/theme-support/theme.service'; +import { NativeWindowRef, NativeWindowService } from '../../../../../core/services/window.service'; describe('ItemAdminSearchResultGridElementComponent', () => { let component: ItemAdminSearchResultGridElementComponent; @@ -52,6 +53,7 @@ describe('ItemAdminSearchResultGridElementComponent', () => { SharedModule ], providers: [ + { provide: NativeWindowService, useValue: new NativeWindowRef() }, { provide: TruncatableService, useValue: mockTruncatableService }, { provide: BitstreamDataService, useValue: mockBitstreamDataService }, { provide: ThemeService, useValue: mockThemeService }, diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts index 09109ee400..09d603e6b7 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts @@ -9,6 +9,7 @@ import { getMockTranslateService } from '../../mocks/translate.service.mock'; import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; import { mockTruncatableService } from '../../mocks/mock-trucatable.service'; import { By } from '@angular/platform-browser'; +import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; describe('TruncatablePartComponent', () => { let comp: TruncatablePartComponent; @@ -29,7 +30,7 @@ describe('TruncatablePartComponent', () => { }; beforeEach(waitForAsync(() => { translateService = getMockTranslateService(); - TestBed.configureTestingModule({ + void TestBed.configureTestingModule({ imports: [NoopAnimationsModule, TranslateModule.forRoot({ loader: { @@ -40,6 +41,7 @@ describe('TruncatablePartComponent', () => { ], declarations: [TruncatablePartComponent], providers: [ + { provide: NativeWindowService, useValue: new NativeWindowRef() }, { provide: TruncatableService, useValue: truncatableServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] @@ -110,7 +112,7 @@ describe('TruncatablePartComponent', () => { let truncatableService; beforeEach(waitForAsync(() => { translateService = getMockTranslateService(); - TestBed.configureTestingModule({ + void TestBed.configureTestingModule({ imports: [ NoopAnimationsModule, TranslateModule.forRoot({ @@ -122,6 +124,7 @@ describe('TruncatablePartComponent', () => { ], declarations: [TruncatablePartComponent], providers: [ + { provide: NativeWindowService, useValue: new NativeWindowRef() }, { provide: TruncatableService, useValue: mockTruncatableService }, ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts index fe8279ee07..7fe36e950a 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts @@ -2,7 +2,7 @@ import { Component, Inject, Input, OnDestroy, OnInit, PLATFORM_ID } from '@angul import { TruncatableService } from '../truncatable.service'; import { hasValue } from '../../empty.util'; import { DOCUMENT, isPlatformBrowser } from '@angular/common'; -import { NativeWindowRef, NativeWindowService } from 'src/app/core/services/window.service'; +import { NativeWindowRef, NativeWindowService } from '../../../core/services/window.service'; @Component({ selector: 'ds-truncatable-part', From 7a193cc9a2971739980d9e486d7afb8c5db564ff Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 27 Apr 2022 11:14:47 +0200 Subject: [PATCH 026/151] [CST-5307] Fixed compilation --- .../profile/model/researcher-profile.model.ts | 2 +- .../core/profile/researcher-profile.service.ts | 2 +- .../profile-claim/profile-claim.service.ts | 18 ++++++------------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/app/core/profile/model/researcher-profile.model.ts b/src/app/core/profile/model/researcher-profile.model.ts index 1a9e75cbf6..e5098496d3 100644 --- a/src/app/core/profile/model/researcher-profile.model.ts +++ b/src/app/core/profile/model/researcher-profile.model.ts @@ -1,10 +1,10 @@ import { autoserialize, deserialize, deserializeAs } from 'cerialize'; import { typedObject } from '../../cache/builders/build-decorators'; -import { CacheableObject } from '../../cache/object-cache.reducer'; 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"; /** * Class the represents a Researcher Profile. diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index c61c5ca9f9..3b4144b77c 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -10,7 +10,6 @@ import { NotificationsService } from '../../shared/notifications/notifications.s import { dataService } from '../cache/builders/build-decorators'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { CoreState } from '../core.reducers'; import { ConfigurationDataService } from '../data/configuration-data.service'; import { DataService } from '../data/data.service'; import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; @@ -31,6 +30,7 @@ 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"; /* tslint:disable:max-classes-per-file */ 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 f1841ac0b5..1c86ba29f1 100644 --- a/src/app/profile-page/profile-claim/profile-claim.service.ts +++ b/src/app/profile-page/profile-claim/profile-claim.service.ts @@ -10,7 +10,7 @@ import { SearchService } from '../../core/shared/search/search.service'; import { hasValue } 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, getFirstSucceededRemoteDataPayload } from './../../core/shared/operators'; +import { getFirstSucceededRemoteData } from './../../core/shared/operators'; @Injectable() export class ProfileClaimService { @@ -27,18 +27,10 @@ export class ProfileClaimService { return of(false); } - return this.configurationService.findByPropertyName('claimable.entityType').pipe( - getFirstSucceededRemoteDataPayload(), - switchMap((claimableTypes) => { - if (!claimableTypes.values || claimableTypes.values.length === 0) { - return of(false); - } else { - return this.lookup(query).pipe( - mergeMap((rd: RemoteData>>) => of(rd.payload.totalElements > 0)) - ); - } - }) + return this.lookup(query).pipe( + mergeMap((rd: RemoteData>>) => of(rd.payload.totalElements > 0)) ); + } search(eperson: EPerson): Observable>>> { @@ -73,4 +65,6 @@ export class ProfileClaimService { if (!hasValue(value)) {return;} query.push(metadata + ':' + value); } + + } From 107199eb8e474848e0ff634f5c65687837a62e63 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Wed, 27 Apr 2022 13:40:33 +0200 Subject: [PATCH 027/151] [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 028/151] [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 029/151] [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 aaa166593e8fb2a2f082808934a0ad95c890d639 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 27 Apr 2022 18:59:29 +0200 Subject: [PATCH 030/151] [DSC-516] Fix accessibility issues --- .../truncatable-part.component.html | 16 +++++++--------- .../truncatable-part.component.scss | 15 ++++++--------- .../truncatable-part.component.spec.ts | 4 ++-- .../truncatable-part.component.ts | 11 +++++------ 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html index 34227e2583..cb9f529f99 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html @@ -1,11 +1,9 @@
-
- -
- - - {{ 'item.truncatable-part.show-less' | translate }} +
+ +
+ +
diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss index 7a1f31f578..f67d343cd6 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss @@ -1,10 +1,7 @@ -#dontBreakContent:not(.truncated) ~ label{ - display: none; - } +.content:not(.truncated) ~ button.expandButton { + display: none; +} -a { - color: #207698 !important; - text-decoration: none !important; - background-color: transparent !important; - cursor: pointer; -} \ No newline at end of file +.btn:focus { + box-shadow: none !important; +} diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts index 09d603e6b7..08d3e18117 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.spec.ts @@ -70,7 +70,7 @@ describe('TruncatablePartComponent', () => { }); it('collapseButton should be hidden', () => { - const a = fixture.debugElement.query(By.css('#collapseButton')); + const a = fixture.debugElement.query(By.css('.collapseButton')); expect(a).toBeNull(); }); }); @@ -98,7 +98,7 @@ describe('TruncatablePartComponent', () => { (comp as any).setLines(); (comp as any).expandable = true; fixture.detectChanges(); - const a = fixture.debugElement.query(By.css('#collapseButton')); + const a = fixture.debugElement.query(By.css('.collapseButton')); expect(a).not.toBeNull(); }); }); diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts index 7fe36e950a..0bfcf25d39 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts @@ -1,4 +1,4 @@ -import { Component, Inject, Input, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; +import { AfterContentChecked, Component, Inject, Input, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { TruncatableService } from '../truncatable.service'; import { hasValue } from '../../empty.util'; import { DOCUMENT, isPlatformBrowser } from '@angular/common'; @@ -14,7 +14,7 @@ import { NativeWindowRef, NativeWindowService } from '../../../core/services/win * Component that truncates/clamps a piece of text * It needs a TruncatableComponent parent to identify it's current state */ -export class TruncatablePartComponent implements OnInit, OnDestroy { +export class TruncatablePartComponent implements AfterContentChecked, OnInit, OnDestroy { /** * Number of lines shown when the part is collapsed */ @@ -116,10 +116,9 @@ export class TruncatablePartComponent implements OnInit, OnDestroy { * Function to get data to be observed */ toObserve() { - this.observedContent = this.document.querySelectorAll('#dontBreakContent'); - this.observer = new (this._window.nativeWindow as any).ResizeObserver(entries => { - // tslint:disable-next-line:prefer-const - for (let entry of entries) { + this.observedContent = this.document.querySelectorAll('.content'); + this.observer = new (this._window.nativeWindow as any).ResizeObserver((entries) => { + for (let entry of entries) { if (!entry.target.classList.contains('notruncatable')) { if (entry.target.scrollHeight > entry.contentRect.height) { if (entry.target.children.length > 0) { From 44f393d65a54db0ec8c1b4b6663267da783b1f87 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 28 Apr 2022 11:44:53 +0200 Subject: [PATCH 031/151] [CST-5674] Fixed link in TODO --- .../resource-policies/form/resource-policy-form.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.ts b/src/app/shared/resource-policies/form/resource-policy-form.component.ts index 7ae699340e..0a7f0b7a90 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.ts +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.ts @@ -193,11 +193,11 @@ export class ResourcePolicyFormComponent implements OnInit, OnDestroy { */ private buildResourcePolicyForm(): DynamicFormControlModel[] { const formModel: DynamicFormControlModel[] = []; - // TODO to be removed when https://jira.lyrasis.org/browse/DS-4477 will be implemented + // TODO to be removed when https://github.com/DSpace/DSpace/issues/7812 will be implemented const policyTypeConf = Object.assign({}, RESOURCE_POLICY_FORM_POLICY_TYPE_CONFIG, { disabled: isNotEmpty(this.resourcePolicy) }); - // TODO to be removed when https://jira.lyrasis.org/browse/DS-4477 will be implemented + // TODO to be removed when https://github.com/DSpace/DSpace/issues/7812 will be implemented const actionConf = Object.assign({}, RESOURCE_POLICY_FORM_ACTION_TYPE_CONFIG, { disabled: isNotEmpty(this.resourcePolicy) }); From a754a20ec6d7ca96a4b93421cf333dbdcb002ffa Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Thu, 28 Apr 2022 18:12:15 +0200 Subject: [PATCH 032/151] [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 033/151] [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 034/151] [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 2e979afd22fdfe19427761189d0e64fa10bb4b32 Mon Sep 17 00:00:00 2001 From: Yura Bondarenko Date: Thu, 28 Apr 2022 16:48:24 +0200 Subject: [PATCH 035/151] 90948: Show notifications when failing to create/delete role Groups --- .../collection-roles.component.spec.ts | 3 ++ .../community-roles.component.spec.ts | 3 ++ .../comcol-role/comcol-role.component.html | 2 +- .../comcol-role/comcol-role.component.spec.ts | 30 +++++++++++++++++++ .../comcol-role/comcol-role.component.ts | 25 ++++++++++++++-- src/assets/i18n/en.json5 | 4 +++ 6 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.spec.ts b/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.spec.ts index 985290a592..5a8ca5b7ab 100644 --- a/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.spec.ts +++ b/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.spec.ts @@ -13,6 +13,8 @@ import { RouterTestingModule } from '@angular/router/testing'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ComcolModule } from '../../../shared/comcol/comcol.module'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; describe('CollectionRolesComponent', () => { @@ -79,6 +81,7 @@ describe('CollectionRolesComponent', () => { { provide: ActivatedRoute, useValue: route }, { provide: RequestService, useValue: requestService }, { provide: GroupDataService, useValue: groupDataService }, + { provide: NotificationsService, useClass: NotificationsServiceStub } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/community-page/edit-community-page/community-roles/community-roles.component.spec.ts b/src/app/community-page/edit-community-page/community-roles/community-roles.component.spec.ts index d1188df02d..baea5fb9d0 100644 --- a/src/app/community-page/edit-community-page/community-roles/community-roles.component.spec.ts +++ b/src/app/community-page/edit-community-page/community-roles/community-roles.component.spec.ts @@ -13,6 +13,8 @@ import { RouterTestingModule } from '@angular/router/testing'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ComcolModule } from '../../../shared/comcol/comcol.module'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; describe('CommunityRolesComponent', () => { @@ -64,6 +66,7 @@ describe('CommunityRolesComponent', () => { { provide: ActivatedRoute, useValue: route }, { provide: RequestService, useValue: requestService }, { provide: GroupDataService, useValue: groupDataService }, + { provide: NotificationsService, useClass: NotificationsServiceStub } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html index b0a319b4ff..905186a232 100644 --- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html +++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.html @@ -4,7 +4,7 @@ *ngVar="group$ | async as group">
- {{'comcol-role.edit.' + (comcolRole$ | async)?.name + '.name' | translate}} + {{ roleName$ | async }}
diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts index 175abe48e4..59fc95b67f 100644 --- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts +++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts @@ -10,6 +10,8 @@ import { RouterTestingModule } from '@angular/router/testing'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ComcolModule } from '../../../comcol.module'; +import { NotificationsService } from '../../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../../testing/notifications-service.stub'; describe('ComcolRoleComponent', () => { @@ -20,6 +22,7 @@ describe('ComcolRoleComponent', () => { let group; let statusCode; let comcolRole; + let notificationsService; const requestService = { hasByHref$: () => observableOf(true) }; @@ -40,6 +43,7 @@ describe('ComcolRoleComponent', () => { providers: [ { provide: GroupDataService, useValue: groupService }, { provide: RequestService, useValue: requestService }, + { provide: NotificationsService, useClass: NotificationsServiceStub } ], schemas: [ NO_ERRORS_SCHEMA ] @@ -59,12 +63,14 @@ describe('ComcolRoleComponent', () => { fixture = TestBed.createComponent(ComcolRoleComponent); comp = fixture.componentInstance; de = fixture.debugElement; + notificationsService = TestBed.inject(NotificationsService); comcolRole = { name: 'test role name', href: 'test role link', }; comp.comcolRole = comcolRole; + comp.roleName$ = observableOf(comcolRole.name); fixture.detectChanges(); }); @@ -101,6 +107,18 @@ describe('ComcolRoleComponent', () => { done(); }); }); + + describe('when a group cannot be created', () => { + beforeEach(() => { + groupService.createComcolGroup.and.returnValue(createFailedRemoteDataObject$()); + de.query(By.css('.btn.create')).nativeElement.click(); + }); + + it('should show an error notification', (done) => { + expect(notificationsService.error).toHaveBeenCalled(); + done(); + }); + }); }); describe('when the related group is the Anonymous group', () => { @@ -169,5 +187,17 @@ describe('ComcolRoleComponent', () => { done(); }); }); + + describe('when a group cannot be deleted', () => { + beforeEach(() => { + groupService.deleteComcolGroup.and.returnValue(createFailedRemoteDataObject$()); + de.query(By.css('.btn.delete')).nativeElement.click(); + }); + + it('should show an error notification', (done) => { + expect(notificationsService.error).toHaveBeenCalled(); + done(); + }); + }); }); }); diff --git a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts index 7ed88fae1c..3091dd0cf0 100644 --- a/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts +++ b/src/app/shared/comcol/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts @@ -12,6 +12,8 @@ import { HALLink } from '../../../../../core/shared/hal-link.model'; import { getGroupEditRoute } from '../../../../../access-control/access-control-routing-paths'; import { hasNoValue, hasValue } from '../../../../empty.util'; import { NoContent } from '../../../../../core/shared/NoContent.model'; +import { NotificationsService } from '../../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; /** * Component for managing a community or collection role. @@ -64,9 +66,16 @@ export class ComcolRoleComponent implements OnInit { */ hasCustomGroup$: Observable; + /** + * The human-readable name of this role + */ + roleName$: Observable; + constructor( protected requestService: RequestService, protected groupService: GroupDataService, + protected notificationsService: NotificationsService, + protected translateService: TranslateService, ) { } @@ -101,7 +110,12 @@ export class ComcolRoleComponent implements OnInit { this.groupService.clearGroupsRequests(); this.requestService.setStaleByHrefSubstring(this.comcolRole.href); } else { - // TODO show error notification + this.notificationsService.error( + this.roleName$.pipe( + switchMap(role => this.translateService.get('comcol-role.edit.create.error.title', { role })) + ), + `${rd.statusCode} ${rd.errorMessage}` + ); } }); } @@ -117,7 +131,12 @@ export class ComcolRoleComponent implements OnInit { this.groupService.clearGroupsRequests(); this.requestService.setStaleByHrefSubstring(this.comcolRole.href); } else { - // TODO show error notification + this.notificationsService.error( + this.roleName$.pipe( + switchMap(role => this.translateService.get('comcol-role.edit.delete.error.title', { role })) + ), + rd.errorMessage + ); } }); } @@ -154,5 +173,7 @@ export class ComcolRoleComponent implements OnInit { this.hasCustomGroup$ = this.group$.pipe( map((group: Group) => hasValue(group) && group.name !== 'Anonymous'), ); + + this.roleName$ = this.translateService.get(`comcol-role.edit.${this.comcolRole.name}.name`); } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index f742273edb..9121be759b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1082,10 +1082,14 @@ "comcol-role.edit.create": "Create", + "comcol-role.edit.create.error.title": "Failed to create a group for the '{{ role }}' role", + "comcol-role.edit.restrict": "Restrict", "comcol-role.edit.delete": "Delete", + "comcol-role.edit.delete.error.title": "Failed to delete the '{{ role }}' role's group", + "comcol-role.edit.community-admin.name": "Administrators", From e94454be97d7a84502295cf9bfa586aba5b970e0 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 29 Apr 2022 12:11:06 +0200 Subject: [PATCH 036/151] [CST-5674] Update selected ePerson/Group --- .../resource-policies/form/resource-policy-form.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.ts b/src/app/shared/resource-policies/form/resource-policy-form.component.ts index 0a7f0b7a90..07385b46c0 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.ts +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.ts @@ -274,6 +274,7 @@ export class ResourcePolicyFormComponent implements OnInit, OnDestroy { updateObjectSelected(object: DSpaceObject, isEPerson: boolean): void { this.resourcePolicyGrant = object; this.resourcePolicyGrantType = isEPerson ? 'eperson' : 'group'; + this.resourcePolicyTargetName$.next(this.getResourcePolicyTargetName()); } /** From 342a62081ccb9f56d1cc519af861b32355fa279d Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 29 Apr 2022 12:17:48 +0200 Subject: [PATCH 037/151] [CST-5674] Enable ePerson/Group editing; prevent switching between ePerson and Group --- .../form/resource-policy-form.component.html | 10 ++-- .../resource-policy-form.component.spec.ts | 4 +- .../form/resource-policy-form.component.ts | 60 +++++++++++-------- 3 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.html b/src/app/shared/resource-policies/form/resource-policy-form.component.html index 42475a955b..51fbd6b526 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.html +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.html @@ -7,16 +7,16 @@ [displayCancel]="false">
- - -
+ + + + + + + diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.ts b/src/app/shared/resource-policies/form/resource-policy-form.component.ts index 16cebfa899..ea49433db0 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.ts +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; import { Observable, @@ -41,7 +41,7 @@ import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; import { GroupDataService } from '../../../core/eperson/group-data.service'; import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { RequestService } from '../../../core/data/request.service'; -import { NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'; +import { NgbModal, NgbNavChangeEvent } from '@ng-bootstrap/ng-bootstrap'; export interface ResourcePolicyEvent { object: ResourcePolicy; @@ -84,6 +84,8 @@ export class ResourcePolicyFormComponent implements OnInit, OnDestroy { */ @Output() submit: EventEmitter = new EventEmitter(); + @ViewChild('content') content: ElementRef; + /** * The form id * @type {string} @@ -136,6 +138,7 @@ export class ResourcePolicyFormComponent implements OnInit, OnDestroy { * @param {FormService} formService * @param {GroupDataService} groupService * @param {RequestService} requestService + * @param modalService */ constructor( private dsoNameService: DSONameService, @@ -143,6 +146,7 @@ export class ResourcePolicyFormComponent implements OnInit, OnDestroy { private formService: FormService, private groupService: GroupDataService, private requestService: RequestService, + private modalService: NgbModal, ) { } @@ -334,12 +338,10 @@ export class ResourcePolicyFormComponent implements OnInit, OnDestroy { } onNavChange(changeEvent: NgbNavChangeEvent) { - console.log(`CHANGE ${changeEvent.activeId} -> ${changeEvent.nextId}`); - + // if a policy is being edited it should not be possible to switch between group and eperson if (this.isBeingEdited()) { - // if a policy is being edited it should not be possible to switch between group and eperson changeEvent.preventDefault(); - // TODO add informative modal + this.modalService.open(this.content); } } } From 6b3c6c2dda579a8d6e99b69c8b5fa8e9fe37bd10 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 29 Apr 2022 15:34:57 +0200 Subject: [PATCH 039/151] [CST-5307] Fixed lint --- src/app/core/profile/model/researcher-profile.model.ts | 2 +- src/app/core/profile/researcher-profile.service.ts | 5 ++--- src/app/item-page/simple/item-page.component.ts | 4 ++-- .../profile-page-researcher-form.component.ts | 6 +++--- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/app/core/profile/model/researcher-profile.model.ts b/src/app/core/profile/model/researcher-profile.model.ts index e5098496d3..a07467476e 100644 --- a/src/app/core/profile/model/researcher-profile.model.ts +++ b/src/app/core/profile/model/researcher-profile.model.ts @@ -4,7 +4,7 @@ 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'; /** * Class the represents a Researcher Profile. diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index 3b4144b77c..0220afb964 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; @@ -30,9 +31,7 @@ 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"; - -/* tslint:disable:max-classes-per-file */ +import {CoreState} from '../core-state.model'; /** * A private DataService implementation to delegate specific methods to. diff --git a/src/app/item-page/simple/item-page.component.ts b/src/app/item-page/simple/item-page.component.ts index 32a0d9f4c1..34a059246d 100644 --- a/src/app/item-page/simple/item-page.component.ts +++ b/src/app/item-page/simple/item-page.component.ts @@ -80,7 +80,7 @@ export class ItemPageComponent implements OnInit { this.route.data.pipe( map((data) => data.dso as RemoteData) ).subscribe((data: RemoteData) => { - this.itemUrl = data?.payload?.self + this.itemUrl = data?.payload?.self; }); } @@ -102,7 +102,7 @@ export class ItemPageComponent implements OnInit { this.authorizationService.isAuthorized(FeatureID.ShowClaimItem, this.itemUrl).pipe( take(1) ).subscribe((isAuthorized: boolean) => { - this.claimable$.next(isAuthorized) + this.claimable$.next(isAuthorized); }); } 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 6a0b687afa..e9d9cb6d05 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 @@ -128,10 +128,10 @@ export class ProfilePageResearcherFormComponent implements OnInit { * @param researcherProfile the profile to update */ toggleProfileVisibility(researcherProfile: ResearcherProfile): void { - /* tslint:disable:no-empty */ this.researcherProfileService.setVisibility(researcherProfile, !researcherProfile.visible) - .subscribe((updatedProfile) => {}); // this.researcherProfile$.next(updatedProfile); - /* tslint:enable:no-empty */ + .subscribe((updatedProfile) => { + this.researcherProfile$.next(updatedProfile); + }); } /** From 788a326592ee5e4462eb1ab27f69b090d1d8dcdd Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Fri, 29 Apr 2022 15:51:35 +0200 Subject: [PATCH 040/151] [CST-5307] Fixed test --- ...ile-page-researcher-form.component.spec.ts | 23 +------------------ 1 file changed, 1 insertion(+), 22 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 bacb3469ad..d12c445ce4 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,11 +16,6 @@ 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 { EditItemDataService } from '../../core/submission/edititem-data.service'; -import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; -import { EditItemMode } from '../../core/submission/models/edititem-mode.model'; -import { EditItem } from '../../core/submission/models/edititem.model'; -import { createPaginatedList } from '../../shared/testing/utils.test'; describe('ProfilePageResearcherFormComponent', () => { @@ -39,17 +34,6 @@ describe('ProfilePageResearcherFormComponent', () => { let authService: AuthService; - let editItemDataService: any; - - const editItemMode: EditItemMode = Object.assign(new EditItemMode(), { - name: 'test', - label: 'test' - }); - - const editItem: EditItem = Object.assign(new EditItem(), { - modes: createSuccessfulRemoteDataObject$(createPaginatedList([editItemMode])) - }); - function init() { user = Object.assign(new EPerson(), { @@ -80,10 +64,6 @@ describe('ProfilePageResearcherFormComponent', () => { canClaimProfiles: observableOf(false), }); - editItemDataService = jasmine.createSpyObj('EditItemDataService', { - findById: createSuccessfulRemoteDataObject$(editItem) - }); - } beforeEach(waitForAsync(() => { @@ -96,8 +76,7 @@ describe('ProfilePageResearcherFormComponent', () => { { provide: ResearcherProfileService, useValue: researcherProfileService }, { provide: NotificationsService, useValue: notificationsServiceStub }, { provide: ProfileClaimService, useValue: profileClaimService }, - { provide: AuthService, useValue: authService }, - { provide: EditItemDataService, useValue: editItemDataService } + { provide: AuthService, useValue: authService } ], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); From 8498504b939c5e184800fa030ff1e97d88b82a46 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 29 Apr 2022 16:14:47 +0200 Subject: [PATCH 041/151] [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 bb3cc1c619dfe0e2f9a8bfb55380f8f5eb0671e9 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 2 May 2022 17:02:04 +0200 Subject: [PATCH 042/151] [CST-5674] Edit policy target; modal content; test --- .../resource-policy.service.spec.ts | 8 ++- .../resource-policy.service.ts | 55 +++++++++++++++++-- .../edit/resource-policy-edit.component.ts | 33 +++++++---- .../form/resource-policy-form.component.html | 15 ++++- .../resource-policy-form.component.spec.ts | 13 +++-- .../form/resource-policy-form.component.ts | 5 ++ src/assets/i18n/en.json5 | 10 ++++ 7 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/app/core/resource-policy/resource-policy.service.spec.ts b/src/app/core/resource-policy/resource-policy.service.spec.ts index 59316c0098..ca62159f59 100644 --- a/src/app/core/resource-policy/resource-policy.service.spec.ts +++ b/src/app/core/resource-policy/resource-policy.service.spec.ts @@ -19,6 +19,8 @@ import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils import { RestResponse } from '../cache/response.models'; import { RequestEntry } from '../data/request-entry.model'; import { FindListOptions } from '../data/find-list-options.model'; +import { EPersonDataService } from '../eperson/eperson-data.service'; +import { GroupDataService } from '../eperson/group-data.service'; describe('ResourcePolicyService', () => { let scheduler: TestScheduler; @@ -28,6 +30,8 @@ describe('ResourcePolicyService', () => { let objectCache: ObjectCacheService; let halService: HALEndpointService; let responseCacheEntry: RequestEntry; + let ePersonService: EPersonDataService; + let groupService: GroupDataService; const resourcePolicy: any = { id: '1', @@ -129,7 +133,9 @@ describe('ResourcePolicyService', () => { halService, notificationsService, http, - comparator + comparator, + ePersonService, + groupService ); spyOn((service as any).dataService, 'create').and.callThrough(); diff --git a/src/app/core/resource-policy/resource-policy.service.ts b/src/app/core/resource-policy/resource-policy.service.ts index 065e58c53d..f4f70a73e2 100644 --- a/src/app/core/resource-policy/resource-policy.service.ts +++ b/src/app/core/resource-policy/resource-policy.service.ts @@ -1,6 +1,6 @@ /* eslint-disable max-classes-per-file */ import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; @@ -23,11 +23,19 @@ import { PaginatedList } from '../data/paginated-list.model'; import { ActionType } from './models/action-type.model'; import { RequestParam } from '../cache/models/request-param.model'; import { isNotEmpty } from '../../shared/empty.util'; -import { map } from 'rxjs/operators'; +import { map, switchMap, take } from 'rxjs/operators'; import { NoContent } from '../shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../shared/operators'; import { CoreState } from '../core-state.model'; import { FindListOptions } from '../data/find-list-options.model'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; +import { PostRequest } from '../data/request.models'; +import { GenericConstructor } from '../shared/generic-constructor'; +import { ResponseParsingService } from '../data/parsing.service'; +import { StatusCodeOnlyResponseParsingService } from '../data/status-code-only-response-parsing.service'; +import { HALLink } from '../shared/hal-link.model'; +import { EPersonDataService } from '../eperson/eperson-data.service'; +import { GroupDataService } from '../eperson/group-data.service'; /** @@ -44,7 +52,8 @@ class DataServiceImpl extends DataService { protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: ChangeAnalyzer) { + protected comparator: ChangeAnalyzer, + ) { super(); } @@ -68,7 +77,10 @@ export class ResourcePolicyService { protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: DefaultChangeAnalyzer) { + protected comparator: DefaultChangeAnalyzer, + protected ePersonService: EPersonDataService, + protected groupService: GroupDataService, + ) { this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator); } @@ -221,4 +233,39 @@ export class ResourcePolicyService { return this.dataService.searchBy(this.searchByResourceMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } + /** + * Update the target of the resource policy + * @param resourcePolicyHref the link to the resource policy + * @param uuid the UUID of the target to which the permission is being granted + * @param type the type of the target (eperson or group) to which the permission is being granted + */ + updateTarget(resourcePolicyHref: string, uuid: string, type: string): Observable> { + + const targetService = type === 'eperson' ? this.ePersonService : this.groupService; + + const ep$ = targetService.getBrowseEndpoint().pipe( + take(1), + map((endpoint: string) =>`${endpoint}/${uuid}`), + ); + + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + + const requestId = this.requestService.generateRequestId(); + + return ep$.pipe(switchMap((ep) => { + const request = new PostRequest(requestId, resourcePolicyHref, ep, options); + Object.assign(request, { + getResponseParser(): GenericConstructor { + return StatusCodeOnlyResponseParsingService; + } + }); + this.requestService.send(request); + return this.rdbService.buildFromRequestUUID(requestId); + })); + + } + } diff --git a/src/app/shared/resource-policies/edit/resource-policy-edit.component.ts b/src/app/shared/resource-policies/edit/resource-policy-edit.component.ts index a515eef675..d2c1b6dd0d 100644 --- a/src/app/shared/resource-policies/edit/resource-policy-edit.component.ts +++ b/src/app/shared/resource-policies/edit/resource-policy-edit.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, of, combineLatest as observableCombineLatest, } from 'rxjs'; import { map, take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; @@ -88,16 +88,29 @@ export class ResourcePolicyEditComponent implements OnInit { type: RESOURCE_POLICY.value, _links: this.resourcePolicy._links }); - this.resourcePolicyService.update(updatedObject).pipe( + + const updateTargetSucceeded$ = event.updateTarget ? this.resourcePolicyService.updateTarget( + this.resourcePolicy._links.self.href, event.target.uuid, event.target.type + ).pipe( getFirstCompletedRemoteData(), - ).subscribe((responseRD: RemoteData) => { - this.processing$.next(false); - if (responseRD && responseRD.hasSucceeded) { - this.notificationsService.success(null, this.translate.get('resource-policies.edit.page.success.content')); - this.redirectToAuthorizationsPage(); - } else { - this.notificationsService.error(null, this.translate.get('resource-policies.edit.page.failure.content')); + map((responseRD) => responseRD && responseRD.hasSucceeded) + ) : of(true); + + const updateResourcePolicySucceeded$ = this.resourcePolicyService.update(updatedObject).pipe( + getFirstCompletedRemoteData(), + map((responseRD) => responseRD && responseRD.hasSucceeded) + ); + + observableCombineLatest([updateTargetSucceeded$, updateResourcePolicySucceeded$]).subscribe( + ([updateTargetSucceeded, updateResourcePolicySucceeded]) => { + this.processing$.next(false); + if (updateTargetSucceeded && updateResourcePolicySucceeded) { + this.notificationsService.success(null, this.translate.get('resource-policies.edit.page.success.content')); + this.redirectToAuthorizationsPage(); + } else { + this.notificationsService.error(null, this.translate.get('resource-policies.edit.page.failure.content')); + } } - }); + ); } } diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.html b/src/app/shared/resource-policies/form/resource-policy-form.component.html index 821d15f414..f7aad55ce8 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.html +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.html @@ -55,15 +55,24 @@ diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts b/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts index c31a65c1f6..456eb6db5e 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.spec.ts @@ -222,6 +222,8 @@ describe('ResourcePolicyFormComponent test suite', () => { testFixture = createTestComponent(html, TestComponent) as ComponentFixture; testComp = testFixture.componentInstance; + testComp.resourcePolicy = resourcePolicy; + fixture.detectChanges(); }); afterEach(() => { @@ -242,6 +244,7 @@ describe('ResourcePolicyFormComponent test suite', () => { fixture = TestBed.createComponent(ResourcePolicyFormComponent); comp = fixture.componentInstance; compAsAny = fixture.componentInstance; + compAsAny.resourcePolicy = resourcePolicy; comp.isProcessing = observableOf(false); }); @@ -261,7 +264,7 @@ describe('ResourcePolicyFormComponent test suite', () => { expect(compAsAny.buildResourcePolicyForm).toHaveBeenCalled(); expect(compAsAny.initModelsValue).toHaveBeenCalled(); expect(compAsAny.formModel.length).toBe(5); - expect(compAsAny.subs.length).toBe(0); + expect(compAsAny.subs.length).toBe(1); }); @@ -279,7 +282,7 @@ describe('ResourcePolicyFormComponent test suite', () => { expect(compAsAny.reset.emit).toHaveBeenCalled(); }); - it('should update resource policy grant object properly', () => { + it('should update resource policy grant object properly', () => { comp.updateObjectSelected(EPersonMock, true); expect(comp.resourcePolicyGrant).toEqual(EPersonMock); @@ -301,6 +304,7 @@ describe('ResourcePolicyFormComponent test suite', () => { comp = fixture.componentInstance; compAsAny = fixture.componentInstance; comp.resourcePolicy = resourcePolicy; + compAsAny.resourcePolicy = resourcePolicy; comp.isProcessing = observableOf(false); compAsAny.ePersonService.findByHref.and.returnValue( observableOf(createSuccessfulRemoteDataObject({})).pipe(delay(100)) @@ -343,8 +347,8 @@ describe('ResourcePolicyFormComponent test suite', () => { }); }); - it('should not can set grant', () => { - expect(comp.isBeingEdited()).toBeFalsy(); + it('should be being edited', () => { + expect(comp.isBeingEdited()).toBeTrue(); }); it('should have a target name', () => { @@ -398,6 +402,7 @@ describe('ResourcePolicyFormComponent test suite', () => { type: 'group', uuid: GroupMock.id }; + eventPayload.updateTarget = false; scheduler = getTestScheduler(); scheduler.schedule(() => comp.onSubmit()); diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.ts b/src/app/shared/resource-policies/form/resource-policy-form.component.ts index ea49433db0..2783200d8f 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.ts +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.ts @@ -49,6 +49,7 @@ export interface ResourcePolicyEvent { type: string, uuid: string }; + updateTarget: boolean; } @Component({ @@ -130,6 +131,8 @@ export class ResourcePolicyFormComponent implements OnInit, OnDestroy { navActiveId: string; + resourcePolicyTargetUpdated = false; + /** * Initialize instance variables * @@ -278,6 +281,7 @@ export class ResourcePolicyFormComponent implements OnInit, OnDestroy { * Update reference to the eperson or group that will be granted the permission */ updateObjectSelected(object: DSpaceObject, isEPerson: boolean): void { + this.resourcePolicyTargetUpdated = true; this.resourcePolicyGrant = object; this.resourcePolicyGrantType = isEPerson ? 'eperson' : 'group'; this.resourcePolicyTargetName$.next(this.getResourcePolicyTargetName()); @@ -304,6 +308,7 @@ export class ResourcePolicyFormComponent implements OnInit, OnDestroy { type: this.resourcePolicyGrantType, uuid: this.resourcePolicyGrant.id }; + eventPayload.updateTarget = this.resourcePolicyTargetUpdated; this.submit.emit(eventPayload); }); } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index a7ce942e7d..dfc715b72b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3136,6 +3136,16 @@ "resource-policies.form.eperson-group-list.table.headers.name": "Name", + "resource-policies.form.eperson-group-list.modal.header": "Cannot change type", + + "resource-policies.form.eperson-group-list.modal.text1.toGroup": "It is not possible to replace an ePerson with a group.", + + "resource-policies.form.eperson-group-list.modal.text1.toEPerson": "It is not possible to replace a group with an ePerson.", + + "resource-policies.form.eperson-group-list.modal.text2": "Delete the current resource policy and create a new one with the desired type.", + + "resource-policies.form.eperson-group-list.modal.close": "Ok", + "resource-policies.form.date.end.label": "End Date", "resource-policies.form.date.start.label": "Start Date", From 32a91f64d9d8db83390589628c76882b212c968b Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 3 May 2022 12:06:48 +0200 Subject: [PATCH 043/151] [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 044/151] [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 045/151] [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 804930fbe2b5cca46343d24bf7486034cf5cc4f8 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 3 May 2022 15:35:51 +0200 Subject: [PATCH 046/151] [CST-5307] Improved researcher profile component --- ...on-search-result-list-element.component.ts | 15 ++- .../item-pages/person/person.component.html | 3 +- .../person/person.component.spec.ts | 7 +- .../item-pages/person/person.component.ts | 93 ++++++++++++++++++- .../full/full-item-page.component.html | 3 +- .../full/full-item-page.component.ts | 9 +- .../item-page/simple/item-page.component.ts | 63 +------------ .../item-types/shared/item.component.spec.ts | 13 ++- ...rofile-page-researcher-form.component.html | 4 +- .../profile-page/profile-page.component.html | 17 ++-- .../profile-page.component.spec.ts | 10 ++ .../profile-page/profile-page.component.ts | 19 +++- 12 files changed, 162 insertions(+), 94 deletions(-) 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 a6f64bff1b..32785d7b8a 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 @@ -2,6 +2,9 @@ import { Component } from '@angular/core'; 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'; @listableObjectComponent('PersonSearchResult', ViewMode.ListElement) @Component({ @@ -14,9 +17,15 @@ import { ItemSearchResultListElementComponent } from '../../../../../shared/obje */ export class PersonSearchResultListElementComponent extends ItemSearchResultListElementComponent { + public constructor(protected truncatableService: TruncatableService, protected dsoNameService: DSONameService) { + super(truncatableService, dsoNameService); + } + get name() { - return this.value ? - this.value : - this.firstMetadataValue('person.familyName') + ', ' + this.firstMetadataValue('person.givenName'); + 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; } } 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 8cf6117121..7505a31327 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 @@ -1,9 +1,10 @@

- {{'person.page.titleprefix' | translate}} + {{'person.page.titleprefix' | 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 546621700a..93b3cf208d 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 @@ -54,7 +54,12 @@ const mockItem: Item = Object.assign(new Item(), { } ] }, - relationships: createRelationshipsObservable() + relationships: createRelationshipsObservable(), + _links: { + self : { + href: 'item-href' + } + } }); describe('PersonComponent', getItemPageFieldsTest(mockItem, 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 8b104cc9b1..42eb4ec7e6 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,7 +1,20 @@ -import { Component } from '@angular/core'; +import {Component, OnInit} 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'; @listableObjectComponent('Person', ViewMode.StandalonePage) @Component({ @@ -12,5 +25,81 @@ import { listableObjectComponent } from '../../../../shared/object-collection/sh /** * The component for displaying metadata and relations of an item of the type Person */ -export class PersonComponent extends ItemComponent { +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.ShowClaimItem, this.object._links.self.href).pipe( + take(1) + ).subscribe((isAuthorized: boolean) => { + this.claimable$.next(isAuthorized); + }); + + } + + claim() { + + this.authorizationService.isAuthorized(FeatureID.CanClaimItem, this.object._links.self.href).pipe( + take(1) + ).subscribe((isAuthorized: boolean) => { + if (!isAuthorized) { + this.notificationsService.warning(this.translate.get('researcherprofile.claim.not-authorized')); + } else { + this.createFromExternalSource(); + } + }); + + } + + createFromExternalSource() { + 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')); + } + }); + } + + isClaimable(): Observable { + return this.claimable$; + } + + 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){ + metadataValues.push(familyName); + } + if (givenName){ + metadataValues.push(givenName); + } + if (metadataValues.length === 0 && title){ + metadataValues.push(title); + } + return metadataValues; + } + } diff --git a/src/app/item-page/full/full-item-page.component.html b/src/app/item-page/full/full-item-page.component.html index bfa25ec009..7cc8ff92c4 100644 --- a/src/app/item-page/full/full-item-page.component.html +++ b/src/app/item-page/full/full-item-page.component.html @@ -10,7 +10,6 @@
-
- \ No newline at end of file + diff --git a/src/app/item-page/full/full-item-page.component.ts b/src/app/item-page/full/full-item-page.component.ts index 99cf083bc5..369769c77d 100644 --- a/src/app/item-page/full/full-item-page.component.ts +++ b/src/app/item-page/full/full-item-page.component.ts @@ -16,10 +16,6 @@ import { hasValue } from '../../shared/empty.util'; import { AuthService } from '../../core/auth/auth.service'; import { Location } from '@angular/common'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { TranslateService } from '@ngx-translate/core'; -import { NotificationsService } from '../../shared/notifications/notifications.service'; -import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; -import { CollectionDataService } from '../../core/data/collection-data.service'; /** @@ -52,11 +48,8 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit, items: ItemDataService, authService: AuthService, authorizationService: AuthorizationDataService, - translate: TranslateService, - notificationsService: NotificationsService, - researcherProfileService: ResearcherProfileService, private _location: Location) { - super(route, router, items, authService, authorizationService, translate, notificationsService, researcherProfileService); + super(route, router, items, authService, authorizationService); } /*** AoT inheritance fix, will hopefully be resolved in the near future **/ diff --git a/src/app/item-page/simple/item-page.component.ts b/src/app/item-page/simple/item-page.component.ts index 34a059246d..b3660eb9d7 100644 --- a/src/app/item-page/simple/item-page.component.ts +++ b/src/app/item-page/simple/item-page.component.ts @@ -64,24 +64,13 @@ export class ItemPageComponent implements OnInit { itemUrl: string; - public claimable$: BehaviorSubject = new BehaviorSubject(false); - public isProcessing$: BehaviorSubject = new BehaviorSubject(false); - constructor( protected route: ActivatedRoute, private router: Router, private items: ItemDataService, private authService: AuthService, - private authorizationService: AuthorizationDataService, - private translate: TranslateService, - private notificationsService: NotificationsService, - private researcherProfileService: ResearcherProfileService + private authorizationService: AuthorizationDataService ) { - this.route.data.pipe( - map((data) => data.dso as RemoteData) - ).subscribe((data: RemoteData) => { - this.itemUrl = data?.payload?.self; - }); } /** @@ -99,55 +88,5 @@ export class ItemPageComponent implements OnInit { this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf); - this.authorizationService.isAuthorized(FeatureID.ShowClaimItem, this.itemUrl).pipe( - take(1) - ).subscribe((isAuthorized: boolean) => { - this.claimable$.next(isAuthorized); - }); - } - - claim() { - this.isProcessing$.next(true); - - this.authorizationService.isAuthorized(FeatureID.CanClaimItem, this.itemUrl).pipe( - take(1) - ).subscribe((isAuthorized: boolean) => { - if (!isAuthorized) { - this.notificationsService.warning(this.translate.get('researcherprofile.claim.not-authorized')); - this.isProcessing$.next(false); - } else { - this.createFromExternalSource(); - } - }); - - } - - createFromExternalSource() { - this.researcherProfileService.createFromExternalSource(this.itemUrl).pipe( - tap((rd: any) => { - if (!rd.hasSucceeded) { - this.isProcessing$.next(false); - } - }), - 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); - this.isProcessing$.next(false); - } else { - this.notificationsService.error( - this.translate.get('researcherprofile.error.claim.title'), - this.translate.get('researcherprofile.error.claim.body')); - } - }); - } - - isClaimable(): Observable { - return this.claimable$; } } diff --git a/src/app/item-page/simple/item-types/shared/item.component.spec.ts b/src/app/item-page/simple/item-types/shared/item.component.spec.ts index fc07f60b28..6f7684b896 100644 --- a/src/app/item-page/simple/item-types/shared/item.component.spec.ts +++ b/src/app/item-page/simple/item-types/shared/item.component.spec.ts @@ -4,7 +4,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { Store } from '@ngrx/store'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { Observable } from 'rxjs'; +import {Observable, of as observableOf} from 'rxjs'; import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../../../core/cache/object-cache.service'; import { BitstreamDataService } from '../../../../core/data/bitstream-data.service'; @@ -32,6 +32,8 @@ import { ItemComponent } from './item.component'; import { createPaginatedList } from '../../../../shared/testing/utils.test'; import { RouteService } from '../../../../core/services/route.service'; import { MetadataValue } from '../../../../core/shared/metadata.models'; +import {AuthorizationDataService} from '../../../../core/data/feature-authorization/authorization-data.service'; +import {ResearcherProfileService} from '../../../../core/profile/researcher-profile.service'; export const iiifEnabled = Object.assign(new MetadataValue(),{ 'value': 'true', @@ -69,6 +71,11 @@ export function getItemPageFieldsTest(mockItem: Item, component) { return createSuccessfulRemoteDataObject$(new Bitstream()); } }; + + const authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true) + }); + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot({ loader: { @@ -92,7 +99,9 @@ export function getItemPageFieldsTest(mockItem: Item, component) { { provide: NotificationsService, useValue: {} }, { provide: DefaultChangeAnalyzer, useValue: {} }, { provide: BitstreamDataService, useValue: mockBitstreamDataService }, - { provide: RouteService, useValue: {} } + { provide: RouteService, useValue: {} }, + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: ResearcherProfileService, useValue: {} } ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html index b2d53ea0e3..bb55418744 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.html @@ -9,7 +9,7 @@

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

- - + +
+ + + +
+ +
+
+
+
+ +
- diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.scss b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.scss new file mode 100644 index 0000000000..c3694e6784 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.scss @@ -0,0 +1,4 @@ +.auth-bitstream-container { + margin-top: -1em; + margin-bottom: 1.5em; +} diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts index 76597a135b..f3313d4cef 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts @@ -1,3 +1,5 @@ +import { isEqual } from 'lodash'; +import { DSONameService } from './../../../core/breadcrumbs/dso-name.service'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @@ -25,7 +27,8 @@ interface BundleBitstreamsMapEntry { @Component({ selector: 'ds-item-authorizations', - templateUrl: './item-authorizations.component.html' + templateUrl: './item-authorizations.component.html', + styleUrls:['./item-authorizations.component.scss'] }) /** * Component that handles the item Authorizations @@ -36,14 +39,20 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * A map that contains all bitstream of the item's bundles * @type {Observable>>>} */ - public bundleBitstreamsMap: Map>> = new Map>>(); + public bundleBitstreamsMap: Map = new Map(); /** - * The list of bundle for the item + * The list of all bundles for the item * @type {Observable>} */ private bundles$: BehaviorSubject = new BehaviorSubject([]); + /** + * The list of bundles to be displayed in the template + * @type {BehaviorSubject} + */ + bundlesToShow$: BehaviorSubject = new BehaviorSubject([]); + /** * The target editing item * @type {Observable} @@ -56,6 +65,37 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { */ private subs: Subscription[] = []; + /** + * The size of the bundles to be loaded on demand + * @type {number} + */ + bunblesPerPage = 6; + + /** + * The number of current page + * @type {number} + */ + bunblesPageSize = 1; + + /** + * The flag to show or not the 'Load more' button + * based on the condition if all the bundles are loaded or not + * @type {boolean} + */ + allBundlesLoaded = false; + + /** + * Initial size of loaded bitstreams + * The size of incrementation used in bitstream pagination + */ + bitstreamSize = 4; + + /** + * The size of the loaded bitstremas at a certain moment + * @private + */ + private bitstreamPageSize = 4; + /** * Initialize instance variables * @@ -64,7 +104,8 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { */ constructor( private linkService: LinkService, - private route: ActivatedRoute + private route: ActivatedRoute, + private nameService: DSONameService ) { } @@ -72,16 +113,53 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * Initialize the component, setting up the bundle and bitstream within the item */ ngOnInit(): void { + this.getBundlesPerItem(); + } + + /** + * Return the item's UUID + */ + getItemUUID(): Observable { + return this.item$.pipe( + map((item: Item) => item.id), + first((UUID: string) => isNotEmpty(UUID)) + ); + } + + /** + * Return the item's name + */ + getItemName(): Observable { + return this.item$.pipe( + map((item: Item) => this.nameService.getName(item)) + ); + } + + /** + * Return all item's bundles + * + * @return an observable that emits all item's bundles + */ + getItemBundles(): Observable { + return this.bundles$.asObservable(); + } + + /** + * Get all bundles per item + * and all the bitstreams per bundle + * @param page number of current page + */ + getBundlesPerItem(page: number = 1) { this.item$ = this.route.data.pipe( map((data) => data.dso), getFirstSucceededRemoteDataWithNotEmptyPayload(), map((item: Item) => this.linkService.resolveLink( item, - followLink('bundles', {}, followLink('bitstreams')) + followLink('bundles', {findListOptions: {currentPage : page, elementsPerPage: this.bunblesPerPage}}, followLink('bitstreams')) )) ) as Observable; - const bundles$: Observable> = this.item$.pipe( + const bundles$: Observable> = this.item$.pipe( filter((item: Item) => isNotEmpty(item.bundles)), mergeMap((item: Item) => item.bundles), getFirstSucceededRemoteDataWithNotEmptyPayload(), @@ -96,37 +174,40 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { take(1), map((list: PaginatedList) => list.page) ).subscribe((bundles: Bundle[]) => { - this.bundles$.next(bundles); + if (isEqual(bundles.length,0) || bundles.length < this.bunblesPerPage) { + this.allBundlesLoaded = true; + } + if (isEqual(page, 1)) { + this.bundles$.next(bundles); + this.bundlesToShow$.next(bundles); + } else { + this.bundlesToShow$.next(this.bundles$.getValue().concat(bundles)); + this.bundles$.next(this.bundles$.getValue().concat(bundles)); + } }), bundles$.pipe( take(1), mergeMap((list: PaginatedList) => list.page), map((bundle: Bundle) => ({ id: bundle.id, bitstreams: this.getBundleBitstreams(bundle) })) ).subscribe((entry: BundleBitstreamsMapEntry) => { - this.bundleBitstreamsMap.set(entry.id, entry.bitstreams); + let bitstreamMapValues: BitstreamMapValue = { + isCollapsed: true, + allBitstreamsLoaded: false, + bitstreams: null + }; + let bits = entry.bitstreams.pipe( + map((b: PaginatedList) => { + bitstreamMapValues.allBitstreamsLoaded = b?.page.length < this.bitstreamSize ; + let firstLoaded = [...b.page.slice(0, this.bitstreamSize)]; + return firstLoaded; + }) + ); + bitstreamMapValues.bitstreams = bits; + this.bundleBitstreamsMap.set(entry.id, bitstreamMapValues); }) ); } - /** - * Return the item's UUID - */ - getItemUUID(): Observable { - return this.item$.pipe( - map((item: Item) => item.id), - first((UUID: string) => isNotEmpty(UUID)) - ); - } - - /** - * Return all item's bundles - * - * @return an observable that emits all item's bundles - */ - getItemBundles(): Observable { - return this.bundles$.asObservable(); - } - /** * Return all bundle's bitstreams * @@ -142,6 +223,48 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { ); } + /** + * Changes the collapsible state of the area that contains the bitstream list + * @param bundleId Id of bundle responsible for the requested bitstreams + */ + collapseArea(bundleId: string) { + this.bundleBitstreamsMap.get(bundleId).isCollapsed = !this.bundleBitstreamsMap.get(bundleId).isCollapsed; + } + + /** + * Loads as much bundles as initial value of bundleSize to be displayed + */ + onBunbleLoad(){ + this.bunblesPageSize ++; + this.getBundlesPerItem(this.bunblesPageSize); + } + + /** + * Calculates the bitstreams that are going to be loaded on demand, + * based on the number configured on this.bitstreamSize. + * @param bundle parent of bitstreams that are requested to be shown + * @returns Subscription + */ + onBitstreamsLoad(bundle: Bundle) { + return this.getBundleBitstreams(bundle).pipe( + map((res: PaginatedList) => { + let nextBitstreams = res?.page.slice(this.bitstreamPageSize, this.bitstreamPageSize + this.bitstreamSize); + let bitstreamsToShow = this.bundleBitstreamsMap.get(bundle.id).bitstreams.pipe( + map((existingBits: Bitstream[])=> { + return [... existingBits, ...nextBitstreams]; + }) + ); + this.bitstreamPageSize = this.bitstreamPageSize + this.bitstreamSize; + let bitstreamMapValues: BitstreamMapValue = { + bitstreams: bitstreamsToShow , + isCollapsed: this.bundleBitstreamsMap.get(bundle.id).isCollapsed, + allBitstreamsLoaded: res?.page.length <= this.bitstreamPageSize + }; + this.bundleBitstreamsMap.set(bundle.id, bitstreamMapValues); + }) + ).subscribe(); + } + /** * Unsubscribe from all subscriptions */ @@ -151,3 +274,9 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { .forEach((subscription) => subscription.unsubscribe()); } } + +export interface BitstreamMapValue { + bitstreams: Observable; + isCollapsed: boolean; + allBitstreamsLoaded: boolean; +} diff --git a/src/app/shared/resource-policies/resource-policies.component.html b/src/app/shared/resource-policies/resource-policies.component.html index 0a1ccf7952..5d11ac81f2 100644 --- a/src/app/shared/resource-policies/resource-policies.component.html +++ b/src/app/shared/resource-policies/resource-policies.component.html @@ -4,9 +4,16 @@
- {{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} {{resourceUUID}} + + {{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} + + {{resourceName}} + + ({{resourceUUID}}) + +
- -
@@ -21,13 +21,13 @@ [resourceName]="bitstream.name">
- +
- +
diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts index f3313d4cef..a833b90b20 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts @@ -45,13 +45,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * The list of all bundles for the item * @type {Observable>} */ - private bundles$: BehaviorSubject = new BehaviorSubject([]); - - /** - * The list of bundles to be displayed in the template - * @type {BehaviorSubject} - */ - bundlesToShow$: BehaviorSubject = new BehaviorSubject([]); + bundles$: BehaviorSubject = new BehaviorSubject([]); /** * The target editing item @@ -69,13 +63,13 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * The size of the bundles to be loaded on demand * @type {number} */ - bunblesPerPage = 6; + bunblesPerPage = 1; /** * The number of current page * @type {number} */ - bunblesPageSize = 1; + bunblesPageSize = 6; /** * The flag to show or not the 'Load more' button @@ -179,9 +173,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { } if (isEqual(page, 1)) { this.bundles$.next(bundles); - this.bundlesToShow$.next(bundles); } else { - this.bundlesToShow$.next(this.bundles$.getValue().concat(bundles)); this.bundles$.next(this.bundles$.getValue().concat(bundles)); } }), diff --git a/src/app/shared/resource-policies/resource-policies.component.html b/src/app/shared/resource-policies/resource-policies.component.html index 5d11ac81f2..d1fd9266b1 100644 --- a/src/app/shared/resource-policies/resource-policies.component.html +++ b/src/app/shared/resource-policies/resource-policies.component.html @@ -6,7 +6,6 @@
{{ 'resource-policies.table.headers.title.for.' + resourceType | translate }} - {{resourceName}} ({{resourceUUID}}) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 76f4f538ea..cef52fde9c 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -848,6 +848,12 @@ "collection.edit.tabs.authorizations.title": "Collection Edit - Authorizations", + "collection.edit.item.authorizations.load-bundle-button": "Load more bundles", + + "collection.edit.item.authorizations.load-more-button": "Load more", + + "collection.edit.item.authorizations.show-bitstreams-button": "Show all Bitstreams' Policies for Bundle", + "collection.edit.tabs.metadata.head": "Edit Metadata", "collection.edit.tabs.metadata.title": "Collection Edit - Metadata", From 6cd41be3b80278fd086ffbf6ad7ad023a5aa3e67 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Mon, 9 May 2022 12:01:46 +0200 Subject: [PATCH 054/151] [CST-5677] Changed bunblesPerPage value --- .../item-authorizations/item-authorizations.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts index a833b90b20..f018d1c642 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts @@ -63,7 +63,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * The size of the bundles to be loaded on demand * @type {number} */ - bunblesPerPage = 1; + bunblesPerPage = 6; /** * The number of current page From 32d7fca27af3d92eda08c998414a2927fb4ad2c3 Mon Sep 17 00:00:00 2001 From: Alisa Ismailati Date: Mon, 9 May 2022 12:02:50 +0200 Subject: [PATCH 055/151] [CST-5677] Changed bunblesPageSize value --- .../item-authorizations/item-authorizations.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts index f018d1c642..3a7a7d95f2 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts @@ -69,7 +69,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * The number of current page * @type {number} */ - bunblesPageSize = 6; + bunblesPageSize = 1; /** * The flag to show or not the 'Load more' button From 0b664431afb6306a67034e70261b406081b805f0 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 10 May 2022 18:10:50 +0200 Subject: [PATCH 056/151] [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 057/151] [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 058/151] [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 064/151] [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 065/151] [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 066/151] [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 067/151] [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 068/151] [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 881af6449542ee174206d5b10ec27b87f0254219 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Tue, 17 May 2022 16:36:40 +0200 Subject: [PATCH 069/151] [CST-5674] POST replaced with PUT --- .../resource-policy.service.ts | 4 +-- .../form/resource-policy-form.component.html | 34 +++++++++---------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/app/core/resource-policy/resource-policy.service.ts b/src/app/core/resource-policy/resource-policy.service.ts index ca3951109a..8411647bea 100644 --- a/src/app/core/resource-policy/resource-policy.service.ts +++ b/src/app/core/resource-policy/resource-policy.service.ts @@ -29,7 +29,7 @@ import { getFirstCompletedRemoteData } from '../shared/operators'; import { CoreState } from '../core-state.model'; import { FindListOptions } from '../data/find-list-options.model'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; -import { PostRequest } from '../data/request.models'; +import { PutRequest } from '../data/request.models'; import { GenericConstructor } from '../shared/generic-constructor'; import { ResponseParsingService } from '../data/parsing.service'; import { StatusCodeOnlyResponseParsingService } from '../data/status-code-only-response-parsing.service'; @@ -260,7 +260,7 @@ export class ResourcePolicyService { return targetEndpoint$.pipe(switchMap((targetEndpoint) => { const resourceEndpoint = resourcePolicyHref + '/' + type; - const request = new PostRequest(requestId, resourceEndpoint, targetEndpoint, options); + const request = new PutRequest(requestId, resourceEndpoint, targetEndpoint, options); Object.assign(request, { getResponseParser(): GenericConstructor { return StatusCodeOnlyResponseParsingService; diff --git a/src/app/shared/resource-policies/form/resource-policy-form.component.html b/src/app/shared/resource-policies/form/resource-policy-form.component.html index f7aad55ce8..66c1fc400e 100644 --- a/src/app/shared/resource-policies/form/resource-policy-form.component.html +++ b/src/app/shared/resource-policies/form/resource-policy-form.component.html @@ -8,24 +8,22 @@
- - -
-
+ +

From 618ff0ce19607900bfd165aec9ae7ef01a9b19a2 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 17 May 2022 17:45:27 +0200 Subject: [PATCH 070/151] [CST-5303] added missing labels --- src/assets/i18n/en.json5 | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index bb2bcf8ebc..5bae41d0a1 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3644,6 +3644,26 @@ "submission.sections.describe.relationship-lookup.external-source.import-button-title.isProjectOfPublication": "Project", + "submission.sections.describe.relationship-lookup.external-source.import-button-title.none = "Import remote item", + + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Event": "Import remote event", + + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Product": "Import remote product", + + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Equipment": "Import remote equipment", + + "submission.sections.describe.relationship-lookup.external-source.import-button-title.OrgUnit = "Import remote organizational unit", + + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Funding": "Import remote fund", + + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Person = "Import remote person", + + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Patent": "Import remote patent", + + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Project = "Import remote project", + + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Publication = "Import remote publication", + "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.added.new-entity": "New Entity Added!", "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.title": "Project", From a732f1534de03fbdc89aad33af769dc50f40fb72 Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Wed, 18 May 2022 09:33:08 +0200 Subject: [PATCH 071/151] [CST-5303] fix wrong format --- src/assets/i18n/en.json5 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 5bae41d0a1..dffbbd4b7e 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3644,7 +3644,7 @@ "submission.sections.describe.relationship-lookup.external-source.import-button-title.isProjectOfPublication": "Project", - "submission.sections.describe.relationship-lookup.external-source.import-button-title.none = "Import remote item", + "submission.sections.describe.relationship-lookup.external-source.import-button-title.none": "Import remote item", "submission.sections.describe.relationship-lookup.external-source.import-button-title.Event": "Import remote event", @@ -3652,17 +3652,17 @@ "submission.sections.describe.relationship-lookup.external-source.import-button-title.Equipment": "Import remote equipment", - "submission.sections.describe.relationship-lookup.external-source.import-button-title.OrgUnit = "Import remote organizational unit", + "submission.sections.describe.relationship-lookup.external-source.import-button-title.OrgUnit": "Import remote organizational unit", "submission.sections.describe.relationship-lookup.external-source.import-button-title.Funding": "Import remote fund", - "submission.sections.describe.relationship-lookup.external-source.import-button-title.Person = "Import remote person", + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Person": "Import remote person", "submission.sections.describe.relationship-lookup.external-source.import-button-title.Patent": "Import remote patent", - "submission.sections.describe.relationship-lookup.external-source.import-button-title.Project = "Import remote project", + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Project": "Import remote project", - "submission.sections.describe.relationship-lookup.external-source.import-button-title.Publication = "Import remote publication", + "submission.sections.describe.relationship-lookup.external-source.import-button-title.Publication": "Import remote publication", "submission.sections.describe.relationship-lookup.external-source.import-modal.isProjectOfPublication.added.new-entity": "New Entity Added!", From d4dc176870d46506ad72670ba391f9f041e1c495 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 18 May 2022 13:04:04 +0200 Subject: [PATCH 072/151] [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 073/151] [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 089/151] [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 090/151] [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 091/151] [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 bb898022e34a7040332b858d806506ae7ee768c4 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 24 May 2022 13:38:53 +0200 Subject: [PATCH 092/151] 91400: Fix for missing comma in json5 label files by sync script --- scripts/sync-i18n-files.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/sync-i18n-files.ts b/scripts/sync-i18n-files.ts index ad8a712f21..a9b1896e7b 100755 --- a/scripts/sync-i18n-files.ts +++ b/scripts/sync-i18n-files.ts @@ -192,7 +192,10 @@ function createNewChunkComparingSourceAndTarget(correspondingTargetChunk, source const targetList = correspondingTargetChunk.split("\n"); const oldKeyValueInTargetComments = getSubStringWithRegex(correspondingTargetChunk, "\\s*\\/\\/\\s*\".*"); - const keyValueTarget = targetList[targetList.length - 1]; + let keyValueTarget = targetList[targetList.length - 1]; + if (!keyValueTarget.endsWith(",")) { + keyValueTarget = keyValueTarget + ","; + } if (oldKeyValueInTargetComments != null) { const oldKeyValueUncommented = getSubStringWithRegex(oldKeyValueInTargetComments[0], "\".*")[0]; From d1f8bb6d7b04035729875c5053de47ea0904fbf2 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 24 May 2022 16:05:21 +0200 Subject: [PATCH 093/151] [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 094/151] [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 d54f959613a60ed2fd411777338072d00d014440 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Wed, 25 May 2022 17:02:11 +0530 Subject: [PATCH 095/151] [DSC-516] Author show more button fixed --- .../truncatable-part.component.ts | 20 +++++++++---------- .../truncatable/truncatable.component.ts | 6 +++--- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts index 0bfcf25d39..2c3cb925cd 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts @@ -116,23 +116,21 @@ export class TruncatablePartComponent implements AfterContentChecked, OnInit, On * Function to get data to be observed */ toObserve() { - this.observedContent = this.document.querySelectorAll('.content'); + this.observedContent = this.document.querySelectorAll('.content:not(.notruncatable)'); this.observer = new (this._window.nativeWindow as any).ResizeObserver((entries) => { for (let entry of entries) { - if (!entry.target.classList.contains('notruncatable')) { - if (entry.target.scrollHeight > entry.contentRect.height) { - if (entry.target.children.length > 0) { - if (entry.target.children[0].offsetHeight > entry.contentRect.height) { - entry.target.classList.add('truncated'); - } else { - entry.target.classList.remove('truncated'); - } - } else { + if (entry.target.scrollHeight > entry.contentRect.height) { + if (entry.target.children.length > 0) { + if (entry.target.children[entry.target.children.length - 1].offsetHeight > entry.contentRect.height) { entry.target.classList.add('truncated'); + } else { + entry.target.classList.remove('truncated'); } } else { - entry.target.classList.remove('truncated'); + entry.target.classList.add('truncated'); } + } else { + entry.target.classList.remove('truncated'); } } }); diff --git a/src/app/shared/truncatable/truncatable.component.ts b/src/app/shared/truncatable/truncatable.component.ts index 61ec9c422a..3f3b91b018 100644 --- a/src/app/shared/truncatable/truncatable.component.ts +++ b/src/app/shared/truncatable/truncatable.component.ts @@ -1,4 +1,4 @@ -import { AfterViewChecked, Component, ElementRef, Input, OnInit } from '@angular/core'; +import { AfterContentChecked, Component, ElementRef, Input, OnInit } from '@angular/core'; import { TruncatableService } from './truncatable.service'; @Component({ @@ -11,7 +11,7 @@ import { TruncatableService } from './truncatable.service'; /** * Component that represents a section with one or more truncatable parts that all listen to this state */ -export class TruncatableComponent implements OnInit, AfterViewChecked { +export class TruncatableComponent implements OnInit, AfterContentChecked { /** * Is true when all truncatable parts in this truncatable should be expanded on loading */ @@ -59,7 +59,7 @@ export class TruncatableComponent implements OnInit, AfterViewChecked { } } - ngAfterViewChecked() { + ngAfterContentChecked() { const truncatedElements = this.el.nativeElement.querySelectorAll('.truncated'); if (truncatedElements?.length > 1) { for (let i = 0; i < (truncatedElements.length - 1); i++) { From 7f6c17df75e36145d50665d4d4b3831371380c66 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 25 May 2022 15:02:38 +0200 Subject: [PATCH 096/151] add support for multiple platforms to docker build --- .github/workflows/docker.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 00ec2fa8f7..89de307516 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -41,6 +41,10 @@ jobs: - name: Setup Docker Buildx uses: docker/setup-buildx-action@v1 + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + # https://github.com/docker/login-action - name: Login to DockerHub # Only login if not a PR, as PRs only trigger a Docker build and not a push @@ -70,6 +74,7 @@ jobs: with: context: . file: ./Dockerfile + platforms: linux/amd64,linux/arm64 # For pull requests, we run the Docker build (to ensure no PR changes break the build), # but we ONLY do an image push to DockerHub if it's NOT a PR push: ${{ github.event_name != 'pull_request' }} From d297eb708d247939f404d39ca660f4e30ce83679 Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Wed, 25 May 2022 18:20:13 +0200 Subject: [PATCH 097/151] [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 d44253ea8635dbb6ab7c14780209392de9b8d276 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 26 May 2022 09:53:07 +0200 Subject: [PATCH 098/151] 91400: Fix for line indentation on empty lines --- scripts/sync-i18n-files.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/sync-i18n-files.ts b/scripts/sync-i18n-files.ts index a9b1896e7b..96ba0d4010 100755 --- a/scripts/sync-i18n-files.ts +++ b/scripts/sync-i18n-files.ts @@ -1,4 +1,5 @@ -import { projectRoot} from '../webpack/helpers'; +import { projectRoot } from '../webpack/helpers'; + const commander = require('commander'); const fs = require('fs'); const JSON5 = require('json5'); @@ -119,7 +120,7 @@ function syncFileWithSource(pathToTargetFile, pathToOutputFile) { outputChunks.forEach(function (chunk) { progressBar.increment(); chunk.split("\n").forEach(function (line) { - file.write(" " + line + "\n"); + file.write((line === '' ? '' : ` ${line}`) + "\n"); }); }); file.write("\n}"); From 5810309ff85f6a318139041bfd4b3330b3328444 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 26 May 2022 14:43:41 +0530 Subject: [PATCH 099/151] [DSC-516] Performance improvement --- .../truncatable-part.component.html | 2 +- .../truncatable-part.component.ts | 99 ++++++------------- 2 files changed, 32 insertions(+), 69 deletions(-) diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html index cb9f529f99..83ca72e84a 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.html @@ -1,5 +1,5 @@
-
+
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 117/151] [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 68a36c4635a866b42fc68849a58dd573fe750a4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Jun 2022 21:50:02 +0000 Subject: [PATCH 118/151] Bump ejs from 3.1.6 to 3.1.8 Bumps [ejs](https://github.com/mde/ejs) from 3.1.6 to 3.1.8. - [Release notes](https://github.com/mde/ejs/releases) - [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md) - [Commits](https://github.com/mde/ejs/compare/v3.1.6...v3.1.8) --- updated-dependencies: - dependency-name: ejs dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 49 +++++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/yarn.lock b/yarn.lock index c1f7669a71..e7dc5201e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3155,11 +3155,6 @@ async-each-series@0.1.1: resolved "https://registry.yarnpkg.com/async-each-series/-/async-each-series-0.1.1.tgz#7617c1917401fd8ca4a28aadce3dbae98afeb432" integrity sha1-dhfBkXQB/Yykooqtzj266Yr+tDI= -async@0.9.x: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= - async@1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -3172,7 +3167,7 @@ async@^2.6.2: dependencies: lodash "^4.17.14" -async@^3.2.0: +async@^3.2.0, async@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== @@ -3460,6 +3455,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^2.3.1: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" @@ -4135,7 +4137,7 @@ compression@^1.7.4: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== configstore@^5.0.1: version "5.0.1" @@ -5139,11 +5141,11 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= ejs@^3.1.5: - version "3.1.6" - resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a" - integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw== + version "3.1.8" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b" + integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ== dependencies: - jake "^10.6.1" + jake "^10.8.5" electron-to-chromium@^1.4.71: version "1.4.75" @@ -6025,11 +6027,11 @@ file-saver@^2.0.5: integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== filelist@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b" - integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== dependencies: - minimatch "^3.0.4" + minimatch "^5.0.1" filesize@^6.1.0: version "6.4.0" @@ -7524,12 +7526,12 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jake@^10.6.1: - version "10.8.4" - resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.4.tgz#f6a8b7bf90c6306f768aa82bb7b98bf4ca15e84a" - integrity sha512-MtWeTkl1qGsWUtbl/Jsca/8xSoK3x0UmS82sNbjqxxG/de/M/3b1DntdjHgPMC50enlTNwXOCRqPXLLt5cCfZA== +jake@^10.8.5: + version "10.8.5" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" + integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw== dependencies: - async "0.9.x" + async "^3.2.3" chalk "^4.0.2" filelist "^1.0.1" minimatch "^3.0.4" @@ -8562,6 +8564,13 @@ minimatch@^3.0.2, minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" + integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" From 377214c9966995f22905224a6ebd844ff7d20b4f Mon Sep 17 00:00:00 2001 From: Rezart Vata Date: Mon, 6 Jun 2022 13:20:16 +0200 Subject: [PATCH 119/151] [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 120/151] [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 121/151] [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 122/151] [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 125/151] [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 126/151] [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 318d0ead61ca43851cff3c13eff8e3e7cb5a2033 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 6 Jun 2022 18:27:06 +0200 Subject: [PATCH 127/151] [DSC-516] remove unused test provider --- .../item-admin-search-result-grid-element.component.spec.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts index 23d8ca3243..a6ea7e4946 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts @@ -20,7 +20,6 @@ import { getMockThemeService } from '../../../../../shared/mocks/theme-service.m import { ThemeService } from '../../../../../shared/theme-support/theme.service'; import { AccessStatusDataService } from '../../../../../core/data/access-status-data.service'; import { AccessStatusObject } from '../../../../../shared/object-list/access-status-badge/access-status.model'; -import { NativeWindowRef, NativeWindowService } from '../../../../../core/services/window.service'; describe('ItemAdminSearchResultGridElementComponent', () => { let component: ItemAdminSearchResultGridElementComponent; @@ -61,7 +60,6 @@ describe('ItemAdminSearchResultGridElementComponent', () => { SharedModule ], providers: [ - { provide: NativeWindowService, useValue: new NativeWindowRef() }, { provide: TruncatableService, useValue: mockTruncatableService }, { provide: BitstreamDataService, useValue: mockBitstreamDataService }, { provide: ThemeService, useValue: mockThemeService }, From 1d33d1537baf024e20202ab4b28a400e78f55faf Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Tue, 7 Jun 2022 15:43:21 +0530 Subject: [PATCH 128/151] [DSC-516] Card faded affect fix --- ...-search-result-grid-element.component.html | 83 +++++++++---------- .../truncatable-part.component.scss | 4 +- 2 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html index 4344cf9a00..91fb85be40 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item/item-search-result-grid-element.component.html @@ -1,46 +1,45 @@
diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss index e778e1d15e..e045b197d2 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.scss @@ -6,6 +6,6 @@ box-shadow: none !important; } -.removeFaded { - position: inherit; +.removeFaded.content::after { + display: none; } From 73c60e3eef1ff54fd8be71079450b645bf118a87 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 7 Jun 2022 13:05:52 +0200 Subject: [PATCH 129/151] [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 130/151] [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 131/151] 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 132/151] [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 b6f83461ab1ba73a01e268a9b2224f3995bb83f1 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Tue, 7 Jun 2022 20:36:07 +0530 Subject: [PATCH 133/151] [DSC-516] show more button position at the bottom --- src/app/shared/truncatable/truncatable.component.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/shared/truncatable/truncatable.component.ts b/src/app/shared/truncatable/truncatable.component.ts index 61ec9c422a..71f99ecd4f 100644 --- a/src/app/shared/truncatable/truncatable.component.ts +++ b/src/app/shared/truncatable/truncatable.component.ts @@ -61,11 +61,13 @@ export class TruncatableComponent implements OnInit, AfterViewChecked { ngAfterViewChecked() { const truncatedElements = this.el.nativeElement.querySelectorAll('.truncated'); - if (truncatedElements?.length > 1) { - for (let i = 0; i < (truncatedElements.length - 1); i++) { - truncatedElements[i].classList.remove('truncated'); - truncatedElements[i].classList.add('notruncatable'); + if (truncatedElements?.length > 0) { + const truncateElements = this.el.nativeElement.querySelectorAll('.dont-break-out'); + for (let i = 0; i < (truncateElements.length - 1); i++) { + truncateElements[i].classList.remove('truncated'); + truncateElements[i].classList.add('notruncatable'); } + truncateElements[truncateElements.length - 1].classList.add('truncated'); } } From 12f073bdbe0642adb4a6d11241e4b01d4404c6ae Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 8 Jun 2022 11:20:32 +0200 Subject: [PATCH 134/151] [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 135/151] [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 136/151] [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 137/151] [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 138/151] 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 139/151] [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)) { From 4d73a3f2bfe09cdef1c8b20c17d73f84d2f6d210 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 9 Jun 2022 16:24:09 +0200 Subject: [PATCH 140/151] added search.filters.filter.author.label --- src/assets/i18n/de.json5 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 38e7398f46..42334db88c 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -4293,6 +4293,9 @@ // "search.filters.filter.author.placeholder": "Author name", "search.filters.filter.author.placeholder": "Autor:innenname", + // "search.filters.filter.author.label": "Search author name", + "search.filters.filter.author.label": "Autorensuche", + // "search.filters.filter.birthDate.head": "Birth Date", "search.filters.filter.birthDate.head": "Geburtsdatum", From a1570128eae7ec60fbc595f6a55d8fae5ff9a7ae Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 9 Jun 2022 16:31:31 +0200 Subject: [PATCH 141/151] added thumbnail.* translation keys --- src/assets/i18n/de.json5 | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 42334db88c..edbbf9b8a8 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5241,6 +5241,30 @@ "submission.workflow.tasks.pool.show-detail": "Details anzeigen", + // "thumbnail.default.alt": "Thumbnail Image", + "thumbnail.default.alt": "Vorschaubild", + + // "thumbnail.default.placeholder": "No Thumbnail Available", + "thumbnail.default.placeholder": "Vorschaubild nicht verfügbar", + + // "thumbnail.project.alt": "Project Logo", + "thumbnail.project.alt": "Logo des Projekt", + + // "thumbnail.project.placeholder": "Project Placeholder Image", + "thumbnail.project.placeholder": "Platzhalterbild des Projekts", + + // "thumbnail.orgunit.alt": "OrgUnit Logo", + "thumbnail.orgunit.alt": "Logo der Organisationseinheit", + + // "thumbnail.orgunit.placeholder": "OrgUnit Placeholder Image", + "thumbnail.orgunit.placeholder": "Platzhalterbild der Organisationseinheit", + + // "thumbnail.person.alt": "Profile Picture", + "thumbnail.person.alt": "Profilbild", + + // "thumbnail.person.placeholder": "No Profile Picture Available", + "thumbnail.person.placeholder": "Profilbild nicht verfügbar", + // "title": "DSpace", "title": "DSpace", From df1324af9035617e385866c28a0217e418993b9f Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 9 Jun 2022 18:05:30 +0200 Subject: [PATCH 142/151] [CST-5677] Spelling error fixed --- .../item-authorizations.component.html | 2 +- .../item-authorizations.component.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html index 25038171d8..5437525185 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.html @@ -28,6 +28,6 @@
- +
diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts index 3a7a7d95f2..eab14824ff 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts @@ -63,13 +63,13 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * The size of the bundles to be loaded on demand * @type {number} */ - bunblesPerPage = 6; + bundlesPerPage = 6; /** * The number of current page * @type {number} */ - bunblesPageSize = 1; + bundlesPageSize = 1; /** * The flag to show or not the 'Load more' button @@ -149,7 +149,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { getFirstSucceededRemoteDataWithNotEmptyPayload(), map((item: Item) => this.linkService.resolveLink( item, - followLink('bundles', {findListOptions: {currentPage : page, elementsPerPage: this.bunblesPerPage}}, followLink('bitstreams')) + followLink('bundles', {findListOptions: {currentPage : page, elementsPerPage: this.bundlesPerPage}}, followLink('bitstreams')) )) ) as Observable; @@ -168,7 +168,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { take(1), map((list: PaginatedList) => list.page) ).subscribe((bundles: Bundle[]) => { - if (isEqual(bundles.length,0) || bundles.length < this.bunblesPerPage) { + if (isEqual(bundles.length,0) || bundles.length < this.bundlesPerPage) { this.allBundlesLoaded = true; } if (isEqual(page, 1)) { @@ -226,9 +226,9 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { /** * Loads as much bundles as initial value of bundleSize to be displayed */ - onBunbleLoad(){ - this.bunblesPageSize ++; - this.getBundlesPerItem(this.bunblesPageSize); + onBundleLoad(){ + this.bundlesPageSize ++; + this.getBundlesPerItem(this.bundlesPageSize); } /** From 3c3a679ef7aca3a3156a019b427003aaf87d1577 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 9 Jun 2022 18:08:24 +0200 Subject: [PATCH 143/151] [CST-5677] Label fixed --- src/assets/i18n/en.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 1652ce4be8..e733654d94 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -866,7 +866,7 @@ "collection.edit.item.authorizations.load-more-button": "Load more", - "collection.edit.item.authorizations.show-bitstreams-button": "Show all Bitstreams' Policies for Bundle", + "collection.edit.item.authorizations.show-bitstreams-button": "Show bitstream policies for bundle", "collection.edit.tabs.metadata.head": "Edit Metadata", @@ -3662,7 +3662,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 fca8c223e7898635c05abf95f06d8e2d77a6df47 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 9 Jun 2022 18:49:44 +0200 Subject: [PATCH 144/151] [CST-5677] Empty subscription fixed --- .../item-authorizations.component.ts | 46 +++++++++---------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts index eab14824ff..8ed2f9a12e 100644 --- a/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts +++ b/src/app/item-page/edit-item-page/item-authorizations/item-authorizations.component.ts @@ -1,5 +1,5 @@ import { isEqual } from 'lodash'; -import { DSONameService } from './../../../core/breadcrumbs/dso-name.service'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @@ -8,7 +8,8 @@ import { catchError, filter, first, map, mergeMap, take } from 'rxjs/operators'; import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model'; import { - getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteDataWithNotEmptyPayload, + getFirstSucceededRemoteDataPayload, + getFirstSucceededRemoteDataWithNotEmptyPayload, } from '../../../core/shared/operators'; import { Item } from '../../../core/shared/item.model'; import { followLink } from '../../../shared/utils/follow-link-config.model'; @@ -95,6 +96,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * * @param {LinkService} linkService * @param {ActivatedRoute} route + * @param nameService */ constructor( private linkService: LinkService, @@ -187,14 +189,12 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { allBitstreamsLoaded: false, bitstreams: null }; - let bits = entry.bitstreams.pipe( + bitstreamMapValues.bitstreams = entry.bitstreams.pipe( map((b: PaginatedList) => { - bitstreamMapValues.allBitstreamsLoaded = b?.page.length < this.bitstreamSize ; - let firstLoaded = [...b.page.slice(0, this.bitstreamSize)]; - return firstLoaded; + bitstreamMapValues.allBitstreamsLoaded = b?.page.length < this.bitstreamSize; + return [...b.page.slice(0, this.bitstreamSize)]; }) ); - bitstreamMapValues.bitstreams = bits; this.bundleBitstreamsMap.set(entry.id, bitstreamMapValues); }) ); @@ -238,23 +238,21 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy { * @returns Subscription */ onBitstreamsLoad(bundle: Bundle) { - return this.getBundleBitstreams(bundle).pipe( - map((res: PaginatedList) => { - let nextBitstreams = res?.page.slice(this.bitstreamPageSize, this.bitstreamPageSize + this.bitstreamSize); - let bitstreamsToShow = this.bundleBitstreamsMap.get(bundle.id).bitstreams.pipe( - map((existingBits: Bitstream[])=> { - return [... existingBits, ...nextBitstreams]; - }) - ); - this.bitstreamPageSize = this.bitstreamPageSize + this.bitstreamSize; - let bitstreamMapValues: BitstreamMapValue = { - bitstreams: bitstreamsToShow , - isCollapsed: this.bundleBitstreamsMap.get(bundle.id).isCollapsed, - allBitstreamsLoaded: res?.page.length <= this.bitstreamPageSize - }; - this.bundleBitstreamsMap.set(bundle.id, bitstreamMapValues); - }) - ).subscribe(); + return this.getBundleBitstreams(bundle).subscribe((res: PaginatedList) => { + let nextBitstreams = res?.page.slice(this.bitstreamPageSize, this.bitstreamPageSize + this.bitstreamSize); + let bitstreamsToShow = this.bundleBitstreamsMap.get(bundle.id).bitstreams.pipe( + map((existingBits: Bitstream[])=> { + return [... existingBits, ...nextBitstreams]; + }) + ); + this.bitstreamPageSize = this.bitstreamPageSize + this.bitstreamSize; + let bitstreamMapValues: BitstreamMapValue = { + bitstreams: bitstreamsToShow , + isCollapsed: this.bundleBitstreamsMap.get(bundle.id).isCollapsed, + allBitstreamsLoaded: res?.page.length <= this.bitstreamPageSize + }; + this.bundleBitstreamsMap.set(bundle.id, bitstreamMapValues); + }); } /** From 4c19d3a02750767729b6cb93cf55ed9f2b6cd02f Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 9 Jun 2022 18:50:59 +0200 Subject: [PATCH 145/151] [CST-5677] `@types/node-sass` removed --- package.json | 3 +-- yarn.lock | 7 ------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/package.json b/package.json index 1ac3c95799..9bdf984cf1 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,6 @@ "@types/js-cookie": "2.2.6", "@types/lodash": "^4.14.165", "@types/node": "^14.14.9", - "@types/node-sass": "^4.11.2", "@typescript-eslint/eslint-plugin": "5.11.0", "@typescript-eslint/parser": "5.11.0", "axe-core": "^4.3.3", @@ -211,4 +210,4 @@ "webpack-cli": "^4.2.0", "webpack-dev-server": "^4.5.0" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index b11284c4f1..3fe250768d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2304,13 +2304,6 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== -"@types/node-sass@^4.11.2": - version "4.11.2" - resolved "https://registry.yarnpkg.com/@types/node-sass/-/node-sass-4.11.2.tgz#ecdaa44a1ba8847bf7dea2aadbfe33a91a263514" - integrity sha512-pOFlTw/OtZda4e+yMjq6/QYuvY0RDMQ+mxXdWj7rfSyf18V8hS4SfgurO+MasAkQsv6Wt6edOGlwh5QqJml9gw== - dependencies: - "@types/node" "*" - "@types/node@*", "@types/node@>=10.0.0": version "17.0.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644" From 0fbe9731cbd1f9b742bffebc486d0e5924997316 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 9 Jun 2022 19:19:02 +0200 Subject: [PATCH 146/151] added browse.back.all-results --- src/assets/i18n/de.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index edbbf9b8a8..3472f2ea15 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -895,6 +895,8 @@ "bitstream.edit.title": "Datei bearbeiten", + // "browse.back.all-results": "All browse results", + "browse.back.all-results": "Filter zurücksetzen", // "browse.comcol.by.author": "By Author", "browse.comcol.by.author": "Nach Autor:in", From eca0526cced4ca92d8cf2360e4e804310dd8f67c Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 9 Jun 2022 19:27:26 +0200 Subject: [PATCH 147/151] added missing sort order related translation keys --- src/assets/i18n/de.json5 | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 3472f2ea15..5b748c7e1c 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -4539,10 +4539,29 @@ // "sorting.dc.title.DESC": "Title Descending", "sorting.dc.title.DESC": "Titel absteigend", - // "sorting.score.DESC": "Relevance", - "sorting.score.DESC": "Relevanz", - + // "sorting.score.ASC": "Least Relevant", + "sorting.score.ASC": "Relevanz aufsteigend", + + // "sorting.score.DESC": "Most Relevant", + "sorting.score.DESC": "Relevanz absteigend", + + // "sorting.dc.date.issued.ASC": "Date Issued Ascending", + "sorting.dc.date.issued.ASC": "Erscheinungsdatum aufsteigend", + + // "sorting.dc.date.issued.DESC": "Date Issued Descending", + "sorting.dc.date.issued.DESC": "Erscheinungsdatum absteigend", + // "sorting.dc.date.accessioned.ASC": "Accessioned Date Ascending", + "sorting.dc.date.accessioned.ASC": "Freischaltungsdatum aufsteigend", + + // "sorting.dc.date.accessioned.DESC": "Accessioned Date Descending", + "sorting.dc.date.accessioned.DESC": "Freischaltungsdatum absteigend", + + // "sorting.lastModified.ASC": "Last modified Ascending", + "sorting.lastModified.ASC": "Änderungsdatum aufsteigend", + + // "sorting.lastModified.DESC": "Last modified Descending", + "sorting.lastModified.DESC": "Änderungsdatum absteigend", // "statistics.title": "Statistics", "statistics.title": "Statistiken", From a4699d0fafa1f473b82b2f1bc17706b17cd99c3c Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 9 Jun 2022 19:32:39 +0200 Subject: [PATCH 148/151] added missing key search.form.scope.all --- src/assets/i18n/de.json5 | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 5b748c7e1c..f4fbc8c320 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -4477,8 +4477,10 @@ // "search.form.search_mydspace": "Search MyDSpace", "search.form.search_mydspace": "Suche im persönlichen Arbeitsbereich", - - + // "search.form.scope.all": "All of DSpace", + "search.form.scope.all": "Alles durchsuchen", + + // "search.results.head": "Search Results", "search.results.head": "Suchergebnisse", @@ -4491,7 +4493,11 @@ // "search.results.empty": "Your search returned no results.", "search.results.empty": "Ihre Suche führte zu keinem Ergebnis.", - + // "search.results.view-result": "View", + "search.results.view-result": "Anzeigen", + + // "default.search.results.head": "Search Results", + "default.search.results.head": "Suchergebnisse", // "search.sidebar.close": "Back to results", "search.sidebar.close": "Zurück zu den Ergebnissen", From fa5dc5ccf0b285cd5a89ca676bf61747fd5c13e3 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 9 Jun 2022 19:35:46 +0200 Subject: [PATCH 149/151] added several missing dso-selector.set-scope.* keys --- src/assets/i18n/de.json5 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index f4fbc8c320..23ab3a29e5 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -1784,6 +1784,18 @@ // "dso-selector.placeholder": "Search for a {{ type }}", "dso-selector.placeholder": "Suche nach {{ type }}", + // "dso-selector.select.collection.head": "Select a collection", + "dso-selector.select.collection.head": "Sammlung auswählen", + + // "dso-selector.set-scope.community.head": "Select a search scope", + "dso-selector.set-scope.community.head": "Suchbereich auswählen", + + // "dso-selector.set-scope.community.button": "Search all of DSpace", + "dso-selector.set-scope.community.button": "Alles durchsuchen", + + // "dso-selector.set-scope.community.input-header": "Search for a community or collection", + "dso-selector.set-scope.community.input-header": "Suche Bereich oder Sammlung", + // "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", From 06d07dc1df76fc89fe16a82c222ae9722a3de508 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 13 Jun 2022 17:41:51 +0200 Subject: [PATCH 150/151] [DSC-516] Fix issue with sidebar search list element --- ...sidebar-search-list-element.component.html | 6 +-- .../truncatable-part.component.ts | 44 +++++++++++-------- .../truncatable/truncatable.component.ts | 22 +++++++--- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html index 3f93caa278..6a13b7e362 100644 --- a/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html +++ b/src/app/shared/object-list/sidebar-search-list-element/sidebar-search-list-element.component.html @@ -1,13 +1,13 @@ - +
- +
- +
diff --git a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts index e73823fa7e..790bd5985d 100644 --- a/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts +++ b/src/app/shared/truncatable/truncatable-part/truncatable-part.component.ts @@ -40,6 +40,12 @@ export class TruncatablePartComponent implements AfterViewChecked, OnInit, OnDes @Input() background = 'default'; + /** + * A boolean representing if to show or not the show/collapse toggle. + * This value must have the same value as the parent TruncatableComponent + */ + @Input() showToggle = true; + /** * The view on the truncatable part */ @@ -103,28 +109,30 @@ export class TruncatablePartComponent implements AfterViewChecked, OnInit, OnDes * check for the truncate element */ public truncateElement() { - const entry = this.content.nativeElement; - if (entry.scrollHeight > entry.offsetHeight) { - if (entry.children.length > 0) { - if (entry.children[entry.children.length - 1].offsetHeight > entry.offsetHeight) { - entry.classList.add('truncated'); - entry.classList.remove('removeFaded'); + if (this.showToggle) { + const entry = this.content.nativeElement; + if (entry.scrollHeight > entry.offsetHeight) { + if (entry.children.length > 0) { + if (entry.children[entry.children.length - 1].offsetHeight > entry.offsetHeight) { + entry.classList.add('truncated'); + entry.classList.remove('removeFaded'); + } else { + entry.classList.remove('truncated'); + entry.classList.add('removeFaded'); + } } else { - entry.classList.remove('truncated'); - entry.classList.add('removeFaded'); + if (entry.innerText.length > 0) { + entry.classList.add('truncated'); + entry.classList.remove('removeFaded'); + } else { + entry.classList.remove('truncated'); + entry.classList.add('removeFaded'); + } } } else { - if (entry.innerText.length > 0) { - entry.classList.add('truncated'); - entry.classList.remove('removeFaded'); - } else { - entry.classList.remove('truncated'); - entry.classList.add('removeFaded'); - } + entry.classList.remove('truncated'); + entry.classList.add('removeFaded'); } - } else { - entry.classList.remove('truncated'); - entry.classList.add('removeFaded'); } } diff --git a/src/app/shared/truncatable/truncatable.component.ts b/src/app/shared/truncatable/truncatable.component.ts index 71f99ecd4f..8fca300cd4 100644 --- a/src/app/shared/truncatable/truncatable.component.ts +++ b/src/app/shared/truncatable/truncatable.component.ts @@ -27,6 +27,12 @@ export class TruncatableComponent implements OnInit, AfterViewChecked { */ @Input() onHover = false; + /** + * A boolean representing if to show or not the show/collapse toggle + * This value must have the same value as the children TruncatablePartComponent + */ + @Input() showToggle = true; + public constructor(private service: TruncatableService, private el: ElementRef,) { } @@ -60,14 +66,16 @@ export class TruncatableComponent implements OnInit, AfterViewChecked { } ngAfterViewChecked() { - const truncatedElements = this.el.nativeElement.querySelectorAll('.truncated'); - if (truncatedElements?.length > 0) { - const truncateElements = this.el.nativeElement.querySelectorAll('.dont-break-out'); - for (let i = 0; i < (truncateElements.length - 1); i++) { - truncateElements[i].classList.remove('truncated'); - truncateElements[i].classList.add('notruncatable'); + if (this.showToggle) { + const truncatedElements = this.el.nativeElement.querySelectorAll('.truncated'); + if (truncatedElements?.length > 0) { + const truncateElements = this.el.nativeElement.querySelectorAll('.dont-break-out'); + for (let i = 0; i < (truncateElements.length - 1); i++) { + truncateElements[i].classList.remove('truncated'); + truncateElements[i].classList.add('notruncatable'); + } + truncateElements[truncateElements.length - 1].classList.add('truncated'); } - truncateElements[truncateElements.length - 1].classList.add('truncated'); } } From da45ff2d83b1c227fec820c6f8416daf88ecd360 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Wed, 15 Jun 2022 10:26:13 -0500 Subject: [PATCH 151/151] Ensure CI docker backend always runs DB migrations, even when out of order. --- docker/db.entities.yml | 4 ++-- docker/docker-compose-ci.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/db.entities.yml b/docker/db.entities.yml index d1dfdf4a26..6473bf2e38 100644 --- a/docker/db.entities.yml +++ b/docker/db.entities.yml @@ -25,7 +25,7 @@ services: ### OVERRIDE default 'entrypoint' in 'docker-compose-rest.yml' #### # Ensure that the database is ready BEFORE starting tomcat # 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep - # 2. Then, run database migration to init database tables + # 2. Then, run database migration to init database tables (including any out-of-order ignored migrations, if any) # 3. (Custom for Entities) enable Entity-specific collection submission mappings in item-submission.xml # This 'sed' command inserts the sample configurations specific to the Entities data set, see: # https://github.com/DSpace/DSpace/blob/main/dspace/config/item-submission.xml#L36-L49 @@ -35,7 +35,7 @@ services: - '-c' - | while (! /dev/null 2>&1; do sleep 1; done; - /dspace/bin/dspace database migrate + /dspace/bin/dspace database migrate ignored sed -i '/name-map collection-handle="default".*/a \\n \ \ \ diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index 3bd8f52630..dbe9500499 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -46,14 +46,14 @@ services: - solr_configs:/dspace/solr # Ensure that the database is ready BEFORE starting tomcat # 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep - # 2. Then, run database migration to init database tables + # 2. Then, run database migration to init database tables (including any out-of-order ignored migrations, if any) # 3. Finally, start Tomcat entrypoint: - /bin/bash - '-c' - | while (! /dev/null 2>&1; do sleep 1; done; - /dspace/bin/dspace database migrate + /dspace/bin/dspace database migrate ignored catalina.sh run # DSpace database container # NOTE: This is customized to use our loadsql image, so that we are using a database with existing test data