diff --git a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.html b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.html new file mode 100644 index 0000000000..3e360cc55e --- /dev/null +++ b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.html @@ -0,0 +1,27 @@ +
+
+
+ + + {{'system-wide-alert-banner.countdown.prefix' | translate }} + + + {{'system-wide-alert-banner.countdown.days' | translate: { + days: countDownDays|async + } }} + + + {{'system-wide-alert-banner.countdown.hours' | translate: { + hours: countDownHours| async + } }} + + + {{'system-wide-alert-banner.countdown.minutes' | translate: { + minutes: countDownMinutes|async + } }} + + + {{(systemWideAlert$ |async)?.message}} +
+
+
diff --git a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.scss b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.spec.ts b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.spec.ts new file mode 100644 index 0000000000..d27e5379e9 --- /dev/null +++ b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.spec.ts @@ -0,0 +1,111 @@ +import { ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { SystemWideAlertBannerComponent } from './system-wide-alert-banner.component'; +import { SystemWideAlertDataService } from '../../core/data/system-wide-alert-data.service'; +import { SystemWideAlert } from '../system-wide-alert.model'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { utcToZonedTime } from 'date-fns-tz'; +import { createPaginatedList } from '../../shared/testing/utils.test'; +import { TestScheduler } from 'rxjs/testing'; +import { getTestScheduler } from 'jasmine-marbles'; +import { By } from '@angular/platform-browser'; +import { TranslateModule } from '@ngx-translate/core'; + + +describe('SystemWideAlertBannerComponent', () => { + let comp: SystemWideAlertBannerComponent; + let fixture: ComponentFixture; + let systemWideAlertDataService: SystemWideAlertDataService; + + let systemWideAlert: SystemWideAlert; + let scheduler: TestScheduler; + + beforeEach(waitForAsync(() => { + scheduler = getTestScheduler(); + + const countDownDate = new Date(); + countDownDate.setDate(countDownDate.getDate() + 1); + countDownDate.setHours(countDownDate.getHours() + 1); + countDownDate.setMinutes(countDownDate.getMinutes() + 1); + + systemWideAlert = Object.assign(new SystemWideAlert(), { + alertId: 1, + message: 'Test alert message', + active: true, + countdownTo: utcToZonedTime(countDownDate, 'UTC').toISOString() + }); + + systemWideAlertDataService = jasmine.createSpyObj('systemWideAlertDataService', { + findAll: createSuccessfulRemoteDataObject$(createPaginatedList([systemWideAlert])), + }); + + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [SystemWideAlertBannerComponent], + providers: [ + {provide: SystemWideAlertDataService, useValue: systemWideAlertDataService}, + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SystemWideAlertBannerComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + }); + + describe('init', () => { + it('should init the comp', () => { + expect(comp).toBeTruthy(); + }); + it('should set the time countdown parts in their respective behaviour subjects', fakeAsync(() => { + spyOn(comp.countDownDays, 'next'); + spyOn(comp.countDownHours, 'next'); + spyOn(comp.countDownMinutes, 'next'); + comp.ngOnInit(); + tick(2000); + expect(comp.countDownDays.next).toHaveBeenCalled(); + expect(comp.countDownHours.next).toHaveBeenCalled(); + expect(comp.countDownMinutes.next).toHaveBeenCalled(); + discardPeriodicTasks(); + + })); + }); + + describe('banner', () => { + it('should display the alert message and the timer', () => { + comp.countDownDays.next(1); + comp.countDownHours.next(1); + comp.countDownMinutes.next(1); + fixture.detectChanges(); + + const banner = fixture.debugElement.queryAll(By.css('span')); + expect(banner.length).toEqual(6); + + expect(banner[0].nativeElement.innerHTML).toContain('system-wide-alert-banner.countdown.prefix'); + expect(banner[0].nativeElement.innerHTML).toContain('system-wide-alert-banner.countdown.days'); + expect(banner[0].nativeElement.innerHTML).toContain('system-wide-alert-banner.countdown.hours'); + expect(banner[0].nativeElement.innerHTML).toContain('system-wide-alert-banner.countdown.minutes'); + + expect(banner[5].nativeElement.innerHTML).toContain(systemWideAlert.message); + }); + + it('should display the alert message but no timer when no timer is present', () => { + comp.countDownDays.next(0); + comp.countDownHours.next(0); + comp.countDownMinutes.next(0); + fixture.detectChanges(); + + const banner = fixture.debugElement.queryAll(By.css('span')); + expect(banner.length).toEqual(2); + expect(banner[1].nativeElement.innerHTML).toContain(systemWideAlert.message); + }); + + it('should not display an alert when none is present', () => { + comp.systemWideAlert$.next(null); + fixture.detectChanges(); + + const banner = fixture.debugElement.queryAll(By.css('span')); + expect(banner.length).toEqual(0); + }); + }); +}); diff --git a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts new file mode 100644 index 0000000000..b405957c54 --- /dev/null +++ b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts @@ -0,0 +1,113 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { SystemWideAlertDataService } from '../../core/data/system-wide-alert-data.service'; +import { getAllSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { filter, map, switchMap } from 'rxjs/operators'; +import { PaginatedList } from '../../core/data/paginated-list.model'; +import { SystemWideAlert } from '../system-wide-alert.model'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { BehaviorSubject, EMPTY, interval, Subscription } from 'rxjs'; +import { zonedTimeToUtc } from 'date-fns-tz'; + +/** + * Component responsible for rendering a banner and the countdown for an active system-wide alert + */ +@Component({ + selector: 'ds-system-wide-alert-banner', + styleUrls: ['./system-wide-alert-banner.component.scss'], + templateUrl: './system-wide-alert-banner.component.html' +}) +export class SystemWideAlertBannerComponent implements OnInit, OnDestroy { + + /** + * BehaviorSubject that keeps track of the currently configured system-wide alert + */ + systemWideAlert$ = new BehaviorSubject(undefined); + + /** + * BehaviorSubject that keeps track of the amount of minutes left to count down to + */ + countDownMinutes = new BehaviorSubject(0); + + /** + * BehaviorSubject that keeps track of the amount of hours left to count down to + */ + countDownHours = new BehaviorSubject(0); + + /** + * BehaviorSubject that keeps track of the amount of days left to count down to + */ + countDownDays = new BehaviorSubject(0); + + /** + * List of subscriptions + */ + subscriptions: Subscription[] = []; + + constructor( + protected systemWideAlertDataService: SystemWideAlertDataService + ) { + } + + ngOnInit() { + this.subscriptions.push(this.systemWideAlertDataService.findAll().pipe( + getAllSucceededRemoteDataPayload(), + map((payload: PaginatedList) => payload.page), + filter((page) => isNotEmpty(page)), + map((page) => page[0]) + ).subscribe((alert: SystemWideAlert) => { + this.systemWideAlert$.next(alert); + })); + + this.subscriptions.push(this.systemWideAlert$.pipe( + switchMap((alert: SystemWideAlert) => { + if (hasValue(alert) && hasValue(alert.countdownTo)) { + const date = zonedTimeToUtc(alert.countdownTo, 'UTC'); + const timeDifference = date.getTime() - new Date().getTime(); + if (timeDifference > 0) { + return interval(1000); + } + } + // Reset the countDown times to 0 and return EMPTY to prevent unnecessary countdown calculations + this.countDownDays.next(0); + this.countDownHours.next(0); + this.countDownMinutes.next(0); + return EMPTY; + }) + ).subscribe(() => { + this.setTimeDifference(this.systemWideAlert$.getValue().countdownTo); + })); + } + + /** + * Helper method to calculate the time difference between the countdown date from the system-wide alert and "now" + * @param countdownTo - The date to count down to + */ + private setTimeDifference(countdownTo: string) { + const date = zonedTimeToUtc(countdownTo, 'UTC'); + + const timeDifference = date.getTime() - new Date().getTime(); + this.allocateTimeUnits(timeDifference); + } + + /** + * Helper method to push how many days, hours and minutes are left in the countdown to their respective behaviour subject + * @param timeDifference - The time difference to calculate and push the time units for + */ + private allocateTimeUnits(timeDifference) { + const minutes = Math.floor((timeDifference) / (1000 * 60) % 60); + const hours = Math.floor((timeDifference) / (1000 * 60 * 60) % 24); + const days = Math.floor((timeDifference) / (1000 * 60 * 60 * 24)); + + this.countDownMinutes.next(minutes); + this.countDownHours.next(hours); + this.countDownDays.next(days); + } + + ngOnDestroy(): void { + this.subscriptions.forEach((sub: Subscription) => { + if (hasValue(sub)) { + sub.unsubscribe(); + } + }); + } +} diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html new file mode 100644 index 0000000000..2821a14bf8 --- /dev/null +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html @@ -0,0 +1,74 @@ +
+ +
+
+
+
+ +
+
+
+
+ + +
+ + {{ 'system-wide-alert.form.error.message' | translate }} + +
+
+
+
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ {{'system-wide-alert.form.label.countdownTo.hint' | translate}} +
+ +
+ + +
+ +
diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.scss b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.scss new file mode 100644 index 0000000000..ce3e065caf --- /dev/null +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.scss @@ -0,0 +1,3 @@ +.timepicker-margin { + margin-top: -38px; +} diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts new file mode 100644 index 0000000000..435aef3a7d --- /dev/null +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts @@ -0,0 +1,219 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { SystemWideAlertDataService } from '../../core/data/system-wide-alert-data.service'; +import { SystemWideAlert } from '../system-wide-alert.model'; +import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { createPaginatedList } from '../../shared/testing/utils.test'; +import { TranslateModule } from '@ngx-translate/core'; +import { SystemWideAlertFormComponent } from './system-wide-alert-form.component'; +import { RequestService } from '../../core/data/request.service'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { RouterStub } from '../../shared/testing/router.stub'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { Router } from '@angular/router'; +import { FormsModule } from '@angular/forms'; +import { SharedModule } from '../../shared/shared.module'; +import { UiSwitchModule } from 'ngx-ui-switch'; + +describe('SystemWideAlertFormComponent', () => { + let comp: SystemWideAlertFormComponent; + let fixture: ComponentFixture; + let systemWideAlertDataService: SystemWideAlertDataService; + + let systemWideAlert: SystemWideAlert; + let requestService: RequestService; + let notificationsService; + let router; + + + beforeEach(waitForAsync(() => { + + const countDownDate = new Date(); + countDownDate.setDate(countDownDate.getDate() + 1); + countDownDate.setHours(countDownDate.getHours() + 1); + countDownDate.setMinutes(countDownDate.getMinutes() + 1); + + systemWideAlert = Object.assign(new SystemWideAlert(), { + alertId: 1, + message: 'Test alert message', + active: true, + countdownTo: utcToZonedTime(countDownDate, 'UTC').toISOString() + }); + + systemWideAlertDataService = jasmine.createSpyObj('systemWideAlertDataService', { + findAll: createSuccessfulRemoteDataObject$(createPaginatedList([systemWideAlert])), + put: createSuccessfulRemoteDataObject$(systemWideAlert), + create: createSuccessfulRemoteDataObject$(systemWideAlert) + }); + + requestService = jasmine.createSpyObj('requestService', ['setStaleByHrefSubstring']); + + notificationsService = new NotificationsServiceStub(); + router = new RouterStub(); + + TestBed.configureTestingModule({ + imports: [FormsModule, SharedModule, UiSwitchModule, TranslateModule.forRoot()], + declarations: [SystemWideAlertFormComponent], + providers: [ + {provide: SystemWideAlertDataService, useValue: systemWideAlertDataService}, + {provide: NotificationsService, useValue: notificationsService}, + {provide: Router, useValue: router}, + {provide: RequestService, useValue: requestService}, + ] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SystemWideAlertFormComponent); + comp = fixture.componentInstance; + + spyOn(comp, 'createForm').and.callThrough(); + spyOn(comp, 'initFormValues').and.callThrough(); + + fixture.detectChanges(); + }); + + describe('init', () => { + it('should init the comp', () => { + expect(comp).toBeTruthy(); + }); + it('should create the form and init the values based on an existing alert', () => { + expect(comp.createForm).toHaveBeenCalled(); + expect(comp.initFormValues).toHaveBeenCalledWith(systemWideAlert); + }); + }); + + describe('createForm', () => { + it('should create the form', () => { + const now = new Date(); + + comp.createForm(); + expect(comp.formMessage.value).toEqual(''); + expect(comp.formActive.value).toEqual(false); + expect(comp.time).toEqual({hour: now.getHours(), minute: now.getMinutes()}); + expect(comp.date).toEqual({year: now.getFullYear(), month: now.getMonth() + 1, day: now.getDate()}); + }); + }); + + describe('initFormValues', () => { + it('should fill in the form based on the provided system-wide alert', () => { + comp.initFormValues(systemWideAlert); + + const countDownTo = zonedTimeToUtc(systemWideAlert.countdownTo, 'UTC'); + + expect(comp.formMessage.value).toEqual(systemWideAlert.message); + expect(comp.formActive.value).toEqual(true); + expect(comp.time).toEqual({hour: countDownTo.getHours(), minute: countDownTo.getMinutes()}); + expect(comp.date).toEqual({ + year: countDownTo.getFullYear(), + month: countDownTo.getMonth() + 1, + day: countDownTo.getDate() + }); + }); + }); + describe('save', () => { + it('should update the exising alert with the form values and show a success notification on success', () => { + spyOn(comp, 'back'); + comp.currentAlert = systemWideAlert; + + comp.formMessage.patchValue('New message'); + comp.formActive.patchValue(true); + comp.time = {hour: 4, minute: 26}; + comp.date = {year: 2023, month: 1, day: 25}; + + const expectedAlert = new SystemWideAlert(); + expectedAlert.alertId = systemWideAlert.alertId; + expectedAlert.message = 'New message'; + expectedAlert.active = true; + const countDownTo = new Date(2023, 0, 25, 4, 26); + expectedAlert.countdownTo = utcToZonedTime(countDownTo, 'UTC').toUTCString(); + + comp.save(); + + expect(systemWideAlertDataService.put).toHaveBeenCalledWith(expectedAlert); + expect(notificationsService.success).toHaveBeenCalled(); + expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('systemwidealerts'); + expect(comp.back).toHaveBeenCalled(); + }); + it('should update the exising alert with the form values and show a error notification on error', () => { + spyOn(comp, 'back'); + (systemWideAlertDataService.put as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$()); + comp.currentAlert = systemWideAlert; + + comp.formMessage.patchValue('New message'); + comp.formActive.patchValue(true); + comp.time = {hour: 4, minute: 26}; + comp.date = {year: 2023, month: 1, day: 25}; + + const expectedAlert = new SystemWideAlert(); + expectedAlert.alertId = systemWideAlert.alertId; + expectedAlert.message = 'New message'; + expectedAlert.active = true; + const countDownTo = new Date(2023, 0, 25, 4, 26); + expectedAlert.countdownTo = utcToZonedTime(countDownTo, 'UTC').toUTCString(); + + comp.save(); + + expect(systemWideAlertDataService.put).toHaveBeenCalledWith(expectedAlert); + expect(notificationsService.error).toHaveBeenCalled(); + expect(requestService.setStaleByHrefSubstring).not.toHaveBeenCalledWith('systemwidealerts'); + expect(comp.back).not.toHaveBeenCalled(); + }); + it('should create a new alert with the form values and show a success notification on success', () => { + spyOn(comp, 'back'); + comp.currentAlert = undefined; + + comp.formMessage.patchValue('New message'); + comp.formActive.patchValue(true); + comp.time = {hour: 4, minute: 26}; + comp.date = {year: 2023, month: 1, day: 25}; + + const expectedAlert = new SystemWideAlert(); + expectedAlert.message = 'New message'; + expectedAlert.active = true; + const countDownTo = new Date(2023, 0, 25, 4, 26); + expectedAlert.countdownTo = utcToZonedTime(countDownTo, 'UTC').toUTCString(); + + comp.save(); + + expect(systemWideAlertDataService.create).toHaveBeenCalledWith(expectedAlert); + expect(notificationsService.success).toHaveBeenCalled(); + expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('systemwidealerts'); + expect(comp.back).toHaveBeenCalled(); + + }); + it('should create a new alert with the form values and show a error notification on error', () => { + spyOn(comp, 'back'); + (systemWideAlertDataService.create as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$()); + + comp.currentAlert = undefined; + + comp.formMessage.patchValue('New message'); + comp.formActive.patchValue(true); + comp.time = {hour: 4, minute: 26}; + comp.date = {year: 2023, month: 1, day: 25}; + + const expectedAlert = new SystemWideAlert(); + expectedAlert.message = 'New message'; + expectedAlert.active = true; + const countDownTo = new Date(2023, 0, 25, 4, 26); + expectedAlert.countdownTo = utcToZonedTime(countDownTo, 'UTC').toUTCString(); + + comp.save(); + + expect(systemWideAlertDataService.create).toHaveBeenCalledWith(expectedAlert); + expect(notificationsService.error).toHaveBeenCalled(); + expect(requestService.setStaleByHrefSubstring).not.toHaveBeenCalledWith('systemwidealerts'); + expect(comp.back).not.toHaveBeenCalled(); + + }); + }); + describe('back', () => { + it('should navigate back to the home page', () => { + comp.back(); + expect(router.navigate).toHaveBeenCalledWith(['/home']); + }); + }); + + +}); diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts new file mode 100644 index 0000000000..59aa9166ce --- /dev/null +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts @@ -0,0 +1,163 @@ +import { Component, OnInit } from '@angular/core'; +import { SystemWideAlertDataService } from '../../core/data/system-wide-alert-data.service'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { filter, map } from 'rxjs/operators'; +import { PaginatedList } from '../../core/data/paginated-list.model'; +import { SystemWideAlert } from '../system-wide-alert.model'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { Observable } from 'rxjs'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; +import { utcToZonedTime, zonedTimeToUtc } from 'date-fns-tz'; +import { RemoteData } from '../../core/data/remote-data'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { Router } from '@angular/router'; +import { RequestService } from '../../core/data/request.service'; +import { TranslateService } from '@ngx-translate/core'; + + +/** + * Component responsible for rendering the form to update a system-wide alert + */ +@Component({ + selector: 'ds-system-wide-alert-form', + styleUrls: ['./system-wide-alert-form.component.scss'], + templateUrl: './system-wide-alert-form.component.html' +}) +export class SystemWideAlertFormComponent implements OnInit { + + /** + * Observable to track an existing system-wide alert + */ + systemWideAlert$: Observable; + + /** + * The currently configured system-wide alert + */ + currentAlert: SystemWideAlert; + + /** + * The form group representing the system-wide alert + */ + alertForm: FormGroup; + + /** + * Date object to store the countdown date part + */ + date: NgbDateStruct; + + /** + * Object to store the countdown time part + */ + time; + + + constructor( + protected systemWideAlertDataService: SystemWideAlertDataService, + protected notificationsService: NotificationsService, + protected router: Router, + protected requestService: RequestService, + protected translateService: TranslateService + ) { + } + + ngOnInit() { + this.systemWideAlert$ = this.systemWideAlertDataService.findAll().pipe( + getFirstSucceededRemoteDataPayload(), + map((payload: PaginatedList) => payload.page), + filter((page) => isNotEmpty(page)), + map((page) => page[0]) + ); + this.createForm(); + + this.systemWideAlert$.subscribe((alert) => { + this.currentAlert = alert; + this.initFormValues(alert); + }); + } + + /** + * Creates the form with empty values + */ + createForm() { + this.alertForm = new FormBuilder().group({ + formMessage: new FormControl('', { + validators: [Validators.required], + }), + formActive: new FormControl(false), + } + ); + this.setDateTime(new Date()); + } + + /** + * Sets the form values based on the values retrieve from the provided system-wide alert + * @param alert - System-wide alert to use to init the form + */ + initFormValues(alert: SystemWideAlert) { + this.formMessage.patchValue(alert.message); + this.formActive.patchValue(alert.active); + const countDownTo = zonedTimeToUtc(alert.countdownTo, 'UTC'); + if (countDownTo.getTime() - new Date().getTime() > 0) { + this.setDateTime(countDownTo); + } + + } + + private setDateTime(dateToSet) { + this.time = {hour: dateToSet.getHours(), minute: dateToSet.getMinutes()}; + this.date = {year: dateToSet.getFullYear(), month: dateToSet.getMonth() + 1, day: dateToSet.getDate()}; + + } + + get formMessage() { + return this.alertForm.get('formMessage'); + } + + get formActive() { + return this.alertForm.get('formActive'); + } + + /** + * Save the system-wide alert present in the form + * When no alert is present yet on the server, a new one will be created + * When one already exists, the existing one will be updated + */ + save() { + const alert = new SystemWideAlert(); + alert.message = this.formMessage.value; + alert.active = this.formActive.value; + const countDownTo = new Date(this.date.year, this.date.month - 1, this.date.day, this.time.hour, this.time.minute); + alert.countdownTo = utcToZonedTime(countDownTo, 'UTC').toUTCString(); + + if (hasValue(this.currentAlert)) { + const updatedAlert = Object.assign(new SystemWideAlert(), this.currentAlert, alert); + this.handleResponse(this.systemWideAlertDataService.put(updatedAlert), 'system-wide-alert.form.update'); + } else { + this.handleResponse(this.systemWideAlertDataService.create(alert), 'system-wide-alert.form.create'); + } + } + + private handleResponse(response$: Observable>, messagePrefix) { + response$.pipe( + getFirstCompletedRemoteData() + ).subscribe((response: RemoteData) => { + if (response.hasSucceeded) { + this.notificationsService.success(this.translateService.get(`${messagePrefix}.success`)); + this.requestService.setStaleByHrefSubstring('systemwidealerts'); + this.back(); + } else { + this.notificationsService.error(this.translateService.get(`${messagePrefix}.error`, response.errorMessage)); + } + }); + } + + /** + * Navigate back to the homepage + */ + back() { + this.router.navigate(['/home']); + } + + +} diff --git a/src/app/system-wide-alert/system-wide-alert-routing.module.ts b/src/app/system-wide-alert/system-wide-alert-routing.module.ts new file mode 100644 index 0000000000..beb1b32187 --- /dev/null +++ b/src/app/system-wide-alert/system-wide-alert-routing.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { + SiteAdministratorGuard +} from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { SystemWideAlertFormComponent } from './alert-form/system-wide-alert-form.component'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: '', + canActivate: [SiteAdministratorGuard], + component: SystemWideAlertFormComponent, + }, + + ]) + ] +}) +export class SystemWideAlertRoutingModule { + +} diff --git a/src/app/system-wide-alert/system-wide-alert.model.ts b/src/app/system-wide-alert/system-wide-alert.model.ts new file mode 100644 index 0000000000..158deb2603 --- /dev/null +++ b/src/app/system-wide-alert/system-wide-alert.model.ts @@ -0,0 +1,55 @@ +import { autoserialize, deserialize } from 'cerialize'; +import { typedObject } from '../core/cache/builders/build-decorators'; +import { CacheableObject } from '../core/cache/cacheable-object.model'; +import { HALLink } from '../core/shared/hal-link.model'; +import { ResourceType } from '../core/shared/resource-type'; +import { excludeFromEquals } from '../core/utilities/equals.decorators'; +import { SYSTEMWIDEALERT } from './system-wide-alert.resource-type'; + +/** + * Object representing a system-wide alert + */ +@typedObject +export class SystemWideAlert implements CacheableObject { + static type = SYSTEMWIDEALERT; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The identifier for this system-wide alert + */ + @autoserialize + alertId: string; + + /** + * The message for this system-wide alert + */ + @autoserialize + message: string; + + /** + * A string representation of the date to which this system-wide alert will count down when active + */ + @autoserialize + countdownTo: string; + + /** + * Whether the system-wide alert is active + */ + @autoserialize + active: boolean; + + + /** + * The {@link HALLink}s for this system-wide alert + */ + @deserialize + _links: { + self: HALLink, + }; +} diff --git a/src/app/system-wide-alert/system-wide-alert.module.ts b/src/app/system-wide-alert/system-wide-alert.module.ts new file mode 100644 index 0000000000..ce2a87f982 --- /dev/null +++ b/src/app/system-wide-alert/system-wide-alert.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { SystemWideAlertBannerComponent } from './alert-banner/system-wide-alert-banner.component'; +import { SystemWideAlertFormComponent } from './alert-form/system-wide-alert-form.component'; +import { SharedModule } from '../shared/shared.module'; +import { SystemWideAlertDataService } from '../core/data/system-wide-alert-data.service'; +import { SystemWideAlertRoutingModule } from './system-wide-alert-routing.module'; +import { UiSwitchModule } from 'ngx-ui-switch'; + +@NgModule({ + imports: [ + FormsModule, + SharedModule, + UiSwitchModule, + SystemWideAlertRoutingModule, + ], + exports: [ + SystemWideAlertBannerComponent + ], + declarations: [ + SystemWideAlertBannerComponent, + SystemWideAlertFormComponent + ], + providers: [ + SystemWideAlertDataService + ] +}) +export class SystemWideAlertModule { + +} diff --git a/src/app/system-wide-alert/system-wide-alert.resource-type.ts b/src/app/system-wide-alert/system-wide-alert.resource-type.ts new file mode 100644 index 0000000000..f67f00719b --- /dev/null +++ b/src/app/system-wide-alert/system-wide-alert.resource-type.ts @@ -0,0 +1,10 @@ +/** + * The resource type for SystemWideAlert + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ + +import { ResourceType } from '../core/shared/resource-type'; + +export const SYSTEMWIDEALERT = new ResourceType('systemwidealert'); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 597f226cc7..dd52b7dd9d 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4811,4 +4811,47 @@ "person.orcid.registry.auth": "ORCID Authorizations", "home.recent-submissions.head": "Recent Submissions", + + + "system-wide-alert-banner.countdown.prefix": "In", + + "system-wide-alert-banner.countdown.days": "{{days}} day(s),", + + "system-wide-alert-banner.countdown.hours": "{{hours}} hour(s) and", + + "system-wide-alert-banner.countdown.minutes": "{{minutes}} minute(s):", + + + + "menu.section.system-wide-alert": "System-wide Alerts", + + "system-wide-alert.form.header": "System-wide Alerts", + + "system-wide-alert.form.cancel": "Cancel", + + "system-wide-alert.form.save": "Save", + + "system-wide-alert.form.label.active": "ACTIVE", + + "system-wide-alert.form.label.inactive": "INACTIVE", + + "system-wide-alert.form.error.message": "The system wide alert must have a message", + + "system-wide-alert.form.label.message": "Alert message", + + "system-wide-alert.form.label.countdownTo": "Count down", + + "system-wide-alert.form.label.countdownTo.hint": "Hint: When a date in the future is set, the system wide alert banner will perform a countdown to the set date. Setting the date to the current time or in the past will disable the countdown.", + + "system-wide-alert.form.update.success": "The system-wide alert was successfully updated", + + "system-wide-alert.form.update.error": "Something went wrong when updating the system-wide alert", + + "system-wide-alert.form.create.success": "The system-wide alert was successfully created", + + "system-wide-alert.form.create.error": "Something went wrong when creating the system-wide alert", + + "admin.system-wide-alert.breadcrumbs": "System-wide Alerts", + + "admin.system-wide-alert.title": "System-wide Alerts", } diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index d2ac0ae787..3aacac9c73 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -114,6 +114,7 @@ import { ObjectListComponent } from './app/shared/object-list/object-list.compon import { BrowseByMetadataPageComponent } from './app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; import { BrowseByDatePageComponent } from './app/browse-by/browse-by-date-page/browse-by-date-page.component'; import { BrowseByTitlePageComponent } from './app/browse-by/browse-by-title-page/browse-by-title-page.component'; +import { SystemWideAlertModule } from '../../app/system-wide-alert/system-wide-alert.module'; const DECLARATIONS = [ FileSectionComponent, @@ -220,6 +221,7 @@ const DECLARATIONS = [ FormsModule, ResourcePoliciesModule, ComcolModule, + SystemWideAlertModule ], declarations: DECLARATIONS, exports: [ diff --git a/yarn.lock b/yarn.lock index 9542bfdbe1..2e2ce3140e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4759,6 +4759,16 @@ data-urls@^3.0.1: whatwg-mimetype "^3.0.0" whatwg-url "^10.0.0" +date-fns-tz@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.7.tgz#e8e9d2aaceba5f1cc0e677631563081fdcb0e69a" + integrity sha512-1t1b8zyJo+UI8aR+g3iqr5fkUHWpd58VBx8J/ZSQ+w7YrGlw80Ag4sA86qkfCXRBLmMc4I2US+aPMd4uKvwj5g== + +date-fns@^2.29.3: + version "2.29.3" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" + integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== + date-format@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.4.tgz#b58036e29e74121fca3e1b3e0dc4a62c65faa233" From b6218494e548a4bdf11689ce39056df2007a2b1b Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 23 Dec 2022 12:15:35 +0100 Subject: [PATCH 022/173] 97425: Fix countdown timer intial display and remove rounded banner corner --- .../alert-banner/system-wide-alert-banner.component.html | 2 +- .../alert-banner/system-wide-alert-banner.component.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.html b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.html index 3e360cc55e..5f741091f1 100644 --- a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.html +++ b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.html @@ -1,5 +1,5 @@
-
+
diff --git a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts index b405957c54..57a9604f90 100644 --- a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts +++ b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts @@ -64,6 +64,7 @@ export class SystemWideAlertBannerComponent implements OnInit, OnDestroy { const date = zonedTimeToUtc(alert.countdownTo, 'UTC'); const timeDifference = date.getTime() - new Date().getTime(); if (timeDifference > 0) { + this.allocateTimeUnits(timeDifference); return interval(1000); } } From f15ca85afd9043aedb4402d619d721ebc66f1ba3 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 23 Dec 2022 13:18:40 +0100 Subject: [PATCH 023/173] [CST-7757] Subscriptions porting (wip) --- .../subscription-modal.component.html | 5 +- .../subscription-modal.component.ts | 168 +++++------------- 2 files changed, 50 insertions(+), 123 deletions(-) diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html index 53119d9a15..52746fb5c8 100644 --- a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html @@ -17,9 +17,8 @@
-
- +
+
diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts index c249cc592b..e0d025be54 100644 --- a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts +++ b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts @@ -1,38 +1,23 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; -import { - AbstractControl, - FormArray, - FormBuilder, - FormControl, - FormGroup, - ValidatorFn, - Validators -} from '@angular/forms'; +import { FormBuilder, FormGroup } from '@angular/forms'; import { Subscription } from '../../models/subscription.model'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, shareReplay } from 'rxjs'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { SubscriptionService } from '../../subscription.service'; import { NotificationsService } from '../../../notifications/notifications.service'; -import { NotificationType } from '../../../notifications/models/notification-type'; -import { NotificationOptions } from '../../../notifications/models/notification-options.model'; -import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; -import { hasValue } from '../../../empty.util'; -import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; - -import { filter, map, switchMap, take, tap } from 'rxjs/operators'; -import { NoContent } from '../../../../core/shared/NoContent.model'; +import { map, switchMap, take, tap } from 'rxjs/operators'; import { RemoteData } from '../../../../core/data/remote-data'; import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; -import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { AuthService } from '../../../../core/auth/auth.service'; @Component({ @@ -70,6 +55,8 @@ export class SubscriptionModalComponent implements OnInit { ePersonId$: Observable; + ePersonId: string; + /** * Types of subscription to be shown on select */ @@ -98,6 +85,10 @@ export class SubscriptionModalComponent implements OnInit { this.ePersonId$ = this.authService.getAuthenticatedUserFromStore().pipe( take(1), map((ePerson) => ePerson.uuid), + tap((res) => { + this.ePersonId = res; + }), + shareReplay(), ); this.subscriptionForm = this.formBuilder.group({}); @@ -138,9 +129,9 @@ export class SubscriptionModalComponent implements OnInit { // TODO loop over subscription types // for (let type of this.subscriptionTypes) { - const type = 'content'; // remove + const type = 'content'; // TODO remove const subscription = this.subscriptions.find((s) => s.subscriptionType === type); - // TODO manage multiple subscriptions with same tipe (there should be only one) + // TODO manage multiple subscriptions with same type (there should be only one) for (let parameter of subscription.subscriptionParameterList.filter((p) => p.name === 'frequency')) { this.subscriptionForm.controls[parameter.value]?.setValue(true); } @@ -169,29 +160,45 @@ export class SubscriptionModalComponent implements OnInit { submit() { - // TODO + // for (let type of this.subscriptionTypes) { - /* - - remove subscription if no checkbox is selected - - add subscription if it does not exist - - edit subscription if it already exists - */ + const type = 'content'; // TODO remove - const body = { - type: 'content', - subscriptionParameterList: [] - }; + const currentSubscription = this.subscriptions?.find((s) => s.subscriptionType === type); - for (let frequency of this.frequencies) { - if (this.subscriptionForm.value[frequency]) { - body.subscriptionParameterList.push( - { - name: 'frequency', - value: frequency, - } - ); + const body = { + id: currentSubscription?.id, + type, + subscriptionParameterList: [] + }; + + let someCheckboxSelected = false; + + for (let frequency of this.frequencies) { + if (this.subscriptionForm.value[frequency]) { // TODO read the value for the type + someCheckboxSelected = true; + body.subscriptionParameterList.push( + { + name: 'frequency', + value: frequency, + } + ); + } } - } + + if (currentSubscription && someCheckboxSelected) { + console.log('UPDATE'); + this.subscriptionService.updateSubscription(body, this.ePersonId, this.dso.uuid).subscribe(console.log); + } else if (currentSubscription && !someCheckboxSelected) { + console.log('DELETE'); + this.subscriptionService.deleteSubscription(currentSubscription.id).subscribe(console.log); + } else if (someCheckboxSelected) { + console.log('CREATE'); + this.subscriptionService.createSubscription(body, this.ePersonId, this.dso.uuid).subscribe(console.log); + } + + + // } // this.subscriptionService.createSubscription(body, this.ePersonId, this.dso.uuid).subscribe((res) => { // // this.refresh(); @@ -206,43 +213,6 @@ export class SubscriptionModalComponent implements OnInit { } - /** - * Sends request to create a new subscription, refreshes the table of subscriptions and notifies about summary page - */ - /*createForm(body): void { - this.subscriptionService.createSubscription(body, this.ePersonId, this.dso.uuid).subscribe((res) => { - this.refresh(); - this.notify(); - this.processing$.next(false); - }, - err => { - this.processing$.next(false); - } - ); - }*/ - - /** - * Sends request to update a subscription, refreshes the table of subscriptions and notifies about summary page - */ - /*updateForm(body) { - this.subscriptionService.updateSubscription(body, this.ePersonId, this.dso.uuid).subscribe((res) => { - this.refresh(); - this.notify(); - this.processing$.next(false); - }, - err => { - this.processing$.next(false); - } - ); - }*/ - - - /** - * Sends the request to delete the subscription with a specific id - */ - /*deleteSubscription(id): Observable { - return this.subscriptionService.deleteSubscription(id); - }*/ /** * Creates a notification with the link to the subscription summary page @@ -261,46 +231,4 @@ export class SubscriptionModalComponent implements OnInit { ); }*/ - /** - * When an action is done it will reinitialize the table and remove subscription form - */ - /*refresh(): void { - this.initSubscription(); - this.subscriptionForm = null; - this.submitted = false; - }*/ - - /** - * Returns if a specific frequency exists in the subscriptionParameterList - */ - getIsChecked(frequency): boolean { - return !!this.subscriptionForm.get('subscriptionParameterList').value.find(el => el.value === frequency.value); - } - - /** - * Deletes Subscription, show notification on success/failure & updates list - * - * @param subscription Subscription to be deleted - */ - /*deleteSubscriptionPopup(subscription: Subscription): void { - if (hasValue(subscription.id)) { - const modalRef = this.modalService.open(ConfirmationModalComponent); - modalRef.componentInstance.dso = this.dso; - modalRef.componentInstance.headerLabel = 'confirmation-modal.delete-subscription.header'; - modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-subscription.info'; - modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-subscription.cancel'; - modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-subscription.confirm'; - modalRef.componentInstance.brandColor = 'danger'; - modalRef.componentInstance.confirmIcon = 'fas fa-trash'; - - modalRef.componentInstance.response.pipe( - take(1), - filter((confirm: boolean) => confirm), - switchMap(() => this.deleteSubscription(subscription.id)) - ).subscribe(() => { - this.refresh(); - }); - - } - }*/ } From 3ab552ed389c1d2b660985c75e2d004202b56765 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 23 Dec 2022 14:06:28 +0100 Subject: [PATCH 024/173] [CST-7757] Subscriptions porting (wip) --- .../subscription-modal.component.ts | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts index e0d025be54..39cc79ff8d 100644 --- a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts +++ b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts @@ -17,8 +17,10 @@ import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { map, switchMap, take, tap } from 'rxjs/operators'; import { RemoteData } from '../../../../core/data/remote-data'; -import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; import { AuthService } from '../../../../core/auth/auth.service'; +import { NotificationType } from '../../../notifications/models/notification-type'; +import { NotificationOptions } from '../../../notifications/models/notification-options.model'; @Component({ selector: 'ds-subscription-modal', @@ -188,7 +190,17 @@ export class SubscriptionModalComponent implements OnInit { if (currentSubscription && someCheckboxSelected) { console.log('UPDATE'); - this.subscriptionService.updateSubscription(body, this.ePersonId, this.dso.uuid).subscribe(console.log); + this.subscriptionService.updateSubscription(body, this.ePersonId, this.dso.uuid).pipe( + getFirstCompletedRemoteData(), + ).subscribe((res) => { + if (res.hasSucceeded) { + this.notifySuccess(); + this.activeModal.close(); + } else { + this.notifyFailure(); + } + } + ); } else if (currentSubscription && !someCheckboxSelected) { console.log('DELETE'); this.subscriptionService.deleteSubscription(currentSubscription.id).subscribe(console.log); @@ -217,7 +229,7 @@ export class SubscriptionModalComponent implements OnInit { /** * Creates a notification with the link to the subscription summary page */ - /*notify(): void { + notifySuccess(): void { const options = new NotificationOptions(); options.timeOut = 0; const link = '/subscriptions'; @@ -229,6 +241,11 @@ export class SubscriptionModalComponent implements OnInit { 'context-menu.actions.subscription.notification.content', 'here' ); - }*/ + } + + notifyFailure() { + console.error('error'); + // TODO + } } From a6cd39c9067b1ee4dd7c004a28519becc83e5467 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 23 Dec 2022 18:25:04 +0100 Subject: [PATCH 025/173] [CST-7757] Subscriptions porting (wip) --- .../subscription-modal.component.ts | 39 ++++++++--- .../subscriptions-page.component.html | 2 +- .../subscriptions-page.component.ts | 66 +++++++++++-------- src/assets/i18n/en.json5 | 32 +++++++++ 4 files changed, 101 insertions(+), 38 deletions(-) diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts index 39cc79ff8d..b283e12d34 100644 --- a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts +++ b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts @@ -188,25 +188,44 @@ export class SubscriptionModalComponent implements OnInit { } } - if (currentSubscription && someCheckboxSelected) { - console.log('UPDATE'); - this.subscriptionService.updateSubscription(body, this.ePersonId, this.dso.uuid).pipe( - getFirstCompletedRemoteData(), - ).subscribe((res) => { + if (currentSubscription) { + const subscriptionsToBeRemobed = this.subscriptions?.filter( + (s) => s.subscriptionType === type && s.id !== currentSubscription.id + ); + for (let s of subscriptionsToBeRemobed) { + this.subscriptionService.deleteSubscription(currentSubscription.id).pipe( + getFirstCompletedRemoteData(), + ).subscribe((res) => { if (res.hasSucceeded) { - this.notifySuccess(); - this.activeModal.close(); + console.warn(`An additional subscription with type=${type} and id=${s.id} has been removed`); } else { this.notifyFailure(); } + }); + } + } + + + if (currentSubscription && someCheckboxSelected) { + // Update the existing subscription + this.subscriptionService.updateSubscription(body, this.ePersonId, this.dso.uuid).pipe( + getFirstCompletedRemoteData(), + ).subscribe((res) => { + if (res.hasSucceeded) { + this.notifySuccess(); + this.activeModal.close(); + } else { + this.notifyFailure(); } - ); + }); } else if (currentSubscription && !someCheckboxSelected) { - console.log('DELETE'); + // Delete the existing subscription this.subscriptionService.deleteSubscription(currentSubscription.id).subscribe(console.log); + // TODO handle notifications } else if (someCheckboxSelected) { - console.log('CREATE'); + // Create a new subscription this.subscriptionService.createSubscription(body, this.ePersonId, this.dso.uuid).subscribe(console.log); + // TODO handle notifications } diff --git a/src/app/subscriptions-page/subscriptions-page.component.html b/src/app/subscriptions-page/subscriptions-page.component.html index bfbdbe72fc..9d1e6d504e 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.html +++ b/src/app/subscriptions-page/subscriptions-page.component.html @@ -10,7 +10,7 @@ diff --git a/src/app/subscriptions-page/subscriptions-page.component.ts b/src/app/subscriptions-page/subscriptions-page.component.ts index b81b7c5617..2951cf037c 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.ts +++ b/src/app/subscriptions-page/subscriptions-page.component.ts @@ -1,6 +1,6 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; -import { BehaviorSubject, Subscription as rxSubscription } from 'rxjs'; -import { switchMap, take } from 'rxjs/operators'; +import { BehaviorSubject, combineLatestWith, Observable, of, shareReplay, Subscription as rxSubscription } from 'rxjs'; +import { combineLatest, map, switchMap, take, tap } from 'rxjs/operators'; import { Subscription } from '../shared/subscriptions/models/subscription.model'; import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model'; import { SubscriptionService } from '../shared/subscriptions/subscription.service'; @@ -9,6 +9,7 @@ import { PaginationService } from '../core/pagination/pagination.service'; import { PageInfo } from '../core/shared/page-info.model'; import { AuthService } from '../core/auth/auth.service'; import { EPerson } from '../core/eperson/models/eperson.model'; +import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; @Component({ selector: 'ds-subscriptions-page', @@ -42,10 +43,12 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { */ loading$: BehaviorSubject = new BehaviorSubject(false); + ePersonId$: Observable; + /** * EPerson id of the logged in user */ - eperson: string; + // ePersonId: string; constructor( private paginationService: PaginationService, @@ -58,27 +61,32 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { * When page is changed it will request the new subscriptions for the new page config */ ngOnInit(): void { - this.authService.getAuthenticatedUserFromStore().pipe(take(1)).subscribe( (eperson: EPerson) => { - this.eperson = eperson.id; - - this.sub = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( - switchMap((findListOptions) => { - this.loading$.next(true); - return this.subscriptionService.findByEPerson(this.eperson,{ - currentPage: findListOptions.currentPage, - elementsPerPage: findListOptions.pageSize - }); - } - ) - ).subscribe({ - next: (res: any) => { - this.subscriptions$.next(res); - this.loading$.next(false); - }, - error: () => { - this.loading$.next(false); - } - }); + this.ePersonId$ = this.authService.getAuthenticatedUserFromStore().pipe( + take(1), + map((ePerson: EPerson) => ePerson.id), + shareReplay(), + /*tap((ePersonId: string) => { // TODO unused + this.ePersonId = ePersonId; + }),*/ + ); + const currentPagination$ = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + tap(console.log), + combineLatestWith(this.ePersonId$), + tap(() => {this.loading$.next(true);}), + switchMap(([currentPagination, ePersonId]) => this.subscriptionService.findByEPerson(ePersonId,{ + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize + })), + tap((x) => console.log('find', x)), + // getFirstSucceededRemoteDataPayload(), + ).subscribe({ + next: (res: any) => { + this.subscriptions$.next(res); + this.loading$.next(false); + }, + error: () => { + this.loading$.next(false); + } }); } @@ -86,11 +94,11 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { * When an action is made and the information is changed refresh the information */ refresh(): void { - this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + /*this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( take(1), switchMap((findListOptions) => { this.loading$.next(true); - return this.subscriptionService.findByEPerson(this.eperson,{ + return this.subscriptionService.findByEPerson(this.ePersonId,{ currentPage: findListOptions.currentPage, elementsPerPage: findListOptions.pageSize }); @@ -104,7 +112,7 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { error: () => { this.loading$.next(false); } - }); + });*/ } /** @@ -114,4 +122,8 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { this.sub.unsubscribe(); } + obs(v) { + return of(v); + } + } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 12a21beaa2..2af33eb11d 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2915,6 +2915,8 @@ "nav.stop-impersonating": "Stop impersonating EPerson", + "nav.subscriptions" : "Subscriptions", + "nav.toggle" : "Toggle navigation", "nav.user.description" : "User profile bar", @@ -4492,6 +4494,36 @@ "submission.workspace.generic.view-help": "Select this option to view the item's metadata.", + "subscriptions.title": "Subscriptions", + + "subscriptions.item": "Subscriptions for items", + + "subscriptions.collection": "Subscriptions for collections", + + "subscriptions.community": "Subscriptions for communities", + + "subscriptions.subscription_type": "Subscription type", + + "subscriptions.frequency": "Subscription frequency", + + "subscriptions.frequency.D": "Daily", + + "subscriptions.frequency.M": "Monthly", + + "subscriptions.frequency.W": "Weekly", + + "subscriptions.table.dso": "Subject", + + "subscriptions.table.subscription_type": "Subscription Type", + + "subscriptions.table.subscription_frequency": "Subscription Frequency", + + "subscriptions.table.action": "Action", + + "subscriptions.table.empty.message": "You have not subscribed any notification yet. To subscribe notification about an object please use the contextual menu in the object detail view", + + + "thumbnail.default.alt": "Thumbnail Image", "thumbnail.default.placeholder": "No Thumbnail Available", From 72a42b0a6e4df708e5bbe97e06033575e5fc5625 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Tue, 27 Dec 2022 20:10:47 +0100 Subject: [PATCH 026/173] [CST-7757] Refactoring code --- ...so-page-subscription-button.component.html | 2 +- ...page-subscription-button.component.spec.ts | 66 ++++- .../dso-page-subscription-button.component.ts | 7 +- .../subscription-edit-modal.component.html | 44 --- .../subscription-edit-modal.component.scss | 4 - .../subscription-edit-modal.component.spec.ts | 116 -------- .../subscription-edit-modal.component.ts | 190 ------------ .../subscription-modal.component.scss | 12 - .../subscription-modal.component.spec.ts | 223 --------------- .../subscription-modal.component.ts | 270 ------------------ .../models/subscription.model.ts | 30 +- .../subscription-modal.component.html | 15 +- .../subscription-modal.component.scss} | 0 .../subscription-modal.component.spec.ts | 194 +++++++++++++ .../subscription-modal.component.ts | 261 +++++++++++++++++ .../subscription-view.component.html | 7 +- .../subscription-view.component.scss | 0 .../subscription-view.component.spec.ts | 37 ++- .../subscription-view.component.ts | 42 +-- .../subscriptions/subscription.service.ts | 11 +- .../subscriptions/subscriptions.module.ts | 17 +- .../shared/testing/subscriptions-data.mock.ts | 33 ++- .../subscriptions-page.component.html | 13 +- .../subscriptions-page.component.ts | 55 +--- src/assets/i18n/en.json5 | 21 ++ 25 files changed, 689 insertions(+), 981 deletions(-) delete mode 100644 src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.html delete mode 100644 src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.scss delete mode 100644 src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.spec.ts delete mode 100644 src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.ts delete mode 100644 src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.scss delete mode 100644 src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.spec.ts delete mode 100644 src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts rename src/app/shared/subscriptions/{components => }/subscription-modal/subscription-modal.component.html (64%) rename src/app/shared/subscriptions/{components/subscription-view/subscription-view.component.scss => subscription-modal/subscription-modal.component.scss} (100%) create mode 100644 src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts create mode 100644 src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts rename src/app/shared/subscriptions/{components => }/subscription-view/subscription-view.component.html (69%) create mode 100644 src/app/shared/subscriptions/subscription-view/subscription-view.component.scss rename src/app/shared/subscriptions/{components => }/subscription-view/subscription-view.component.spec.ts (77%) rename src/app/shared/subscriptions/{components => }/subscription-view/subscription-view.component.ts (66%) diff --git a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html index 9956163944..1932c37000 100644 --- a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html +++ b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html @@ -1,4 +1,4 @@ - -
- - diff --git a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.scss b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.scss deleted file mode 100644 index fb9db5b0ff..0000000000 --- a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.scss +++ /dev/null @@ -1,4 +0,0 @@ -.alert{ - font-weight: bold; - color:red; -} diff --git a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.spec.ts b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.spec.ts deleted file mode 100644 index d406fa2c1a..0000000000 --- a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { ComponentFixture, ComponentFixtureAutoDetect, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing'; -import { cold } from 'jasmine-marbles'; - -// Import modules -import { CommonModule } from '@angular/common'; -import { ReactiveFormsModule } from '@angular/forms'; -import { By } from '@angular/platform-browser'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { DebugElement } from '@angular/core'; - -import { SubscriptionEditModalComponent } from './subscription-edit-modal.component'; - -// Import mocks -import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; -import { subscription } from '../../../testing/subscriptions-data.mock'; -import { ItemInfo } from '../../../testing/relationships-mocks'; - -// Import utils -import { NotificationsService } from '../../../notifications/notifications.service'; -import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; -import { SubscriptionService } from '../../subscription.service'; -import { Subscription } from '../../models/subscription.model'; - - -describe('SubscriptionEditModalComponent', () => { - let component: SubscriptionEditModalComponent; - let fixture: ComponentFixture; - let de: DebugElement; - - const subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { - updateSubscription: jasmine.createSpy('updateSubscription'), - }); - - - beforeEach(waitForAsync (() => { - TestBed.configureTestingModule({ - imports: [ - CommonModule, - ReactiveFormsModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - }), - ], - declarations: [ SubscriptionEditModalComponent ], - providers: [ - { provide: ComponentFixtureAutoDetect, useValue: true }, - { provide: NotificationsService, useValue: NotificationsServiceStub }, - { provide: SubscriptionService, useValue: subscriptionServiceStub }, - ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SubscriptionEditModalComponent); - component = fixture.componentInstance; - component.eperson = 'testid123'; - component.dso = ItemInfo.payload; - - de = fixture.debugElement; - - subscriptionServiceStub.updateSubscription.and.returnValue(cold('a|', { - a: {} - })); - - - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - describe('No Subscription inserted', () => { - it('should not show form', () => { - expect(de.query(By.css('form'))).toBeNull(); - }); - }); - - - describe('Subscription inserted', () => { - - beforeEach(fakeAsync(() => { - component.subscription = Object.assign(new Subscription(), subscription); - component.ngOnInit(); - fixture.detectChanges(); - })); - - it('when insert subscription show form', () => { - expect(de.query(By.css('form'))).toBeTruthy(); - }); - - it('should have right checkboxes checked', () => { - expect(de.query(By.css('#checkbox-0'))?.nativeElement?.checked).toEqual(true); - expect(de.query(By.css('#checkbox-1'))?.nativeElement?.checked).toEqual(true); - expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(false); - }); - - it('on checkbox clicked should change form values', () => { - const checkbox = de.query(By.css('#checkbox-2')).nativeElement; - checkbox.click(); - - expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(true); - expect(component.subscriptionParameterList?.value?.length).toEqual(3); - }); - - it('on submit clicked update should have been called', () => { - const button = de.query(By.css('.btn-success')).nativeElement; - button.click(); - expect(subscriptionServiceStub.updateSubscription).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.ts b/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.ts deleted file mode 100644 index 46c895dc18..0000000000 --- a/src/app/shared/subscriptions/components/subscription-edit-modal/subscription-edit-modal.component.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; - -import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms'; - -import { Subscription } from '../../models/subscription.model'; - -import { BehaviorSubject } from 'rxjs'; - -import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; - -import { SubscriptionService } from '../../subscription.service'; -import { NotificationsService } from '../../../notifications/notifications.service'; - -import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; - -@Component({ - selector: 'ds-subscription-edit-modal', - templateUrl: './subscription-edit-modal.component.html', - styleUrls: ['./subscription-edit-modal.component.scss'] -}) -export class SubscriptionEditModalComponent implements OnInit { - - - /** - * DSpaceObject of the subscription - */ - @Input() dso: DSpaceObject; - - /** - * EPerson of the subscription - */ - @Input() eperson: string; - - /** - * List of subscription for the dso object and eperson relation - */ - @Input() subscription!: Subscription; - - /** - * Close event emit to close modal - */ - @Output() close: EventEmitter = new EventEmitter(); - - /** - * Reload event emit to refresh informations - */ - @Output() reload: EventEmitter = new EventEmitter(); - - /** - * A boolean representing if a request operation is pending - * @type {BehaviorSubject} - */ - public processing$ = new BehaviorSubject(false); - - /** - * Reactive form group that will be used to add subscriptions - */ - subscriptionForm: FormGroup; - - /** - * Used to show validation errors when user submits - */ - submitted = false; - - /** - * Reference to NgbModal - */ - public modalRef: NgbModalRef; - - - /** - * Frequencies to be shown as checkboxes - */ - frequencies = [ - {name: 'daily' ,value: 'D'}, - {name: 'monthly' ,value: 'M'}, - {name: 'weekly' ,value: 'W'}, - ]; - - constructor(private formGroup: FormBuilder, - private notificationsService: NotificationsService, - private subscriptionService: SubscriptionService - ) {} - - /** - * When component starts initialize starting functionality - */ - ngOnInit(): void { - this.initSubscription(); - } - - /** - * If the subscription is passed start the form with the information of subscription - */ - initSubscription(): void { - if (!!this.subscription) { - this.buildFormBuilder(this.subscription); - } - } - - /** - * Function to get subscriptionParameterList form array cleaner - */ - get subscriptionParameterList(): FormArray { - return this.subscriptionForm.get('subscriptionParameterList') as FormArray; - } - - /** - * When frequency checkboxes are being changed we add/remove frequencies from subscriptionParameterList - */ - selectCheckbox(event,frequency): void { - if (event.target.checked) { - this.addFrequency(frequency); - } else { - this.removeFrequency(frequency); - } - } - - /** - * Start the form with preinserted informations - */ - buildFormBuilder(subscription): void { - - this.subscriptionForm = this.formGroup.group({ - id: subscription.id, - type: subscription.subscriptionType, - subscriptionParameterList: this.formGroup.array([], Validators.required) - }); - - subscription.subscriptionParameterList.forEach( (parameter) => { - this.addFrequency(parameter.value); - }); - } - - /** - * Add a new frequency to the subscriptionParameterList form array - */ - addFrequency(frequency): void { - this.subscriptionParameterList.push( - this.formGroup.group({ - name: 'frequency', - value: frequency - }) - ); - } - - /** - * Remove frequency from subscriptionParameterList form array - */ - removeFrequency(frequency): void { - const index = this.subscriptionParameterList.controls.findIndex(el => el.value.value === frequency); - this.subscriptionParameterList.removeAt(index); - } - - /** - * When user saves it will check if form is valid and send request to update subscription - */ - submit(): void { - this.submitted = true; - if (this.subscriptionForm.valid) { - if (this.subscriptionForm.value.id) { - this.updateForm(this.subscriptionForm.value); - } - } - } - - /** - * Sends request to update a new subscription, refreshes the table of subscriptions and notifies about summary page - */ - updateForm(body): void { - this.subscriptionService.updateSubscription(body,this.eperson,this.dso.uuid).subscribe( (res) => { - this.reload.emit(); - this.close.emit(); - }); - } - - /** - * When close button is pressed emit function to close modal - */ - c(text): void { - this.close.emit(text); - } - - /** - * Returns if a specific frequency exists in the subscriptionParameterList - */ - getIsChecked(frequency): boolean { - return !!this.subscriptionForm.get('subscriptionParameterList').value.find(el => el.value === frequency.value); - } -} diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.scss b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.scss deleted file mode 100644 index 62dd1105e7..0000000000 --- a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.scss +++ /dev/null @@ -1,12 +0,0 @@ -.alert{ - font-weight: bold; - color:red; -} - -// .modal-footer{ -// justify-content: space-between; -// } - -.add-button{ - padding: 0px 15px 15px 15px; -} \ No newline at end of file diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.spec.ts b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.spec.ts deleted file mode 100644 index 088ec3004c..0000000000 --- a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.spec.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { ComponentFixture, ComponentFixtureAutoDetect, TestBed } from '@angular/core/testing'; - -import { of as observableOf } from 'rxjs'; - -// Import modules -import { CommonModule } from '@angular/common'; -import { ReactiveFormsModule } from '@angular/forms'; -import { By } from '@angular/platform-browser'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { DebugElement } from '@angular/core'; - -import { SubscriptionModalComponent } from './subscription-modal.component'; - -// Import mocks -import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; -import { findByEPersonAndDsoRes, findByEPersonAndDsoResEmpty } from '../../../testing/subscriptions-data.mock'; -import { ItemInfo } from '../../../testing/relationships-mocks'; - -// Import utils -import { NotificationsService } from '../../../notifications/notifications.service'; -import { SubscriptionService } from '../../subscription.service'; - -import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils'; - - -describe('SubscriptionModalComponent', () => { - let component: SubscriptionModalComponent; - let fixture: ComponentFixture; - let de: DebugElement; - - let subscriptionServiceStub; - const notificationServiceStub = { - notificationWithAnchor() { - return true; - } - }; - - - describe('when empty subscriptions', () => { - - beforeEach(async () => { - - subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { - getSubscriptionByPersonDSO: observableOf(findByEPersonAndDsoResEmpty), - createSubscription: createSuccessfulRemoteDataObject$({}), - updateSubscription: createSuccessfulRemoteDataObject$({}), - }); - - await TestBed.configureTestingModule({ - imports: [ - CommonModule, - NgbModule, - ReactiveFormsModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - }), - ], - declarations: [SubscriptionModalComponent], - providers: [ - { provide: ComponentFixtureAutoDetect, useValue: true }, - { provide: NotificationsService, useValue: notificationServiceStub }, - { provide: SubscriptionService, useValue: subscriptionServiceStub }, - ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(SubscriptionModalComponent); - component = fixture.componentInstance; - component.ePersonId = 'testid123'; - component.dso = ItemInfo.payload; - de = fixture.debugElement; - - await fixture.whenStable(); - await fixture.whenRenderingDone(); - - fixture.detectChanges(); - - }); - - it('should be no table', () => { - expect(de.query(By.css('table'))).toBeNull(); - }); - - it('should show empty form', () => { - expect(de.query(By.css('form'))).toBeTruthy(); - }); - - it('should show form with empty checkboxes', () => { - expect(de.query(By.css('#checkbox-0'))?.nativeElement?.checked).toEqual(false); - expect(de.query(By.css('#checkbox-1'))?.nativeElement?.checked).toEqual(false); - expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(false); - }); - - }); - - - describe('when we have subscriptions', () => { - - beforeEach(async () => { - - subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { - getSubscriptionByPersonDSO: observableOf(findByEPersonAndDsoRes), - createSubscription: createSuccessfulRemoteDataObject$({}), - updateSubscription: createSuccessfulRemoteDataObject$({}), - }); - - await TestBed.configureTestingModule({ - imports: [ - CommonModule, - NgbModule, - ReactiveFormsModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - }), - ], - declarations: [SubscriptionModalComponent], - providers: [ - { provide: ComponentFixtureAutoDetect, useValue: true }, - { provide: NotificationsService, useValue: notificationServiceStub }, - { provide: SubscriptionService, useValue: subscriptionServiceStub }, - ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(SubscriptionModalComponent); - component = fixture.componentInstance; - component.ePersonId = 'testid123'; - component.dso = ItemInfo.payload; - de = fixture.debugElement; - await fixture.whenStable(); - await fixture.whenRenderingDone(); - - fixture.detectChanges(); - - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); - - it('should render 2 subscriptions', () => { - expect(de.queryAll(By.css('tbody > tr')).length).toEqual(2); - }); - - it('should show no form', () => { - expect(de.query(By.css('form'))).toBeNull(); - }); - - it('should have 2 edit buttons', () => { - expect(de.queryAll(By.css('.btn-outline-primary')).length).toEqual(2); - }); - - it('should have 2 delete buttons', () => { - expect(de.queryAll(By.css('.btn-outline-danger')).length).toEqual(2); - }); - - describe('When creating new subscription', () => { - - beforeEach(() => { - // add button click - const button = de.query(By.css('.btn-success')).nativeElement; - button.click(); - }); - - - it('should show form when add button click event', () => { - expect(de.query(By.css('form'))).toBeTruthy(); - }); - - it('should show form with empty checkboxes', () => { - expect(de.query(By.css('#checkbox-0'))?.nativeElement?.checked).toEqual(false); - expect(de.query(By.css('#checkbox-1'))?.nativeElement?.checked).toEqual(false); - expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(false); - }); - - it('should call create request when submit click event', () => { - const checkbox = de.query(By.css('#checkbox-2')).nativeElement; - checkbox.click(); - - const button = de.queryAll(By.css('.btn-success'))[1].nativeElement; - button.click(); - expect(subscriptionServiceStub.createSubscription).toHaveBeenCalled(); - }); - - }); - - - describe('When updating subscription', () => { - - beforeEach(() => { - // edit button click - const button = de.query(By.css('.btn-outline-primary')).nativeElement; - button.click(); - }); - - it('should show form when edit button click event', () => { - expect(de.query(By.css('form'))).toBeTruthy(); - }); - - it('should show form with empty checkboxes', () => { - expect(de.query(By.css('#checkbox-0'))?.nativeElement?.checked).toEqual(false); - expect(de.query(By.css('#checkbox-1'))?.nativeElement?.checked).toEqual(true); - expect(de.query(By.css('#checkbox-2'))?.nativeElement?.checked).toEqual(false); - }); - - it('should call update request when submit click event', () => { - const button = de.queryAll(By.css('.btn-success'))[1].nativeElement; - button.click(); - expect(subscriptionServiceStub.updateSubscription).toHaveBeenCalled(); - }); - - }); - - }); - -}); diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts deleted file mode 100644 index b283e12d34..0000000000 --- a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; - -import { FormBuilder, FormGroup } from '@angular/forms'; - -import { Subscription } from '../../models/subscription.model'; - -import { BehaviorSubject, Observable, shareReplay } from 'rxjs'; - -import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; - -import { SubscriptionService } from '../../subscription.service'; -import { NotificationsService } from '../../../notifications/notifications.service'; - -import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; - -import { PaginatedList } from '../../../../core/data/paginated-list.model'; - -import { map, switchMap, take, tap } from 'rxjs/operators'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; -import { AuthService } from '../../../../core/auth/auth.service'; -import { NotificationType } from '../../../notifications/models/notification-type'; -import { NotificationOptions } from '../../../notifications/models/notification-options.model'; - -@Component({ - selector: 'ds-subscription-modal', - templateUrl: './subscription-modal.component.html', - styleUrls: ['./subscription-modal.component.scss'] -}) -export class SubscriptionModalComponent implements OnInit { - - /** - * DSpaceObject of which to get the subscriptions - */ - @Input() dso: DSpaceObject; - - /** - * List of subscription for the dso object and eperson relation - */ - subscriptions: Subscription[]; - - /** - * A boolean representing if a request operation is pending - * @type {BehaviorSubject} - */ - public processing$ = new BehaviorSubject(false); - - /** - * Reactive form group that will be used to add subscriptions - */ - subscriptionForm: FormGroup; - - /** - * Used to show validation errors when user submits - */ - submitted = false; - - ePersonId$: Observable; - - ePersonId: string; - - /** - * Types of subscription to be shown on select - */ - subscriptionTypes = [ 'content', 'statistics' ]; - - /** - * Frequencies to be shown as checkboxes - */ - frequencies = [ 'D', 'M', 'W' ]; - - constructor( - private formBuilder: FormBuilder, - private modalService: NgbModal, - private notificationsService: NotificationsService, - private subscriptionService: SubscriptionService, - public activeModal: NgbActiveModal, - private authService: AuthService, - ) { - } - - /** - * When component starts initialize starting functionality - */ - ngOnInit(): void { - - this.ePersonId$ = this.authService.getAuthenticatedUserFromStore().pipe( - take(1), - map((ePerson) => ePerson.uuid), - tap((res) => { - this.ePersonId = res; - }), - shareReplay(), - ); - - this.subscriptionForm = this.formBuilder.group({}); - - for (let f of this.frequencies) { - this.subscriptionForm.addControl(f, this.formBuilder.control(false)); - } - - // TODO iterate over subscription types - - /*this.subscriptionForm = this.formBuilder.group({}); - for (let t of this.subscriptionTypes) { - this.subscriptionForm.addControl(t, this.formBuilder.group({})); - for (let f of this.frequencies) { - this.subscriptionForm[t].addControl(f, this.formBuilder.control(false)); - } - }*/ - - - this.initSubscription(); - - } - - /** - * Get subscription for the eperson & dso object relation - * If no subscription start with an empty form - */ - initSubscription(): void { - this.processing$.next(true); - this.ePersonId$.pipe( - tap(console.log), - switchMap((ePersonId: string) => this.getSubscription(ePersonId, this.dso?.uuid)), - getFirstSucceededRemoteDataPayload(), - ).subscribe({ - next: (res: PaginatedList) => { - if (res.pageInfo.totalElements > 0) { - this.subscriptions = res.page; - - // TODO loop over subscription types - // for (let type of this.subscriptionTypes) { - const type = 'content'; // TODO remove - const subscription = this.subscriptions.find((s) => s.subscriptionType === type); - // TODO manage multiple subscriptions with same type (there should be only one) - for (let parameter of subscription.subscriptionParameterList.filter((p) => p.name === 'frequency')) { - this.subscriptionForm.controls[parameter.value]?.setValue(true); - } - // } - - } - this.processing$.next(false); - }, - error: err => { - this.processing$.next(false); - } - }); - } - - /** - * Function to get subscriptions based on the eperson & dso - * - * @param ePersonId Eperson that is logged in - * @param uuid DSpaceObject id that subscriptions are related to - */ - getSubscription(ePersonId: string, uuid: string): Observable>> { - return this.subscriptionService.getSubscriptionByPersonDSO(ePersonId, uuid); - } - - - - submit() { - - // for (let type of this.subscriptionTypes) { - - const type = 'content'; // TODO remove - - const currentSubscription = this.subscriptions?.find((s) => s.subscriptionType === type); - - const body = { - id: currentSubscription?.id, - type, - subscriptionParameterList: [] - }; - - let someCheckboxSelected = false; - - for (let frequency of this.frequencies) { - if (this.subscriptionForm.value[frequency]) { // TODO read the value for the type - someCheckboxSelected = true; - body.subscriptionParameterList.push( - { - name: 'frequency', - value: frequency, - } - ); - } - } - - if (currentSubscription) { - const subscriptionsToBeRemobed = this.subscriptions?.filter( - (s) => s.subscriptionType === type && s.id !== currentSubscription.id - ); - for (let s of subscriptionsToBeRemobed) { - this.subscriptionService.deleteSubscription(currentSubscription.id).pipe( - getFirstCompletedRemoteData(), - ).subscribe((res) => { - if (res.hasSucceeded) { - console.warn(`An additional subscription with type=${type} and id=${s.id} has been removed`); - } else { - this.notifyFailure(); - } - }); - } - } - - - if (currentSubscription && someCheckboxSelected) { - // Update the existing subscription - this.subscriptionService.updateSubscription(body, this.ePersonId, this.dso.uuid).pipe( - getFirstCompletedRemoteData(), - ).subscribe((res) => { - if (res.hasSucceeded) { - this.notifySuccess(); - this.activeModal.close(); - } else { - this.notifyFailure(); - } - }); - } else if (currentSubscription && !someCheckboxSelected) { - // Delete the existing subscription - this.subscriptionService.deleteSubscription(currentSubscription.id).subscribe(console.log); - // TODO handle notifications - } else if (someCheckboxSelected) { - // Create a new subscription - this.subscriptionService.createSubscription(body, this.ePersonId, this.dso.uuid).subscribe(console.log); - // TODO handle notifications - } - - - // } - - // this.subscriptionService.createSubscription(body, this.ePersonId, this.dso.uuid).subscribe((res) => { - // // this.refresh(); - // // this.notify(); - // // this.processing$.next(false); - // }, - // err => { - // // this.processing$.next(false); - // } - // ); - - } - - - - /** - * Creates a notification with the link to the subscription summary page - */ - notifySuccess(): void { - const options = new NotificationOptions(); - options.timeOut = 0; - const link = '/subscriptions'; - this.notificationsService.notificationWithAnchor( - NotificationType.Success, - options, - link, - 'context-menu.actions.subscription.notification.here-text', - 'context-menu.actions.subscription.notification.content', - 'here' - ); - } - - notifyFailure() { - console.error('error'); - // TODO - } - -} diff --git a/src/app/shared/subscriptions/models/subscription.model.ts b/src/app/shared/subscriptions/models/subscription.model.ts index b5d6977d39..36e43ce996 100644 --- a/src/app/shared/subscriptions/models/subscription.model.ts +++ b/src/app/shared/subscriptions/models/subscription.model.ts @@ -1,11 +1,14 @@ +import { Observable } from 'rxjs'; import { autoserialize, deserialize, inheritSerialization } from 'cerialize'; -import { typedObject } from '../../../core/cache/builders/build-decorators'; +import { link, typedObject } from '../../../core/cache/builders/build-decorators'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { HALLink } from '../../../core/shared/hal-link.model'; import { SUBSCRIPTION } from './subscription.resource-type'; import { EPerson } from '../../../core/eperson/models/eperson.model'; - +import { RemoteData } from '../../../core/data/remote-data'; +import { EPERSON } from '../../../core/eperson/models/eperson.resource-type'; +import { DSPACE_OBJECT } from '../../../core/shared/dspace-object.resource-type'; @typedObject @inheritSerialization(DSpaceObject) @@ -40,14 +43,27 @@ export class Subscription extends DSpaceObject { dSpaceObject: HALLink; }; + /** + * The logo for this Community + * Will be undefined unless the logo {@link HALLink} has been resolved. + */ + @link(EPERSON) + ePerson?: Observable>; + + /** + * The logo for this Community + * Will be undefined unless the logo {@link HALLink} has been resolved. + */ + @link(DSPACE_OBJECT) + dSpaceObject?: Observable>; /** * The embedded ePerson & dSpaceObject for this Subscription */ - @deserialize - _embedded: { - ePerson: EPerson; - dSpaceObject: DSpaceObject; - }; + /* @deserialize + _embedded: { + ePerson: EPerson; + dSpaceObject: DSpaceObject; + };*/ } export interface SubscriptionParameterList { diff --git a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html similarity index 64% rename from src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html rename to src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html index 52746fb5c8..ef9e26032d 100644 --- a/src/app/shared/subscriptions/components/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html @@ -1,4 +1,4 @@ -
+ - - - - - diff --git a/src/app/shared/subscriptions/subscription-view/subscription-view.component.scss b/src/app/shared/subscriptions/subscription-view/subscription-view.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.spec.ts b/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts similarity index 77% rename from src/app/shared/subscriptions/components/subscription-view/subscription-view.component.spec.ts rename to src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts index e32112c5a0..3ec826d6c5 100644 --- a/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.spec.ts +++ b/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts @@ -6,27 +6,27 @@ import { ReactiveFormsModule } from '@angular/forms'; import { BrowserModule, By } from '@angular/platform-browser'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { SharedModule } from '../../../shared.module'; +import { SharedModule } from '../../shared.module'; import { DebugElement } from '@angular/core'; import { RouterTestingModule } from '@angular/router/testing'; import { SubscriptionViewComponent } from './subscription-view.component'; // Import mocks -import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; -import { ItemInfo } from '../../../testing/relationships-mocks'; -import { findByEPersonAndDsoResEmpty, subscription } from '../../../testing/subscriptions-data.mock'; +import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; +import { findByEPersonAndDsoResEmpty, subscriptionMock } from '../../testing/subscriptions-data.mock'; // Import utils -import { NotificationsService } from '../../../notifications/notifications.service'; -import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; -import { SubscriptionService } from '../../subscription.service'; -import { Subscription } from '../../models/subscription.model'; +import { NotificationsService } from '../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; +import { SubscriptionService } from '../subscription.service'; +import { Subscription } from '../models/subscription.model'; import { of as observableOf } from 'rxjs'; -import { createSuccessfulRemoteDataObject$ } from '../../../remote-data.utils'; - +import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; +import { Item } from '../../../core/shared/item.model'; +import { ITEM } from '../../../core/shared/item.resource-type'; describe('SubscriptionViewComponent', () => { let component: SubscriptionViewComponent; @@ -40,6 +40,19 @@ describe('SubscriptionViewComponent', () => { updateSubscription: createSuccessfulRemoteDataObject$({}), }); + const mockItem = Object.assign(new Item(), { + id: 'fake-id', + uuid: 'fake-id', + handle: 'fake/handle', + lastModified: '2018', + type: ITEM, + _links: { + self: { + href: 'https://localhost:8000/items/fake-id' + } + } + }); + beforeEach(async () => { await TestBed.configureTestingModule({ imports: [ @@ -70,8 +83,8 @@ describe('SubscriptionViewComponent', () => { fixture = TestBed.createComponent(SubscriptionViewComponent); component = fixture.componentInstance; component.eperson = 'testid123'; - component.dso = ItemInfo.payload; - component.subscription = Object.assign(new Subscription(), subscription); + component.dso = mockItem; + component.subscription = Object.assign(new Subscription(), subscriptionMock); de = fixture.debugElement; fixture.detectChanges(); }); diff --git a/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.ts b/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts similarity index 66% rename from src/app/shared/subscriptions/components/subscription-view/subscription-view.component.ts rename to src/app/shared/subscriptions/subscription-view/subscription-view.component.ts index cb831534b6..4844be5b79 100644 --- a/src/app/shared/subscriptions/components/subscription-view/subscription-view.component.ts +++ b/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts @@ -1,13 +1,17 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { Subscription } from '../../models/subscription.model'; -import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; +import { Subscription } from '../models/subscription.model'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { take } from 'rxjs/operators'; - import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; -import { hasValue } from '../../../empty.util'; -import { ConfirmationModalComponent } from '../../../confirmation-modal/confirmation-modal.component'; -import { SubscriptionService } from '../../subscription.service'; + +import { hasValue } from '../../empty.util'; +import { ConfirmationModalComponent } from '../../confirmation-modal/confirmation-modal.component'; +import { SubscriptionService } from '../subscription.service'; +import { getCommunityModuleRoute } from '../../../community-page/community-page-routing-paths'; +import { getCollectionModuleRoute } from '../../../collection-page/collection-page-routing-paths'; +import { getItemModuleRoute } from '../../../item-page/item-page-routing-paths'; +import { SubscriptionModalComponent } from '../subscription-modal/subscription-modal.component'; @Component({ // eslint-disable-next-line @angular-eslint/component-selector @@ -47,16 +51,6 @@ export class SubscriptionViewComponent { private subscriptionService: SubscriptionService, ) { } - - /** - * Open modal - * - * @param content - */ - public openSubscription(content: any) { - this.modalRef = this.modalService.open(content); - } - /** * Return the prefix of the route to the dso object page ( e.g. "items") */ @@ -64,13 +58,13 @@ export class SubscriptionViewComponent { let routePrefix; switch (this.dso.type.toString()) { case 'community': - routePrefix = '/communities'; + routePrefix = getCommunityModuleRoute(); break; case 'collection': - routePrefix = '/collections'; + routePrefix = getCollectionModuleRoute(); break; case 'item': - routePrefix = '/items'; + routePrefix = getItemModuleRoute(); break; } return routePrefix; @@ -99,4 +93,14 @@ export class SubscriptionViewComponent { }); } } + + public openSubscriptionModal() { + this.modalRef = this.modalService.open(SubscriptionModalComponent); + this.modalRef.componentInstance.dso = this.dso; + this.modalRef.componentInstance.subscription = this.subscription; + this.modalRef.componentInstance.updateSubscription.pipe(take(1)).subscribe((subscription: Subscription) => { + this.subscription = subscription; + }) + + } } diff --git a/src/app/shared/subscriptions/subscription.service.ts b/src/app/shared/subscriptions/subscription.service.ts index 5010286e72..480598a15a 100644 --- a/src/app/shared/subscriptions/subscription.service.ts +++ b/src/app/shared/subscriptions/subscription.service.ts @@ -70,7 +70,7 @@ export class SubscriptionService extends IdentifiableDataService { * @param eperson The eperson to search for * @param uuid The uuid of the dsobjcet to search for */ - getSubscriptionByPersonDSO(eperson: string, uuid: string): Observable>> { + getSubscriptionsByPersonDSO(eperson: string, uuid: string): Observable>> { const optionsWithObject = Object.assign(new FindListOptions(), { searchParams: [ @@ -160,7 +160,14 @@ export class SubscriptionService extends IdentifiableDataService { ] }); - return this.searchData.searchBy(this.findByEpersonLinkPath, optionsWithObject, true, true, followLink('dSpaceObject'), followLink('ePerson')); + // return this.searchData.searchBy(this.findByEpersonLinkPath, optionsWithObject, true, true, followLink('dSpaceObject'), followLink('ePerson')); + + return this.getEndpoint().pipe( + map(href => `${href}/search/${this.findByEpersonLinkPath}`), + switchMap(href => this.findListByHref(href, optionsWithObject, true, true, followLink('dSpaceObject'), followLink('ePerson'))) + ); + + } } diff --git a/src/app/shared/subscriptions/subscriptions.module.ts b/src/app/shared/subscriptions/subscriptions.module.ts index 9b72493f67..122bf5ca8d 100644 --- a/src/app/shared/subscriptions/subscriptions.module.ts +++ b/src/app/shared/subscriptions/subscriptions.module.ts @@ -2,16 +2,16 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ReactiveFormsModule } from '@angular/forms'; -import { SubscriptionViewComponent } from './components/subscription-view/subscription-view.component'; -import { SubscriptionModalComponent } from './components/subscription-modal/subscription-modal.component'; -import { SubscriptionEditModalComponent } from './components/subscription-edit-modal/subscription-edit-modal.component'; +import { SubscriptionViewComponent } from './subscription-view/subscription-view.component'; +import { SubscriptionModalComponent } from './subscription-modal/subscription-modal.component'; import { TranslateModule } from '@ngx-translate/core'; import { RouterModule } from '@angular/router'; +import { SharedModule } from '../shared.module'; +import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; const COMPONENTS = [ SubscriptionViewComponent, - SubscriptionModalComponent, - SubscriptionEditModalComponent, + SubscriptionModalComponent ]; @NgModule({ @@ -20,12 +20,15 @@ const COMPONENTS = [ ], imports: [ CommonModule, + NgbModalModule, ReactiveFormsModule, TranslateModule, - RouterModule + RouterModule, + SharedModule ], exports: [ ...COMPONENTS ] }) -export class SubscriptionsModule { } +export class SubscriptionsModule { +} diff --git a/src/app/shared/testing/subscriptions-data.mock.ts b/src/app/shared/testing/subscriptions-data.mock.ts index 971f69786e..191fc46252 100644 --- a/src/app/shared/testing/subscriptions-data.mock.ts +++ b/src/app/shared/testing/subscriptions-data.mock.ts @@ -4435,7 +4435,7 @@ export const findByEPersonAndDsoResEmpty = { 'page': [] }; -export const subscription = { +export const subscriptionMock = { 'id': 21, 'type': 'subscription', 'subscriptionParameterList': [ @@ -4450,18 +4450,41 @@ export const subscription = { 'value': 'M' } ], - 'subscriptionType': 'content', + 'subscriptionType': 'test1', '_links': { 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/dSpaceObject' + 'href': 'https://dspace/server/api/core/subscriptions/21/dSpaceObject' }, 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/ePerson' + 'href': 'https://dspace/server/api/core/subscriptions/21/ePerson' }, 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21' + 'href': 'https://dspace/server/api/core/subscriptions/21' } } }; +export const subscriptionMock2 = { + 'id': 21, + 'type': 'subscription', + 'subscriptionParameterList': [ + { + 'id': 77, + 'name': 'frequency', + 'value': 'D' + }, + ], + 'subscriptionType': 'test2', + '_links': { + 'dSpaceObject': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/ePerson' + }, + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21' + } + } +}; diff --git a/src/app/subscriptions-page/subscriptions-page.component.html b/src/app/subscriptions-page/subscriptions-page.component.html index 9d1e6d504e..56e4980ca7 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.html +++ b/src/app/subscriptions-page/subscriptions-page.component.html @@ -4,13 +4,12 @@

{{'subscriptions.title' | translate}}

- + @@ -25,16 +24,20 @@ - +
-
+ {{ 'subscriptions.table.empty.message' | translate }} -
+
diff --git a/src/app/subscriptions-page/subscriptions-page.component.ts b/src/app/subscriptions-page/subscriptions-page.component.ts index 2951cf037c..a99daa9d68 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.ts +++ b/src/app/subscriptions-page/subscriptions-page.component.ts @@ -1,6 +1,8 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { BehaviorSubject, combineLatestWith, Observable, of, shareReplay, Subscription as rxSubscription } from 'rxjs'; -import { combineLatest, map, switchMap, take, tap } from 'rxjs/operators'; +import { Component, OnInit } from '@angular/core'; + +import { BehaviorSubject, combineLatestWith, Observable, shareReplay } from 'rxjs'; +import { map, switchMap, take, tap } from 'rxjs/operators'; + import { Subscription } from '../shared/subscriptions/models/subscription.model'; import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model'; import { SubscriptionService } from '../shared/subscriptions/subscription.service'; @@ -16,7 +18,7 @@ import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; templateUrl: './subscriptions-page.component.html', styleUrls: ['./subscriptions-page.component.scss'] }) -export class SubscriptionsPageComponent implements OnInit, OnDestroy { +export class SubscriptionsPageComponent implements OnInit { /** * The subscriptions to show on this page, as an Observable list. @@ -33,11 +35,6 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { currentPage: 1 }); - /** - * Subscription to be unsubscribed - */ - sub: rxSubscription; - /** * A boolean representing if is loading */ @@ -69,7 +66,11 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { this.ePersonId = ePersonId; }),*/ ); - const currentPagination$ = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + this.retrieveSubscriptions(); + } + + private retrieveSubscriptions() { + this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( tap(console.log), combineLatestWith(this.ePersonId$), tap(() => {this.loading$.next(true);}), @@ -77,10 +78,12 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { currentPage: currentPagination.currentPage, elementsPerPage: currentPagination.pageSize })), + getFirstSucceededRemoteDataPayload(), tap((x) => console.log('find', x)), // getFirstSucceededRemoteDataPayload(), ).subscribe({ next: (res: any) => { + console.log('next',res); this.subscriptions$.next(res); this.loading$.next(false); }, @@ -89,41 +92,11 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { } }); } - /** * When an action is made and the information is changed refresh the information */ refresh(): void { - /*this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( - take(1), - switchMap((findListOptions) => { - this.loading$.next(true); - return this.subscriptionService.findByEPerson(this.ePersonId,{ - currentPage: findListOptions.currentPage, - elementsPerPage: findListOptions.pageSize - }); - } - ) - ).subscribe({ - next: (res: any) => { - this.subscriptions$.next(res); - this.loading$.next(false); - }, - error: () => { - this.loading$.next(false); - } - });*/ - } - - /** - * Unsubscribe from pagination subscription - */ - ngOnDestroy(): void { - this.sub.unsubscribe(); - } - - obs(v) { - return of(v); + this.retrieveSubscriptions(); } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 2af33eb11d..ea0c87b858 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1450,6 +1450,13 @@ "confirmation-modal.delete-profile.confirm": "Delete", + "confirmation-modal.delete-subscription.header": "Delete Subscription", + + "confirmation-modal.delete-subscription.info": "Are you sure you want to delete subscription for \"{{ dsoName }}\"", + + "confirmation-modal.delete-subscription.cancel": "Cancel", + + "confirmation-modal.delete-subscription.confirm": "Delete", "error.bitstream": "Error fetching bitstream", @@ -4512,6 +4519,20 @@ "subscriptions.frequency.W": "Weekly", + + + "subscriptions.modal.create.success": "Subscribed to {{ type }} successfully.", + + "subscriptions.modal.delete.success": "Subscription deleted successfully", + + "subscriptions.modal.update.success": "Subscription to {{ type }} updated successfully", + + "subscriptions.modal.create.error": "An error occurs during the subscription creation", + + "subscriptions.modal.delete.error": "An error occurs during the subscription delete", + + "subscriptions.modal.update.error": "An error occurs during the subscription update", + "subscriptions.table.dso": "Subject", "subscriptions.table.subscription_type": "Subscription Type", From 88e191e961a9143e5e7ca3c435a062820342e48a Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 28 Dec 2022 11:28:18 +0100 Subject: [PATCH 027/173] [CST-7757] Fix lint errors --- ...page-subscription-button.component.spec.ts | 4 ++-- .../subscription-modal.component.spec.ts | 10 ++++----- .../subscription-modal.component.ts | 22 +++++++++---------- .../subscription-view.component.ts | 2 +- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.spec.ts b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.spec.ts index 7dcdf84235..726854778d 100644 --- a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.spec.ts +++ b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.spec.ts @@ -61,7 +61,7 @@ describe('DsoPageSubscriptionButtonComponent', () => { describe('when is authorized', () => { beforeEach(() => { - authorizationService.isAuthorized.and.returnValue(observableOf(true)) + authorizationService.isAuthorized.and.returnValue(observableOf(true)); fixture.detectChanges(); }); @@ -72,7 +72,7 @@ describe('DsoPageSubscriptionButtonComponent', () => { describe('when is not authorized', () => { beforeEach(() => { - authorizationService.isAuthorized.and.returnValue(observableOf(false)) + authorizationService.isAuthorized.and.returnValue(observableOf(false)); fixture.detectChanges(); }); diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts index c693f1e168..05fa1c4a77 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts @@ -106,7 +106,7 @@ describe('SubscriptionModalComponent', () => { fixture = TestBed.createComponent(SubscriptionModalComponent); component = fixture.componentInstance; component.dso = mockItem; - (component as any).subscriptionDefaultTypes = ['test1', 'test2'] + (component as any).subscriptionDefaultTypes = ['test1', 'test2']; de = fixture.debugElement; }); @@ -128,7 +128,7 @@ describe('SubscriptionModalComponent', () => { (component as any).frequencyDefaultValues.forEach((frequency) => { expect(component.subscriptionForm.get('test1').get('frequencies').get(frequency)).toBeTruthy(); expect(component.subscriptionForm.get('test2').get('frequencies').get(frequency)).toBeTruthy(); - }) + }); }); }); @@ -151,7 +151,7 @@ describe('SubscriptionModalComponent', () => { expect(component.subscriptionForm.get('test1').get('frequencies').get(frequency)).toBeTruthy(); expect(component.subscriptionForm.get('test2').get('frequencies').get(frequency)).toBeTruthy(); - }) + }); expect(component.subscriptionForm.get('test1').get('frequencies').get('D').value).toBeTrue(); expect(component.subscriptionForm.get('test1').get('frequencies').get('M').value).toBeTrue(); expect(component.subscriptionForm.get('test1').get('frequencies').get('W').value).toBeFalse(); @@ -169,7 +169,7 @@ describe('SubscriptionModalComponent', () => { component = fixture.componentInstance; component.dso = mockItem; component.subscription = subscriptionMock as any; - (component as any).subscriptionDefaultTypes = ['test1', 'test2'] + (component as any).subscriptionDefaultTypes = ['test1', 'test2']; de = fixture.debugElement; fixture.detectChanges(); }); @@ -184,7 +184,7 @@ describe('SubscriptionModalComponent', () => { expect(component.subscriptionForm.get('test1')).toBeTruthy(); (component as any).frequencyDefaultValues.forEach((frequency) => { expect(component.subscriptionForm.get('test1').get('frequencies').get(frequency)).toBeTruthy(); - }) + }); expect(component.subscriptionForm.get('test1').get('frequencies').get('D').value).toBeTrue(); expect(component.subscriptionForm.get('test1').get('frequencies').get('M').value).toBeTrue(); expect(component.subscriptionForm.get('test1').get('frequencies').get('W').value).toBeFalse(); diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts index 7fcba2d16b..6f1d1fd158 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts @@ -104,13 +104,13 @@ export class SubscriptionModalComponent implements OnInit { private initFormByAllSubscriptions(): void { this.subscriptionForm = new FormGroup({}); for (let t of this.subscriptionDefaultTypes) { - const formGroup = new FormGroup({}) + const formGroup = new FormGroup({}); formGroup.addControl('subscriptionId', this.formBuilder.control('')); formGroup.addControl('frequencies', this.formBuilder.group({})); for (let f of this.frequencyDefaultValues) { - (formGroup.controls['frequencies'] as FormGroup).addControl(f, this.formBuilder.control(false)); + (formGroup.controls.frequencies as FormGroup).addControl(f, this.formBuilder.control(false)); } - this.subscriptionForm.addControl(t, formGroup) + this.subscriptionForm.addControl(t, formGroup); } this.initFormDataBySubscriptions(); @@ -120,13 +120,13 @@ export class SubscriptionModalComponent implements OnInit { * If the subscription is passed start the form with the information of subscription */ initFormByGivenSubscription(): void { - const formGroup = new FormGroup({}) + const formGroup = new FormGroup({}); formGroup.addControl('subscriptionId', this.formBuilder.control(this.subscription.id)); formGroup.addControl('frequencies', this.formBuilder.group({})); (formGroup.get('frequencies') as FormGroup).addValidators(Validators.required); for (let f of this.frequencyDefaultValues) { const value = findIndex(this.subscription.subscriptionParameterList, ['value', f]) !== -1; - (formGroup.controls['frequencies'] as FormGroup).addControl(f, this.formBuilder.control(value)); + (formGroup.controls.frequencies as FormGroup).addControl(f, this.formBuilder.control(value)); } this.subscriptionForm = this.formBuilder.group({ @@ -149,9 +149,9 @@ export class SubscriptionModalComponent implements OnInit { const type = subscription.subscriptionType; const subscriptionGroup: FormGroup = this.subscriptionForm.get(type) as FormGroup; if (isNotEmpty(subscriptionGroup)) { - subscriptionGroup.controls['subscriptionId'].setValue(subscription.id); + subscriptionGroup.controls.subscriptionId.setValue(subscription.id); for (let parameter of subscription.subscriptionParameterList.filter((p) => p.name === 'frequency')) { - (subscriptionGroup.controls['frequencies'] as FormGroup).controls[parameter.value]?.setValue(true); + (subscriptionGroup.controls.frequencies as FormGroup).controls[parameter.value]?.setValue(true); } } } @@ -177,9 +177,9 @@ export class SubscriptionModalComponent implements OnInit { const subscriptionGroup: FormGroup = this.subscriptionForm.controls[subscriptionType] as FormGroup; if (subscriptionGroup.touched && subscriptionGroup.dirty) { const body = this.createBody( - subscriptionGroup.controls['subscriptionId'].value, + subscriptionGroup.controls.subscriptionId.value, subscriptionType, - subscriptionGroup.controls['frequencies'] as FormGroup + subscriptionGroup.controls.frequencies as FormGroup ); if (isNotEmpty(body.id)) { @@ -197,7 +197,7 @@ export class SubscriptionModalComponent implements OnInit { mergeMap((subscriptionBody) => { return this.subscriptionService.createSubscription(subscriptionBody, this.ePersonId, this.dso.uuid).pipe( getFirstCompletedRemoteData() - ) + ); }), tap((res: RemoteData) => { if (res.hasSucceeded) { @@ -215,7 +215,7 @@ export class SubscriptionModalComponent implements OnInit { mergeMap((subscriptionBody) => { return this.subscriptionService.updateSubscription(subscriptionBody, this.ePersonId, this.dso.uuid).pipe( getFirstCompletedRemoteData() - ) + ); }), tap((res: RemoteData) => { if (res.hasSucceeded) { diff --git a/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts b/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts index 4844be5b79..b8aa4d2375 100644 --- a/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts +++ b/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts @@ -100,7 +100,7 @@ export class SubscriptionViewComponent { this.modalRef.componentInstance.subscription = this.subscription; this.modalRef.componentInstance.updateSubscription.pipe(take(1)).subscribe((subscription: Subscription) => { this.subscription = subscription; - }) + }); } } From 88eefc6b79d40f5050c3c8bf5a085aebea37101f Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 28 Dec 2022 15:29:13 +0100 Subject: [PATCH 028/173] [CST-7757] fix tests --- .../subscription-modal.component.ts | 3 +- .../testing/notifications-service.stub.ts | 3 - .../shared/testing/subscriptions-data.mock.ts | 4578 +---------------- .../subscriptions-page.component.html | 15 +- .../subscriptions-page.component.spec.ts | 118 +- .../subscriptions-page.component.ts | 30 +- 6 files changed, 202 insertions(+), 4545 deletions(-) diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts index 6f1d1fd158..23faf33c54 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts @@ -2,11 +2,11 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; - import { BehaviorSubject, combineLatest, from, shareReplay } from 'rxjs'; import { map, mergeMap, take, tap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import findIndex from 'lodash/findIndex'; import { Subscription } from '../models/subscription.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; @@ -17,7 +17,6 @@ import { RemoteData } from '../../../core/data/remote-data'; import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { AuthService } from '../../../core/auth/auth.service'; import { isNotEmpty } from '../../empty.util'; -import { findIndex } from 'lodash'; @Component({ selector: 'ds-subscription-modal', diff --git a/src/app/shared/testing/notifications-service.stub.ts b/src/app/shared/testing/notifications-service.stub.ts index 829b65fc1d..154c5b4351 100644 --- a/src/app/shared/testing/notifications-service.stub.ts +++ b/src/app/shared/testing/notifications-service.stub.ts @@ -9,9 +9,6 @@ export class NotificationsServiceStub { remove = jasmine.createSpy('remove'); removeAll = jasmine.createSpy('removeAll'); - notificationWithAnchor() { - } - private getDefaultOptions(): NotificationOptions { return new NotificationOptions(); } diff --git a/src/app/shared/testing/subscriptions-data.mock.ts b/src/app/shared/testing/subscriptions-data.mock.ts index 191fc46252..8a4afc6d52 100644 --- a/src/app/shared/testing/subscriptions-data.mock.ts +++ b/src/app/shared/testing/subscriptions-data.mock.ts @@ -1,4467 +1,135 @@ -export const findAllSubscriptionRes = { - 'type': { - 'value': 'paginated-list' - }, - 'pageInfo': { - 'elementsPerPage': 10, - 'totalElements': 10, - 'totalPages': 1, - 'currentPage': 1 - }, - '_links': { - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions?page=0&size=10' - }, - 'search': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/search' - }, - 'page': [ - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/23' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/24' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/25' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/26' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/27' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/30' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/31' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/32' - } - ] - }, - 'page': [ - { - 'id': 21, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 77, - 'name': 'frequency', - 'value': 'D' - }, - { - 'id': 78, - 'name': 'frequency', - 'value': 'M' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'uuid': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'name': 'Bollini, Andrea', - 'handle': '123456789/43', - 'metadata': { - 'creativework.datePublished': [ - { - 'value': null, - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.orcid.access-token': [ - { - 'value': 'a456a1c3-1c9e-45f8-9e11-f273fa58de2e', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.authenticated': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.orcid.refresh-token': [ - { - 'value': '13a63f03-7c49-4dad-b39b-070f73cc7ac1', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.scope': [ - { - 'value': '/authenticate', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '/read-limited', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': '/activities/update', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - }, - { - 'value': '/person/update', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 3, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-fundings': [ - { - 'value': 'ALL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-mode': [ - { - 'value': 'MANUAL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-profile': [ - { - 'value': 'AFFILIATION', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'EDUCATION', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': 'BIOGRAPHICAL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - }, - { - 'value': 'IDENTIFIERS', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 3, - 'securityLevel': 0 - } - ], - 'cris.orcid.sync-projects': [ - { - 'value': 'ALL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-publications': [ - { - 'value': 'ALL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.webhook': [ - { - 'value': '2021-05-26T12:47:27.971367', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.object.owner': [ - { - 'value': 'Demo Site Administrator', - 'language': null, - 'authority': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'confidence': 600, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.policy.group': [ - { - 'value': 'Administrator', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.sourceId': [ - { - 'value': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.workspace.shared': [ - { - 'value': 'fg', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'crisrp.country': [ - { - 'value': 'IT', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'crisrp.education': [ - { - 'value': 'Università degli Studi di Milano Bicocca', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'Sapienza Università di Roma', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.education.end': [ - { - 'value': '2008', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '2003', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.education.role': [ - { - 'value': 'Master post experience 2nd level', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'Graduate Studies - Mathematics, Physics', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.education.start': [ - { - 'value': '2007', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '1998', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.name.translated': [ - { - 'value': null, - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0, - 'securityLevel': 0 - } - ], - 'crisrp.site.title': [ - { - 'value': 'LinkedIn', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'GitHub', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-09-14T09:36:02Z', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-09-14T09:36:02Z', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'I’m responsible for all the technological aspects of the company proposal, from the final solutions to tools, methodologies and technologies adopted for the production and support activities. Among my responsibilities, I define the infrastructure that best fits the project requirements. We provide support on premis on the customer data center and worldwide cloud providers. Our hosted solutions are powered by Amazon Web Services, our experience in their services allow us to offer best in class solutions, scalable and cost-optimized.\n\nI’m in charge of the planning, estimation and execution of the projects from the technical perspective, and of the preparation of technical annexes for national and international tenders.\n\nI lead the teams of software architects and developers, assuring the adoption of best practices and up-to-date technologies. I’m in charge of the scouting and integration of new technologies and products within our solutions with a particular focus on Open Source and Open Standards. I’m directly involved with open-source and domain communities to assure that our solutions remain aligned with the international trends both from the technical perspective and for the functional requirements.', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-09-14T09:36:02Z (GMT). No. of bitstreams: 0', - 'language': 'en', - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/43', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.title': [ - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Person', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'oairecerif.affiliation.endDate': [ - { - 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '2016', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': '2016', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.affiliation.role': [ - { - 'value': 'CTO', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'Head of Open Source & Open Standards Strategy', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': 'Head of Open Source & Open Standards Strategy', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.affiliation.startDate': [ - { - 'value': '2016', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '2012', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': '2012', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.identifier.url': [ - { - 'value': 'https://www.linkedin.com/in/andreabollini/', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'https://github.com/abollini', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'oairecerif.person.affiliation': [ - { - 'value': '4Science', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'CINECA', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': 'CINECA', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.person.gender': [ - { - 'value': 'm', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.affiliation.name': [ - { - 'value': '4Science', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.email': [ - { - 'value': 'andrea.bollini@4science.it', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.familyName': [ - { - 'value': 'Bollini', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.givenName': [ - { - 'value': 'Andrea', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.identifier.orcid': [ - { - 'value': '0000-0003-0864-8867', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.identifier.scopus-author-id': [ - { - 'value': '55484808800', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.jobTitle': [ - { - 'value': 'CTO', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.knowsLanguage': [ - { - 'value': 'en', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'en', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-08-23T10:44:57.768+00:00', - 'entityType': 'Person', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431' - } - } - } - }, - 'type': 'subscription' - }, - { - 'id': 22, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 138, - 'name': 'frequency', - 'value': 'D' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'uuid': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'name': 'Bollini, Andrea', - 'handle': '123456789/43', - 'metadata': { - 'creativework.datePublished': [ - { - 'value': null, - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.orcid.access-token': [ - { - 'value': 'a456a1c3-1c9e-45f8-9e11-f273fa58de2e', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.authenticated': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.orcid.refresh-token': [ - { - 'value': '13a63f03-7c49-4dad-b39b-070f73cc7ac1', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.scope': [ - { - 'value': '/authenticate', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '/read-limited', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': '/activities/update', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - }, - { - 'value': '/person/update', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 3, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-fundings': [ - { - 'value': 'ALL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-mode': [ - { - 'value': 'MANUAL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-profile': [ - { - 'value': 'AFFILIATION', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'EDUCATION', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': 'BIOGRAPHICAL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - }, - { - 'value': 'IDENTIFIERS', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 3, - 'securityLevel': 0 - } - ], - 'cris.orcid.sync-projects': [ - { - 'value': 'ALL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-publications': [ - { - 'value': 'ALL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.webhook': [ - { - 'value': '2021-05-26T12:47:27.971367', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.object.owner': [ - { - 'value': 'Demo Site Administrator', - 'language': null, - 'authority': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'confidence': 600, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.policy.group': [ - { - 'value': 'Administrator', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.sourceId': [ - { - 'value': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.workspace.shared': [ - { - 'value': 'fg', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'crisrp.country': [ - { - 'value': 'IT', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'crisrp.education': [ - { - 'value': 'Università degli Studi di Milano Bicocca', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'Sapienza Università di Roma', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.education.end': [ - { - 'value': '2008', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '2003', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.education.role': [ - { - 'value': 'Master post experience 2nd level', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'Graduate Studies - Mathematics, Physics', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.education.start': [ - { - 'value': '2007', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '1998', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.name.translated': [ - { - 'value': null, - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0, - 'securityLevel': 0 - } - ], - 'crisrp.site.title': [ - { - 'value': 'LinkedIn', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'GitHub', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-09-14T09:36:02Z', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-09-14T09:36:02Z', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'I’m responsible for all the technological aspects of the company proposal, from the final solutions to tools, methodologies and technologies adopted for the production and support activities. Among my responsibilities, I define the infrastructure that best fits the project requirements. We provide support on premis on the customer data center and worldwide cloud providers. Our hosted solutions are powered by Amazon Web Services, our experience in their services allow us to offer best in class solutions, scalable and cost-optimized.\n\nI’m in charge of the planning, estimation and execution of the projects from the technical perspective, and of the preparation of technical annexes for national and international tenders.\n\nI lead the teams of software architects and developers, assuring the adoption of best practices and up-to-date technologies. I’m in charge of the scouting and integration of new technologies and products within our solutions with a particular focus on Open Source and Open Standards. I’m directly involved with open-source and domain communities to assure that our solutions remain aligned with the international trends both from the technical perspective and for the functional requirements.', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-09-14T09:36:02Z (GMT). No. of bitstreams: 0', - 'language': 'en', - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/43', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.title': [ - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Person', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'oairecerif.affiliation.endDate': [ - { - 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '2016', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': '2016', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.affiliation.role': [ - { - 'value': 'CTO', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'Head of Open Source & Open Standards Strategy', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': 'Head of Open Source & Open Standards Strategy', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.affiliation.startDate': [ - { - 'value': '2016', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '2012', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': '2012', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.identifier.url': [ - { - 'value': 'https://www.linkedin.com/in/andreabollini/', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'https://github.com/abollini', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'oairecerif.person.affiliation': [ - { - 'value': '4Science', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'CINECA', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': 'CINECA', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.person.gender': [ - { - 'value': 'm', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.affiliation.name': [ - { - 'value': '4Science', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.email': [ - { - 'value': 'andrea.bollini@4science.it', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.familyName': [ - { - 'value': 'Bollini', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.givenName': [ - { - 'value': 'Andrea', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.identifier.orcid': [ - { - 'value': '0000-0003-0864-8867', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.identifier.scopus-author-id': [ - { - 'value': '55484808800', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.jobTitle': [ - { - 'value': 'CTO', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.knowsLanguage': [ - { - 'value': 'en', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'en', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-08-23T10:44:57.768+00:00', - 'entityType': 'Person', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431' - } - } - } - }, - 'type': 'subscription' - }, - { - 'id': 23, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 80, - 'name': 'frequency', - 'value': 'D' - }, - { - 'id': 81, - 'name': 'frequency', - 'value': 'M' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/23/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/23/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/23' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'uuid': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'name': 'Bollini, Andrea', - 'handle': '123456789/43', - 'metadata': { - 'creativework.datePublished': [ - { - 'value': null, - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.orcid.access-token': [ - { - 'value': 'a456a1c3-1c9e-45f8-9e11-f273fa58de2e', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.authenticated': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.orcid.refresh-token': [ - { - 'value': '13a63f03-7c49-4dad-b39b-070f73cc7ac1', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.scope': [ - { - 'value': '/authenticate', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '/read-limited', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': '/activities/update', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - }, - { - 'value': '/person/update', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 3, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-fundings': [ - { - 'value': 'ALL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-mode': [ - { - 'value': 'MANUAL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-profile': [ - { - 'value': 'AFFILIATION', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'EDUCATION', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': 'BIOGRAPHICAL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - }, - { - 'value': 'IDENTIFIERS', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 3, - 'securityLevel': 0 - } - ], - 'cris.orcid.sync-projects': [ - { - 'value': 'ALL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.sync-publications': [ - { - 'value': 'ALL', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.orcid.webhook': [ - { - 'value': '2021-05-26T12:47:27.971367', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.object.owner': [ - { - 'value': 'Demo Site Administrator', - 'language': null, - 'authority': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'confidence': 600, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.policy.group': [ - { - 'value': 'Administrator', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.sourceId': [ - { - 'value': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'cris.workspace.shared': [ - { - 'value': 'fg', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'crisrp.country': [ - { - 'value': 'IT', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'crisrp.education': [ - { - 'value': 'Università degli Studi di Milano Bicocca', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'Sapienza Università di Roma', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.education.end': [ - { - 'value': '2008', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '2003', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.education.role': [ - { - 'value': 'Master post experience 2nd level', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'Graduate Studies - Mathematics, Physics', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.education.start': [ - { - 'value': '2007', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '1998', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'crisrp.name.translated': [ - { - 'value': null, - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0, - 'securityLevel': 0 - } - ], - 'crisrp.site.title': [ - { - 'value': 'LinkedIn', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'GitHub', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-09-14T09:36:02Z', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-09-14T09:36:02Z', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'I’m responsible for all the technological aspects of the company proposal, from the final solutions to tools, methodologies and technologies adopted for the production and support activities. Among my responsibilities, I define the infrastructure that best fits the project requirements. We provide support on premis on the customer data center and worldwide cloud providers. Our hosted solutions are powered by Amazon Web Services, our experience in their services allow us to offer best in class solutions, scalable and cost-optimized.\n\nI’m in charge of the planning, estimation and execution of the projects from the technical perspective, and of the preparation of technical annexes for national and international tenders.\n\nI lead the teams of software architects and developers, assuring the adoption of best practices and up-to-date technologies. I’m in charge of the scouting and integration of new technologies and products within our solutions with a particular focus on Open Source and Open Standards. I’m directly involved with open-source and domain communities to assure that our solutions remain aligned with the international trends both from the technical perspective and for the functional requirements.', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-09-14T09:36:02Z (GMT). No. of bitstreams: 0', - 'language': 'en', - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/43', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dc.title': [ - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Person', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'oairecerif.affiliation.endDate': [ - { - 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '2016', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': '2016', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.affiliation.role': [ - { - 'value': 'CTO', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'Head of Open Source & Open Standards Strategy', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': 'Head of Open Source & Open Standards Strategy', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.affiliation.startDate': [ - { - 'value': '2016', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': '2012', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': '2012', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.identifier.url': [ - { - 'value': 'https://www.linkedin.com/in/andreabollini/', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'https://github.com/abollini', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ], - 'oairecerif.person.affiliation': [ - { - 'value': '4Science', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'CINECA', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - }, - { - 'value': 'CINECA', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 2, - 'securityLevel': 0 - } - ], - 'oairecerif.person.gender': [ - { - 'value': 'm', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.affiliation.name': [ - { - 'value': '4Science', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.email': [ - { - 'value': 'andrea.bollini@4science.it', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.familyName': [ - { - 'value': 'Bollini', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.givenName': [ - { - 'value': 'Andrea', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.identifier.orcid': [ - { - 'value': '0000-0003-0864-8867', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.identifier.scopus-author-id': [ - { - 'value': '55484808800', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.jobTitle': [ - { - 'value': 'CTO', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - } - ], - 'person.knowsLanguage': [ - { - 'value': 'en', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 0, - 'securityLevel': 0 - }, - { - 'value': 'en', - 'language': null, - 'authority': null, - 'confidence': 0, - 'place': 1, - 'securityLevel': 0 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-08-23T10:44:57.768+00:00', - 'entityType': 'Person', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/092b59e8-8159-4e70-98b5-93ec60bd3431' - } - } - } - }, - 'type': 'subscription' - }, - { - 'id': 24, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 82, - 'name': 'frequency', - 'value': 'D' - }, - { - 'id': 83, - 'name': 'frequency', - 'value': 'M' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/24/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/24/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/24' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': '90f3b9ce-db65-479c-90d7-8c794abf942c', - 'uuid': '90f3b9ce-db65-479c-90d7-8c794abf942c', - 'name': 'DSpace-CRIS', - 'handle': '123456789/34', - 'metadata': { - 'crispj.coinvestigators': [ - { - 'value': 'Mornati, Susanna', - 'language': null, - 'authority': '1325093a-1ef4-4d87-920d-02ce544fea00', - 'confidence': 600, - 'place': 0 - }, - { - 'value': 'Lombardi, Corrado', - 'language': null, - 'authority': 'b5ad6864-012d-4989-8e0d-4acfa1156fd9', - 'confidence': 600, - 'place': 1 - } - ], - 'crispj.coordinator': [ - { - 'value': '4Science', - 'language': null, - 'authority': 'a14ba215-c0f0-4b74-b21a-06359bfabd45', - 'confidence': 600, - 'place': 0 - } - ], - 'crispj.investigator': [ - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'confidence': 600, - 'place': 0 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-09-05T16:33:33Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-09-05T16:33:33Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'DSpace-CRIS is the first free open-source extension of DSpace for the Research Data and Information Management ever developed. Differently from other (commercial) CRIS/RIMS (star), DSpace-CRIS has the institutional repository as its core component, providing high visibility on the web to all the collected information and objects. DSpace-CRIS broadens DSpace functionality and expands its data model while remaining aligned with its code base. \n\nDSpace-CRIS adopts/is compliant with international standards and practices to facilitate interoperability and data transfer:\n\n- ORCID API v2 (complete compliance including pull/push of info for profiles, publications, projects). ORCID API v3 compliance is being released.\n\n- Signposting and ResourceSync (which implement COAR NGR Recommended Behaviors)\n\n- OpenAIRE Guidelines for Literature Repository Managers v4, for Data Archives, for CRIS Managers v1.1.1 (based on CERIF, released Nov. 2019)\n\n- PlanS (by Coalition S)\n\n- FAIR principles\n\nThe main characteristic of DSpace-CRIS is its flexible data model, which allows you to collect and manage research data and information typical of a CRIS system, to define entities and attributes with their reciprocal links. If you would just want to enhance the management of authors, provide name variants and IDs such as the ORCiD, exploit the varied ecosystem of persistent identifiers, link researchers to projects, awards, etc., DSpace-CRIS flexible data model can support this without aggravating the management burden of a normal institutional repository, while giving a great added value. Besides, it has useful features such as the collaboration network graph, aggregated (by researcher, by department) bibliometrics and statistics with graphic reporting, CVs and bibliographies, integration with ORCiD API v.3 and much more, you can explore them vie the menu items here on the left. \n\nIts flexibility allows to configure different data models and metadata schemas, providing the community with new and creative uses of DSpace, such as DSpace-GLAM (Galleries, Libraries, Archives and Museums) for the Cultural Heritage.', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-09-05T16:33:33Z (GMT). No. of bitstreams: 1\nlayers.png: 295537 bytes, checksum: 001e24b05d2c5d6cd76e88580f10cb9b (MD5)', - 'language': 'en', - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/34', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.subject': [ - { - 'value': 'cerif', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': 'datamanagement', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - }, - { - 'value': 'opensource', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 2 - } - ], - 'dc.title': [ - { - 'value': 'DSpace-CRIS', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.type': [ - { - 'value': 'applied research', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Project', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.acronym': [ - { - 'value': 'DSC', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.oamandate': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.oamandate.url': [ - { - 'value': 'https://www.4science.it/en/open-source/', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.project.startDate': [ - { - 'value': '2009-04', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.project.status': [ - { - 'value': 'ongoing', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-08-02T14:19:24.927+00:00', - 'entityType': 'Project', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c' - } - } - } - }, - 'type': 'subscription' - }, - { - 'id': 25, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 84, - 'name': 'frequency', - 'value': 'D' - }, - { - 'id': 85, - 'name': 'frequency', - 'value': 'M' - }, - { - 'id': 86, - 'name': 'frequency', - 'value': 'W' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/25/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/25/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/25' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': '90f3b9ce-db65-479c-90d7-8c794abf942c', - 'uuid': '90f3b9ce-db65-479c-90d7-8c794abf942c', - 'name': 'DSpace-CRIS', - 'handle': '123456789/34', - 'metadata': { - 'crispj.coinvestigators': [ - { - 'value': 'Mornati, Susanna', - 'language': null, - 'authority': '1325093a-1ef4-4d87-920d-02ce544fea00', - 'confidence': 600, - 'place': 0 - }, - { - 'value': 'Lombardi, Corrado', - 'language': null, - 'authority': 'b5ad6864-012d-4989-8e0d-4acfa1156fd9', - 'confidence': 600, - 'place': 1 - } - ], - 'crispj.coordinator': [ - { - 'value': '4Science', - 'language': null, - 'authority': 'a14ba215-c0f0-4b74-b21a-06359bfabd45', - 'confidence': 600, - 'place': 0 - } - ], - 'crispj.investigator': [ - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'confidence': 600, - 'place': 0 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-09-05T16:33:33Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-09-05T16:33:33Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'DSpace-CRIS is the first free open-source extension of DSpace for the Research Data and Information Management ever developed. Differently from other (commercial) CRIS/RIMS (star), DSpace-CRIS has the institutional repository as its core component, providing high visibility on the web to all the collected information and objects. DSpace-CRIS broadens DSpace functionality and expands its data model while remaining aligned with its code base. \n\nDSpace-CRIS adopts/is compliant with international standards and practices to facilitate interoperability and data transfer:\n\n- ORCID API v2 (complete compliance including pull/push of info for profiles, publications, projects). ORCID API v3 compliance is being released.\n\n- Signposting and ResourceSync (which implement COAR NGR Recommended Behaviors)\n\n- OpenAIRE Guidelines for Literature Repository Managers v4, for Data Archives, for CRIS Managers v1.1.1 (based on CERIF, released Nov. 2019)\n\n- PlanS (by Coalition S)\n\n- FAIR principles\n\nThe main characteristic of DSpace-CRIS is its flexible data model, which allows you to collect and manage research data and information typical of a CRIS system, to define entities and attributes with their reciprocal links. If you would just want to enhance the management of authors, provide name variants and IDs such as the ORCiD, exploit the varied ecosystem of persistent identifiers, link researchers to projects, awards, etc., DSpace-CRIS flexible data model can support this without aggravating the management burden of a normal institutional repository, while giving a great added value. Besides, it has useful features such as the collaboration network graph, aggregated (by researcher, by department) bibliometrics and statistics with graphic reporting, CVs and bibliographies, integration with ORCiD API v.3 and much more, you can explore them vie the menu items here on the left. \n\nIts flexibility allows to configure different data models and metadata schemas, providing the community with new and creative uses of DSpace, such as DSpace-GLAM (Galleries, Libraries, Archives and Museums) for the Cultural Heritage.', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-09-05T16:33:33Z (GMT). No. of bitstreams: 1\nlayers.png: 295537 bytes, checksum: 001e24b05d2c5d6cd76e88580f10cb9b (MD5)', - 'language': 'en', - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/34', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.subject': [ - { - 'value': 'cerif', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': 'datamanagement', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - }, - { - 'value': 'opensource', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 2 - } - ], - 'dc.title': [ - { - 'value': 'DSpace-CRIS', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.type': [ - { - 'value': 'applied research', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Project', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.acronym': [ - { - 'value': 'DSC', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.oamandate': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.oamandate.url': [ - { - 'value': 'https://www.4science.it/en/open-source/', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.project.startDate': [ - { - 'value': '2009-04', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.project.status': [ - { - 'value': 'ongoing', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-08-02T14:19:24.927+00:00', - 'entityType': 'Project', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c' - } - } - } - }, - 'type': 'subscription' - }, - { - 'id': 26, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 87, - 'name': 'frequency', - 'value': 'D' - }, - { - 'id': 88, - 'name': 'frequency', - 'value': 'M' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/26/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/26/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/26' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': '90f3b9ce-db65-479c-90d7-8c794abf942c', - 'uuid': '90f3b9ce-db65-479c-90d7-8c794abf942c', - 'name': 'DSpace-CRIS', - 'handle': '123456789/34', - 'metadata': { - 'crispj.coinvestigators': [ - { - 'value': 'Mornati, Susanna', - 'language': null, - 'authority': '1325093a-1ef4-4d87-920d-02ce544fea00', - 'confidence': 600, - 'place': 0 - }, - { - 'value': 'Lombardi, Corrado', - 'language': null, - 'authority': 'b5ad6864-012d-4989-8e0d-4acfa1156fd9', - 'confidence': 600, - 'place': 1 - } - ], - 'crispj.coordinator': [ - { - 'value': '4Science', - 'language': null, - 'authority': 'a14ba215-c0f0-4b74-b21a-06359bfabd45', - 'confidence': 600, - 'place': 0 - } - ], - 'crispj.investigator': [ - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'confidence': 600, - 'place': 0 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-09-05T16:33:33Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-09-05T16:33:33Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'DSpace-CRIS is the first free open-source extension of DSpace for the Research Data and Information Management ever developed. Differently from other (commercial) CRIS/RIMS (star), DSpace-CRIS has the institutional repository as its core component, providing high visibility on the web to all the collected information and objects. DSpace-CRIS broadens DSpace functionality and expands its data model while remaining aligned with its code base. \n\nDSpace-CRIS adopts/is compliant with international standards and practices to facilitate interoperability and data transfer:\n\n- ORCID API v2 (complete compliance including pull/push of info for profiles, publications, projects). ORCID API v3 compliance is being released.\n\n- Signposting and ResourceSync (which implement COAR NGR Recommended Behaviors)\n\n- OpenAIRE Guidelines for Literature Repository Managers v4, for Data Archives, for CRIS Managers v1.1.1 (based on CERIF, released Nov. 2019)\n\n- PlanS (by Coalition S)\n\n- FAIR principles\n\nThe main characteristic of DSpace-CRIS is its flexible data model, which allows you to collect and manage research data and information typical of a CRIS system, to define entities and attributes with their reciprocal links. If you would just want to enhance the management of authors, provide name variants and IDs such as the ORCiD, exploit the varied ecosystem of persistent identifiers, link researchers to projects, awards, etc., DSpace-CRIS flexible data model can support this without aggravating the management burden of a normal institutional repository, while giving a great added value. Besides, it has useful features such as the collaboration network graph, aggregated (by researcher, by department) bibliometrics and statistics with graphic reporting, CVs and bibliographies, integration with ORCiD API v.3 and much more, you can explore them vie the menu items here on the left. \n\nIts flexibility allows to configure different data models and metadata schemas, providing the community with new and creative uses of DSpace, such as DSpace-GLAM (Galleries, Libraries, Archives and Museums) for the Cultural Heritage.', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-09-05T16:33:33Z (GMT). No. of bitstreams: 1\nlayers.png: 295537 bytes, checksum: 001e24b05d2c5d6cd76e88580f10cb9b (MD5)', - 'language': 'en', - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/34', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.subject': [ - { - 'value': 'cerif', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': 'datamanagement', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - }, - { - 'value': 'opensource', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 2 - } - ], - 'dc.title': [ - { - 'value': 'DSpace-CRIS', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.type': [ - { - 'value': 'applied research', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Project', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.acronym': [ - { - 'value': 'DSC', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.oamandate': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.oamandate.url': [ - { - 'value': 'https://www.4science.it/en/open-source/', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.project.startDate': [ - { - 'value': '2009-04', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.project.status': [ - { - 'value': 'ongoing', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-08-02T14:19:24.927+00:00', - 'entityType': 'Project', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c' - } - } - } - }, - 'type': 'subscription' - }, - { - 'id': 27, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 89, - 'name': 'frequency', - 'value': 'D' - }, - { - 'id': 90, - 'name': 'frequency', - 'value': 'M' - }, - { - 'id': 91, - 'name': 'frequency', - 'value': 'W' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/27/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/27/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/27' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': '90f3b9ce-db65-479c-90d7-8c794abf942c', - 'uuid': '90f3b9ce-db65-479c-90d7-8c794abf942c', - 'name': 'DSpace-CRIS', - 'handle': '123456789/34', - 'metadata': { - 'crispj.coinvestigators': [ - { - 'value': 'Mornati, Susanna', - 'language': null, - 'authority': '1325093a-1ef4-4d87-920d-02ce544fea00', - 'confidence': 600, - 'place': 0 - }, - { - 'value': 'Lombardi, Corrado', - 'language': null, - 'authority': 'b5ad6864-012d-4989-8e0d-4acfa1156fd9', - 'confidence': 600, - 'place': 1 - } - ], - 'crispj.coordinator': [ - { - 'value': '4Science', - 'language': null, - 'authority': 'a14ba215-c0f0-4b74-b21a-06359bfabd45', - 'confidence': 600, - 'place': 0 - } - ], - 'crispj.investigator': [ - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'confidence': 600, - 'place': 0 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-09-05T16:33:33Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-09-05T16:33:33Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'DSpace-CRIS is the first free open-source extension of DSpace for the Research Data and Information Management ever developed. Differently from other (commercial) CRIS/RIMS (star), DSpace-CRIS has the institutional repository as its core component, providing high visibility on the web to all the collected information and objects. DSpace-CRIS broadens DSpace functionality and expands its data model while remaining aligned with its code base. \n\nDSpace-CRIS adopts/is compliant with international standards and practices to facilitate interoperability and data transfer:\n\n- ORCID API v2 (complete compliance including pull/push of info for profiles, publications, projects). ORCID API v3 compliance is being released.\n\n- Signposting and ResourceSync (which implement COAR NGR Recommended Behaviors)\n\n- OpenAIRE Guidelines for Literature Repository Managers v4, for Data Archives, for CRIS Managers v1.1.1 (based on CERIF, released Nov. 2019)\n\n- PlanS (by Coalition S)\n\n- FAIR principles\n\nThe main characteristic of DSpace-CRIS is its flexible data model, which allows you to collect and manage research data and information typical of a CRIS system, to define entities and attributes with their reciprocal links. If you would just want to enhance the management of authors, provide name variants and IDs such as the ORCiD, exploit the varied ecosystem of persistent identifiers, link researchers to projects, awards, etc., DSpace-CRIS flexible data model can support this without aggravating the management burden of a normal institutional repository, while giving a great added value. Besides, it has useful features such as the collaboration network graph, aggregated (by researcher, by department) bibliometrics and statistics with graphic reporting, CVs and bibliographies, integration with ORCiD API v.3 and much more, you can explore them vie the menu items here on the left. \n\nIts flexibility allows to configure different data models and metadata schemas, providing the community with new and creative uses of DSpace, such as DSpace-GLAM (Galleries, Libraries, Archives and Museums) for the Cultural Heritage.', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-09-05T16:33:33Z (GMT). No. of bitstreams: 1\nlayers.png: 295537 bytes, checksum: 001e24b05d2c5d6cd76e88580f10cb9b (MD5)', - 'language': 'en', - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/34', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.subject': [ - { - 'value': 'cerif', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': 'datamanagement', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - }, - { - 'value': 'opensource', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 2 - } - ], - 'dc.title': [ - { - 'value': 'DSpace-CRIS', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.type': [ - { - 'value': 'applied research', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Project', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.acronym': [ - { - 'value': 'DSC', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.oamandate': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.oamandate.url': [ - { - 'value': 'https://www.4science.it/en/open-source/', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.project.startDate': [ - { - 'value': '2009-04', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.project.status': [ - { - 'value': 'ongoing', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-08-02T14:19:24.927+00:00', - 'entityType': 'Project', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/90f3b9ce-db65-479c-90d7-8c794abf942c' - } - } - } - }, - 'type': 'subscription' - }, - { - 'id': 30, - 'subscriptionType': 'statistics', - 'subscriptionParameterList': [ - { - 'id': 96, - 'name': 'frequency', - 'value': 'M' - }, - { - 'id': 97, - 'name': 'frequency', - 'value': 'D' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/30/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/30/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/30' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', - 'uuid': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', - 'name': 'DSpace administration issues: the community admin patch', - 'handle': '123456789/107', - 'metadata': { - 'dc.contributor.author': [ - { - 'value': 'Donohue, Tim', - 'language': null, - 'authority': 'fcae3ff0-6a04-4385-8cae-04a38bbe4969', - 'confidence': 600, - 'place': 0 - }, - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'confidence': 600, - 'place': 1 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-12-06T22:35:52Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-12-06T22:35:52Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.issued': [ - { - 'value': '2005', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'A large or medium repository, but also a small repository in some special cases, needs to allow a more decentralized management of administrative activities as: creation of new communities, creation of new collections, management of submitter and workflow groups, editing of published items, access policies and so on. Until now, DSpace allows only a partial decentralization of this functionalities thought into the role of COLLECTION ADMIN. After highlighting these needs, we will introduce the new role of COMMUNITY ADMIN and the changes made to the COLLECTION ADMIN role by our patch so to fix most of the previous needs. We will talk about the \'long history\' of this patch, made for the first time by Andrea against the 1.2 series and next kept updated, bug free and XMLUI aware by Tim from the 1.4 series. This \'pass the buck\', from Andrea to Tim and again together with some other people, shows how useful is for anyone to share results, experiences and customizations with the community so to get them back improved, reducing the cost of locale maintenance, debug and bug fix. We will close the presentation with the good news of the inclusion of this patch in the DSpace codebase for both XMLUI and JSPUI interfaces and also giving some notices about possible improvements in the next future. DSpace User Group Meeting 2009', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-12-06T22:35:52Z (GMT). No. of bitstreams: 0\n Previous issue date: 2005', - 'language': 'en', - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.other': [ - { - 'value': 'od______1149::b43204759808a65356e6cc3ba285d29f', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/107', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.language.iso': [ - { - 'value': 'en', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.source': [ - { - 'value': 'Göteborgs universitets publikationer - e-publicering och e-arkiv', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.subject': [ - { - 'value': 'dspace', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': 'open source', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - } - ], - 'dc.title': [ - { - 'value': 'DSpace administration issues: the community admin patch', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.type': [ - { - 'value': 'Controlled Vocabulary for Resource Type Genres::text::conference object', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Publication', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.author.affiliation': [ - { - 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-05-31T21:41:41.737+00:00', - 'entityType': 'Publication', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40' - } - } - } - }, - 'type': 'subscription' - }, - { - 'id': 31, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 98, - 'name': 'frequency', - 'value': 'D' - }, - { - 'id': 99, - 'name': 'frequency', - 'value': 'M' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/31/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/31/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/31' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', - 'uuid': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', - 'name': 'DSpace administration issues: the community admin patch', - 'handle': '123456789/107', - 'metadata': { - 'dc.contributor.author': [ - { - 'value': 'Donohue, Tim', - 'language': null, - 'authority': 'fcae3ff0-6a04-4385-8cae-04a38bbe4969', - 'confidence': 600, - 'place': 0 - }, - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'confidence': 600, - 'place': 1 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-12-06T22:35:52Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-12-06T22:35:52Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.issued': [ - { - 'value': '2005', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'A large or medium repository, but also a small repository in some special cases, needs to allow a more decentralized management of administrative activities as: creation of new communities, creation of new collections, management of submitter and workflow groups, editing of published items, access policies and so on. Until now, DSpace allows only a partial decentralization of this functionalities thought into the role of COLLECTION ADMIN. After highlighting these needs, we will introduce the new role of COMMUNITY ADMIN and the changes made to the COLLECTION ADMIN role by our patch so to fix most of the previous needs. We will talk about the \'long history\' of this patch, made for the first time by Andrea against the 1.2 series and next kept updated, bug free and XMLUI aware by Tim from the 1.4 series. This \'pass the buck\', from Andrea to Tim and again together with some other people, shows how useful is for anyone to share results, experiences and customizations with the community so to get them back improved, reducing the cost of locale maintenance, debug and bug fix. We will close the presentation with the good news of the inclusion of this patch in the DSpace codebase for both XMLUI and JSPUI interfaces and also giving some notices about possible improvements in the next future. DSpace User Group Meeting 2009', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-12-06T22:35:52Z (GMT). No. of bitstreams: 0\n Previous issue date: 2005', - 'language': 'en', - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.other': [ - { - 'value': 'od______1149::b43204759808a65356e6cc3ba285d29f', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/107', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.language.iso': [ - { - 'value': 'en', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.source': [ - { - 'value': 'Göteborgs universitets publikationer - e-publicering och e-arkiv', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.subject': [ - { - 'value': 'dspace', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': 'open source', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - } - ], - 'dc.title': [ - { - 'value': 'DSpace administration issues: the community admin patch', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.type': [ - { - 'value': 'Controlled Vocabulary for Resource Type Genres::text::conference object', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Publication', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.author.affiliation': [ - { - 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-05-31T21:41:41.737+00:00', - 'entityType': 'Publication', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40' - } - } - } - }, - 'type': 'subscription' - }, - { - 'id': 32, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 100, - 'name': 'frequency', - 'value': 'W' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/32/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/32/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/32' - } - }, - '_embedded': { - 'ePerson': { - 'id': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'uuid': '335647b6-8a52-4ecb-a8c1-7ebabb199bda', - 'name': 'dspacedemo+admin@gmail.com', - 'handle': null, - 'metadata': { - 'dspace.agreements.cookies': [ - { - 'value': '{\'authentication\':true,\'preferences\':true,\'acknowledgement\':true,\'google-analytics\':true}', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.agreements.end-user': [ - { - 'value': 'true', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.firstname': [ - { - 'value': 'Demo', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'eperson.lastname': [ - { - 'value': 'Site Administrator', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ] - }, - 'netid': null, - 'lastActive': '2021-09-01T12:06:19.000+00:00', - 'canLogIn': true, - 'email': 'dspacedemo+admin@gmail.com', - 'requireCertificate': false, - 'selfRegistered': false, - 'type': 'eperson', - '_links': { - 'groups': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda/groups' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/eperson/epersons/335647b6-8a52-4ecb-a8c1-7ebabb199bda' - } - } - }, - 'dSpaceObject': { - 'id': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', - 'uuid': 'b2140cd5-bfdf-4b5b-83fb-8bab4c899b40', - 'name': 'DSpace administration issues: the community admin patch', - 'handle': '123456789/107', - 'metadata': { - 'dc.contributor.author': [ - { - 'value': 'Donohue, Tim', - 'language': null, - 'authority': 'fcae3ff0-6a04-4385-8cae-04a38bbe4969', - 'confidence': 600, - 'place': 0 - }, - { - 'value': 'Bollini, Andrea', - 'language': null, - 'authority': '092b59e8-8159-4e70-98b5-93ec60bd3431', - 'confidence': 600, - 'place': 1 - } - ], - 'dc.date.accessioned': [ - { - 'value': '2020-12-06T22:35:52Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.available': [ - { - 'value': '2020-12-06T22:35:52Z', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.date.issued': [ - { - 'value': '2005', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.abstract': [ - { - 'value': 'A large or medium repository, but also a small repository in some special cases, needs to allow a more decentralized management of administrative activities as: creation of new communities, creation of new collections, management of submitter and workflow groups, editing of published items, access policies and so on. Until now, DSpace allows only a partial decentralization of this functionalities thought into the role of COLLECTION ADMIN. After highlighting these needs, we will introduce the new role of COMMUNITY ADMIN and the changes made to the COLLECTION ADMIN role by our patch so to fix most of the previous needs. We will talk about the \'long history\' of this patch, made for the first time by Andrea against the 1.2 series and next kept updated, bug free and XMLUI aware by Tim from the 1.4 series. This \'pass the buck\', from Andrea to Tim and again together with some other people, shows how useful is for anyone to share results, experiences and customizations with the community so to get them back improved, reducing the cost of locale maintenance, debug and bug fix. We will close the presentation with the good news of the inclusion of this patch in the DSpace codebase for both XMLUI and JSPUI interfaces and also giving some notices about possible improvements in the next future. DSpace User Group Meeting 2009', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.description.provenance': [ - { - 'value': 'Made available in DSpace on 2020-12-06T22:35:52Z (GMT). No. of bitstreams: 0\n Previous issue date: 2005', - 'language': 'en', - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.other': [ - { - 'value': 'od______1149::b43204759808a65356e6cc3ba285d29f', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.identifier.uri': [ - { - 'value': 'https://dspacecris7.4science.cloud/handle/123456789/107', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.language.iso': [ - { - 'value': 'en', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.source': [ - { - 'value': 'Göteborgs universitets publikationer - e-publicering och e-arkiv', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.subject': [ - { - 'value': 'dspace', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': 'open source', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - } - ], - 'dc.title': [ - { - 'value': 'DSpace administration issues: the community admin patch', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dc.type': [ - { - 'value': 'Controlled Vocabulary for Resource Type Genres::text::conference object', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'dspace.entity.type': [ - { - 'value': 'Publication', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - } - ], - 'oairecerif.author.affiliation': [ - { - 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 0 - }, - { - 'value': '#PLACEHOLDER_PARENT_METADATA_VALUE#', - 'language': null, - 'authority': null, - 'confidence': -1, - 'place': 1 - } - ] - }, - 'inArchive': true, - 'discoverable': true, - 'withdrawn': false, - 'lastModified': '2021-05-31T21:41:41.737+00:00', - 'entityType': 'Publication', - 'type': 'item', - '_links': { - 'bundles': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/bundles' - }, - 'mappedCollections': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/mappedCollections' - }, - 'owningCollection': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/owningCollection' - }, - 'relationships': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/relationships' - }, - 'version': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/version' - }, - 'templateItemOf': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/templateItemOf' - }, - 'metrics': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/metrics' - }, - 'thumbnail': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40/thumbnail' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/items/b2140cd5-bfdf-4b5b-83fb-8bab4c899b40' - } - } - } - }, - 'type': 'subscription' - } +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; +import { Item } from '../../core/shared/item.model'; +import { ITEM_TYPE } from '../../core/shared/item-relationships/item-type.resource-type'; + +export const mockSubscriptionEperson = Object.assign(new EPerson(), { + 'id': 'fake-eperson-id', + 'uuid': 'fake-eperson-id', + 'handle': null, + 'metadata': { + 'eperson.firstname': [ + { + 'value': 'user', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } + ], + 'eperson.lastname': [ + { + 'value': 'testr', + 'language': null, + 'authority': null, + 'confidence': -1, + 'place': 0 + } ] -}; - -export const findByEPersonAndDsoRes = { - 'type': { - 'value': 'paginated-list' + }, + 'netid': null, + 'lastActive': '2021-09-01T12:06:19.000+00:00', + 'canLogIn': true, + 'email': 'user@test.com', + 'requireCertificate': false, + 'selfRegistered': false, + 'type': 'eperson', + '_links': { + 'groups': { + 'href': 'https://dspace.org/server/api/eperson/epersons/fake-eperson-id/groups' }, - 'pageInfo': { - 'elementsPerPage': 20, - 'totalElements': 2, - 'totalPages': 1, - 'currentPage': 1 - }, - '_links': { - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/search/findByEPersonAndDso?dspace_object_id=092b59e8-8159-4e70-98b5-93ec60bd3431&eperson_id=335647b6-8a52-4ecb-a8c1-7ebabb199bda' - }, - 'page': [ - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48' - } - ] - }, - 'page': [ - { - 'id': 22, - 'subscriptionType': 'content', - 'subscriptionParameterList': [ - { - 'id': 161, - 'name': 'frequency', - 'value': 'M' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' - } - }, - 'type': 'subscription' - }, - { - 'id': 48, - 'subscriptionType': 'statistics', - 'subscriptionParameterList': [ - { - 'id': 159, - 'name': 'frequency', - 'value': 'D' - }, - { - 'id': 160, - 'name': 'frequency', - 'value': 'M' - } - ], - '_links': { - 'dSpaceObject': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48/ePerson' - }, - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48' - } - }, - 'type': 'subscription' - } - ] -}; + 'self': { + 'href': 'https://dspace.org/server/api/eperson/epersons/fake-eperson-id' + } + } +}); +export const mockSubscriptionDSO = Object.assign(new Item(), + { + id: 'fake-item-id', + uuid: 'fake-item-id', + metadata: { + 'dc.title': [{ value: 'test item subscription' }] + }, + type: ITEM_TYPE, + _links: { + self: { + href: 'https://dspace.org/server/api/core/items/fake-item-id' + } + } + } +); +export const mockSubscriptionDSO2 = Object.assign(new Item(), + { + id: 'fake-item-id2', + uuid: 'fake-item-id2', + metadata: { + 'dc.title': [{ value: 'test item subscription 2' }] + }, + type: ITEM_TYPE, + _links: { + self: { + href: 'https://dspace.org/server/api/core/items/fake-item-id2' + } + } + } +); export const findByEPersonAndDsoResEmpty = { - 'type': { - 'value': 'paginated-list' + 'type': { + 'value': 'paginated-list' + }, + 'pageInfo': { + 'elementsPerPage': 0, + 'totalElements': 0, + 'totalPages': 1, + 'currentPage': 1 + }, + '_links': { + 'self': { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/search/findByEPersonAndDso?dspace_object_id=092b59e8-8159-4e70-98b5-93ec60bd3431&eperson_id=335647b6-8a52-4ecb-a8c1-7ebabb199bda' }, - 'pageInfo': { - 'elementsPerPage': 0, - 'totalElements': 0, - 'totalPages': 1, - 'currentPage': 1 - }, - '_links': { - 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/search/findByEPersonAndDso?dspace_object_id=092b59e8-8159-4e70-98b5-93ec60bd3431&eperson_id=335647b6-8a52-4ecb-a8c1-7ebabb199bda' - }, - 'page': [ - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' - }, - { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48' - } - ] - }, - 'page': [] + 'page': [ + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/22' + }, + { + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/48' + } + ] + }, + 'page': [] }; export const subscriptionMock = { - 'id': 21, - 'type': 'subscription', - 'subscriptionParameterList': [ - { - 'id': 77, - 'name': 'frequency', - 'value': 'D' - }, - { - 'id': 78, - 'name': 'frequency', - 'value': 'M' - } - ], - 'subscriptionType': 'test1', - '_links': { - 'dSpaceObject': { - 'href': 'https://dspace/server/api/core/subscriptions/21/dSpaceObject' - }, - 'ePerson': { - 'href': 'https://dspace/server/api/core/subscriptions/21/ePerson' - }, - 'self': { - 'href': 'https://dspace/server/api/core/subscriptions/21' - } + 'id': 21, + 'type': 'subscription', + 'subscriptionParameterList': [ + { + 'id': 77, + 'name': 'frequency', + 'value': 'D' + }, + { + 'id': 78, + 'name': 'frequency', + 'value': 'M' } + ], + 'subscriptionType': 'test1', + 'ePerson': createSuccessfulRemoteDataObject$(mockSubscriptionEperson), + 'dSpaceObject': createSuccessfulRemoteDataObject$(mockSubscriptionDSO), + '_links': { + 'dSpaceObject': { + 'href': 'https://dspace/server/api/core/subscriptions/21/dSpaceObject' + }, + 'ePerson': { + 'href': 'https://dspace/server/api/core/subscriptions/21/ePerson' + }, + 'self': { + 'href': 'https://dspace/server/api/core/subscriptions/21' + } + } }; export const subscriptionMock2 = { @@ -4475,6 +143,8 @@ export const subscriptionMock2 = { }, ], 'subscriptionType': 'test2', + 'ePerson': createSuccessfulRemoteDataObject$(mockSubscriptionEperson), + 'dSpaceObject': createSuccessfulRemoteDataObject$(mockSubscriptionDSO2), '_links': { 'dSpaceObject': { 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/21/dSpaceObject' diff --git a/src/app/subscriptions-page/subscriptions-page.component.html b/src/app/subscriptions-page/subscriptions-page.component.html index 56e4980ca7..37b882fe44 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.html +++ b/src/app/subscriptions-page/subscriptions-page.component.html @@ -7,14 +7,13 @@ - +
- +
@@ -35,7 +34,7 @@ - + {{ 'subscriptions.table.empty.message' | translate }} diff --git a/src/app/subscriptions-page/subscriptions-page.component.spec.ts b/src/app/subscriptions-page/subscriptions-page.component.spec.ts index 77d2f6805a..9b17d72deb 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.spec.ts +++ b/src/app/subscriptions-page/subscriptions-page.component.spec.ts @@ -1,41 +1,31 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; - - -// Import modules import { CommonModule } from '@angular/common'; import { BrowserModule, By } from '@angular/platform-browser'; -import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { ActivatedRoute } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { of as observableOf } from 'rxjs'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; -// Import components import { SubscriptionsPageComponent } from './subscriptions-page.component'; - -// Import services import { PaginationService } from '../core/pagination/pagination.service'; import { SubscriptionService } from '../shared/subscriptions/subscription.service'; import { PaginationServiceStub } from '../shared/testing/pagination-service.stub'; import { AuthService } from '../core/auth/auth.service'; - -// Import utils -import { HostWindowService } from '../shared/host-window.service'; -import { HostWindowServiceStub } from '../shared/testing/host-window-service.stub'; - - -// Import mocks import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; -import { findAllSubscriptionRes } from '../shared/testing/subscriptions-data.mock'; -import { MockActivatedRoute } from '../shared/mocks/active-router.mock'; -import { of as observableOf } from 'rxjs'; -import { EPersonMock } from '../shared/testing/eperson.mock'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { VarDirective } from '../shared/utils/var.directive'; import { - SubscriptionViewComponent -} from '../shared/subscriptions/components/subscription-view/subscription-view.component'; - + mockSubscriptionEperson, + subscriptionMock, + subscriptionMock2 +} from '../shared/testing/subscriptions-data.mock'; +import { MockActivatedRoute } from '../shared/mocks/active-router.mock'; +import { VarDirective } from '../shared/utils/var.directive'; +import { SubscriptionViewComponent } from '../shared/subscriptions/subscription-view/subscription-view.component'; +import { PageInfo } from '../core/shared/page-info.model'; +import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; +import { buildPaginatedList } from '../core/data/paginated-list.model'; describe('SubscriptionsPageComponent', () => { let component: SubscriptionsPageComponent; @@ -43,15 +33,26 @@ describe('SubscriptionsPageComponent', () => { let de: DebugElement; const authServiceStub = jasmine.createSpyObj('authorizationService', { - getAuthenticatedUserFromStore: observableOf(EPersonMock) + getAuthenticatedUserFromStore: observableOf(mockSubscriptionEperson) }); const subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { - findByEPerson: observableOf(findAllSubscriptionRes) + findByEPerson: jasmine.createSpy('findByEPerson') }); + const paginationService = new PaginationServiceStub(); - beforeEach(waitForAsync( () => { + const mockSubscriptionList = [subscriptionMock, subscriptionMock2]; + + const emptyPageInfo = Object.assign(new PageInfo(), { + totalElements: 0 + }); + + const pageInfo = Object.assign(new PageInfo(), { + totalElements: 2 + }); + + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ CommonModule, @@ -65,63 +66,62 @@ describe('SubscriptionsPageComponent', () => { }), NoopAnimationsModule ], - declarations: [ SubscriptionsPageComponent, SubscriptionViewComponent, VarDirective ], - providers:[ + declarations: [SubscriptionsPageComponent, SubscriptionViewComponent, VarDirective], + providers: [ { provide: SubscriptionService, useValue: subscriptionServiceStub }, - { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }, { provide: ActivatedRoute, useValue: new MockActivatedRoute() }, { provide: AuthService, useValue: authServiceStub }, { provide: PaginationService, useValue: paginationService } ], schemas: [NO_ERRORS_SCHEMA] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SubscriptionsPageComponent); component = fixture.componentInstance; de = fixture.debugElement; - fixture.detectChanges(); }); - it('should create', () => { - fixture.detectChanges(); - expect(component).toBeTruthy(); - }); + describe('when there are subscriptions', () => { - describe('when table', () => { - - it('should show table', async() => { - await fixture.whenStable(); + beforeEach(() => { + subscriptionServiceStub.findByEPerson.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(pageInfo, mockSubscriptionList))); fixture.detectChanges(); - const table = de.query(By.css('table')); - expect(table).toBeTruthy(); }); + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should show table', () => { + expect(de.query(By.css('[data-test="subscription-table"]'))).toBeTruthy(); + expect(de.query(By.css('[data-test="empty-alert"]'))).toBeNull(); + }); + + it('should show a row for each results entry',() => { + expect(de.query(By.css('[data-test="subscription-table"]'))).toBeTruthy(); + expect(de.query(By.css('[data-test="empty-alert"]'))).toBeNull(); + expect(de.queryAll(By.css('tbody > tr')).length).toEqual(2); + }); }); - it('should show all the results', () => { - expect(de.queryAll(By.css('tbody > tr')).length).toEqual(10); - }); + describe('when there are no subscriptions', () => { - it('should have dso object info', () => { - expect(de.query(By.css('.dso-info > span'))).toBeTruthy(); - expect(de.query(By.css('.dso-info > p > a'))).toBeTruthy(); - }); + beforeEach(() => { + subscriptionServiceStub.findByEPerson.and.returnValue(createSuccessfulRemoteDataObject$(buildPaginatedList(emptyPageInfo, []))); + fixture.detectChanges(); + }); - it('should have subscription type info', () => { - expect(de.query(By.css('.subscription-type'))).toBeTruthy(); - }); + it('should create', () => { + expect(component).toBeTruthy(); + }); - it('should have subscription paramenter info', () => { - expect(de.query(By.css('.subscription-parmenters > span'))).toBeTruthy(); + it('should not show table', () => { + expect(de.query(By.css('[data-test="subscription-table"]'))).toBeNull(); + expect(de.query(By.css('[data-test="empty-alert"]'))).toBeTruthy(); + }); }); - it('should have subscription action info', () => { - expect(de.query(By.css('.btn-outline-primary'))).toBeTruthy(); - expect(de.query(By.css('.btn-outline-danger'))).toBeTruthy(); - }); - - }); diff --git a/src/app/subscriptions-page/subscriptions-page.component.ts b/src/app/subscriptions-page/subscriptions-page.component.ts index a99daa9d68..2eed8b5fa5 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.ts +++ b/src/app/subscriptions-page/subscriptions-page.component.ts @@ -11,7 +11,8 @@ import { PaginationService } from '../core/pagination/pagination.service'; import { PageInfo } from '../core/shared/page-info.model'; import { AuthService } from '../core/auth/auth.service'; import { EPerson } from '../core/eperson/models/eperson.model'; -import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; +import { getFirstCompletedRemoteData } from '../core/shared/operators'; +import { RemoteData } from '../core/data/remote-data'; @Component({ selector: 'ds-subscriptions-page', @@ -43,7 +44,7 @@ export class SubscriptionsPageComponent implements OnInit { ePersonId$: Observable; /** - * EPerson id of the logged in user + * EPerson id of the logged-in user */ // ePersonId: string; @@ -61,35 +62,26 @@ export class SubscriptionsPageComponent implements OnInit { this.ePersonId$ = this.authService.getAuthenticatedUserFromStore().pipe( take(1), map((ePerson: EPerson) => ePerson.id), - shareReplay(), - /*tap((ePersonId: string) => { // TODO unused - this.ePersonId = ePersonId; - }),*/ + shareReplay() ); this.retrieveSubscriptions(); } private retrieveSubscriptions() { this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( - tap(console.log), combineLatestWith(this.ePersonId$), - tap(() => {this.loading$.next(true);}), + tap(() => this.loading$.next(true)), switchMap(([currentPagination, ePersonId]) => this.subscriptionService.findByEPerson(ePersonId,{ currentPage: currentPagination.currentPage, elementsPerPage: currentPagination.pageSize })), - getFirstSucceededRemoteDataPayload(), - tap((x) => console.log('find', x)), - // getFirstSucceededRemoteDataPayload(), - ).subscribe({ - next: (res: any) => { - console.log('next',res); - this.subscriptions$.next(res); - this.loading$.next(false); - }, - error: () => { - this.loading$.next(false); + getFirstCompletedRemoteData() + + ).subscribe((res: RemoteData>) => { + if (res.hasSucceeded) { + this.subscriptions$.next(res.payload); } + this.loading$.next(false); }); } /** From 27d5097f4b24795224579dd2bc4e08585ee28390 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 28 Dec 2022 16:22:17 +0100 Subject: [PATCH 029/173] [CST-7757] Add subscribe button to all dso pages --- .../collection-page.component.html | 1 + .../community-page.component.html | 1 + .../journal-issue/journal-issue.component.html | 1 + .../journal-volume.component.html | 1 + .../item-pages/journal/journal.component.html | 1 + .../org-unit/org-unit.component.html | 1 + .../item-pages/person/person.component.html | 1 + .../item-pages/project/project.component.html | 1 + .../full/full-item-page.component.html | 1 + .../untyped-item/untyped-item.component.html | 1 + .../dso-page-subscription-button.component.ts | 18 ++++++++++-------- 11 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/app/collection-page/collection-page.component.html b/src/app/collection-page/collection-page.component.html index eebfdbd829..b236365df5 100644 --- a/src/app/collection-page/collection-page.component.html +++ b/src/app/collection-page/collection-page.component.html @@ -35,6 +35,7 @@
+
diff --git a/src/app/community-page/community-page.component.html b/src/app/community-page/community-page.component.html index 368fec08a5..1b88877f8b 100644 --- a/src/app/community-page/community-page.component.html +++ b/src/app/community-page/community-page.component.html @@ -22,6 +22,7 @@
+
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html index bf73a62447..e12b8a5ceb 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html @@ -6,6 +6,7 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> +
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html index b370431a27..1bc9ec1137 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html @@ -6,6 +6,7 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> +
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index 27ee373237..b787e937d6 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -6,6 +6,7 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> +
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index ac92e4ad6d..524adae4ce 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -6,6 +6,7 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> +
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 a73236006b..998c07777f 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 @@ -7,6 +7,7 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> +
diff --git a/src/app/entity-groups/research-entities/item-pages/project/project.component.html b/src/app/entity-groups/research-entities/item-pages/project/project.component.html index 3fb5e2d520..b52f5aa8dd 100644 --- a/src/app/entity-groups/research-entities/item-pages/project/project.component.html +++ b/src/app/entity-groups/research-entities/item-pages/project/project.component.html @@ -6,6 +6,7 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> +
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 ee4ead2835..60aa6d6f86 100644 --- a/src/app/item-page/full/full-item-page.component.html +++ b/src/app/item-page/full/full-item-page.component.html @@ -10,6 +10,7 @@
+
diff --git a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.ts b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.ts index 39e685b465..f97439c1a1 100644 --- a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.ts +++ b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.ts @@ -1,8 +1,10 @@ import { Component, Input, OnInit } from '@angular/core'; + import { Observable, of } from 'rxjs'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; + import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; -import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { SubscriptionModalComponent } from '../../subscriptions/subscription-modal/subscription-modal.component'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; @@ -23,16 +25,10 @@ export class DsoPageSubscriptionButtonComponent implements OnInit { */ public modalRef: NgbModalRef; - /** - * EPerson id of the logged user - */ - ePersonId: string; - /** * DSpaceObject that is being viewed */ - @Input() - dso: DSpaceObject; + @Input() dso: DSpaceObject; constructor( protected authorizationService: AuthorizationDataService, @@ -40,10 +36,16 @@ export class DsoPageSubscriptionButtonComponent implements OnInit { ) { } + /** + * check if the current DSpaceObject can be subscribed by the user + */ ngOnInit(): void { this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanSubscribe, this.dso.self); } + /** + * Open the modal to subscribe to the related DSpaceObject + */ public openSubscriptionModal() { this.modalRef = this.modalService.open(SubscriptionModalComponent); this.modalRef.componentInstance.dso = this.dso; From 90a923889510b3f304ac04d5c5231f351ad3fcd8 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 28 Dec 2022 16:23:08 +0100 Subject: [PATCH 030/173] [CST-7757] fix pagination --- .../subscription-modal.component.html | 26 ++++------ .../subscriptions-page.component.html | 4 +- .../subscriptions-page.component.ts | 51 +++++++++++++------ 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html index ef9e26032d..f78567ee7b 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html @@ -6,15 +6,11 @@
- diff --git a/src/app/subscriptions-page/subscriptions-page.component.html b/src/app/subscriptions-page/subscriptions-page.component.html index 37b882fe44..a327ec658c 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.html +++ b/src/app/subscriptions-page/subscriptions-page.component.html @@ -9,14 +9,14 @@
{{'subscriptions.table.dso' | translate}}
- + diff --git a/src/app/subscriptions-page/subscriptions-page.component.ts b/src/app/subscriptions-page/subscriptions-page.component.ts index 2eed8b5fa5..242d5145ac 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.ts +++ b/src/app/subscriptions-page/subscriptions-page.component.ts @@ -1,6 +1,6 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; -import { BehaviorSubject, combineLatestWith, Observable, shareReplay } from 'rxjs'; +import { BehaviorSubject, combineLatestWith, Observable, shareReplay, Subscription as rxjsSubscription } from 'rxjs'; import { map, switchMap, take, tap } from 'rxjs/operators'; import { Subscription } from '../shared/subscriptions/models/subscription.model'; @@ -11,15 +11,16 @@ import { PaginationService } from '../core/pagination/pagination.service'; import { PageInfo } from '../core/shared/page-info.model'; import { AuthService } from '../core/auth/auth.service'; import { EPerson } from '../core/eperson/models/eperson.model'; -import { getFirstCompletedRemoteData } from '../core/shared/operators'; +import { getAllCompletedRemoteData } from '../core/shared/operators'; import { RemoteData } from '../core/data/remote-data'; +import { hasValue } from '../shared/empty.util'; @Component({ selector: 'ds-subscriptions-page', templateUrl: './subscriptions-page.component.html', styleUrls: ['./subscriptions-page.component.scss'] }) -export class SubscriptionsPageComponent implements OnInit { +export class SubscriptionsPageComponent implements OnInit, OnDestroy { /** * The subscriptions to show on this page, as an Observable list. @@ -27,8 +28,7 @@ export class SubscriptionsPageComponent implements OnInit { subscriptions$: BehaviorSubject> = new BehaviorSubject(buildPaginatedList(new PageInfo(), [])); /** - * The current pagination configuration for the page used by the FindAll method - * Currently simply renders subscriptions + * The current pagination configuration for the page */ config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { id: 'elp', @@ -41,22 +41,26 @@ export class SubscriptionsPageComponent implements OnInit { */ loading$: BehaviorSubject = new BehaviorSubject(false); + /** + * The current eperson id + */ ePersonId$: Observable; /** - * EPerson id of the logged-in user + * The rxjs subscription used to retrieve the result list */ - // ePersonId: string; + sub: rxjsSubscription = null; constructor( private paginationService: PaginationService, private authService: AuthService, private subscriptionService: SubscriptionService - ) { } + ) { + + } /** - * Subscribe the pagination service to send a request with specific pagination - * When page is changed it will request the new subscriptions for the new page config + * Retrieve the current eperson id and call method to retrieve the subscriptions */ ngOnInit(): void { this.ePersonId$ = this.authService.getAuthenticatedUserFromStore().pipe( @@ -67,16 +71,21 @@ export class SubscriptionsPageComponent implements OnInit { this.retrieveSubscriptions(); } - private retrieveSubscriptions() { - this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + /** + * Retrieve subscription list related to the current user. + * When page is changed it will request the new subscriptions for the new page config + * @private + */ + private retrieveSubscriptions(): void { + this.sub = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + tap(console.log), combineLatestWith(this.ePersonId$), tap(() => this.loading$.next(true)), switchMap(([currentPagination, ePersonId]) => this.subscriptionService.findByEPerson(ePersonId,{ currentPage: currentPagination.currentPage, elementsPerPage: currentPagination.pageSize })), - getFirstCompletedRemoteData() - + getAllCompletedRemoteData() ).subscribe((res: RemoteData>) => { if (res.hasSucceeded) { this.subscriptions$.next(res.payload); @@ -85,10 +94,20 @@ export class SubscriptionsPageComponent implements OnInit { }); } /** - * When an action is made and the information is changed refresh the information + * When a subscription is deleted refresh the subscription list */ refresh(): void { + if (hasValue(this.sub)) { + this.sub.unsubscribe(); + } + this.retrieveSubscriptions(); } + ngOnDestroy(): void { + if (hasValue(this.sub)) { + this.sub.unsubscribe(); + } + } + } From 09781c90573084bd56e76136702584a558882bb6 Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 29 Dec 2022 18:16:54 +0530 Subject: [PATCH 031/173] [CST-7755] LYRASIS: Supervisor orders (Angular) --- .../admin-workflow-page.component.html | 2 +- ...admin-workflow-list-element.component.html | 5 +- ...t-admin-workflow-list-element.component.ts | 2 + ...item-admin-workflow-actions.component.html | 7 + ...m-admin-workflow-actions.component.spec.ts | 15 + ...w-item-admin-workflow-actions.component.ts | 33 +++ src/app/core/core.module.ts | 2 + src/app/core/shared/context.model.ts | 1 + .../models/submission-object.model.ts | 10 + .../models/action-type.model.ts | 44 +++ .../models/supervision-order.model.ts | 74 +++++ .../models/supervision-order.resource-type.ts | 9 + .../supervision-order-data.service.spec.ts | 277 ++++++++++++++++++ .../supervision-order-data.service.ts | 181 ++++++++++++ ...arch-result-list-element.component.spec.ts | 67 ++++- ...arch-result-list-element.component.spec.ts | 66 +++++ ...arch-result-list-element.component.spec.ts | 66 +++++ ...arch-result-list-element.component.spec.ts | 64 +++- ...arch-result-list-element.component.spec.ts | 63 +++- ...on-search-result-list-element.component.ts | 12 +- ...arch-result-list-element.component.spec.ts | 67 ++++- .../my-dspace-configuration-value-type.ts | 1 + .../my-dspace-configuration.service.spec.ts | 4 +- .../my-dspace-configuration.service.ts | 2 + .../supervision-group-selector.component.html | 40 +++ ...supervision-group-selector.component.scss} | 0 ...pervision-group-selector.component.spec.ts | 70 +++++ .../supervision-group-selector.component.ts | 90 ++++++ .../eperson-group-list.component.html | 0 .../eperson-group-list.component.scss | 0 .../eperson-group-list.component.spec.ts | 26 +- .../eperson-group-list.component.ts | 32 +- .../eperson-search-box.component.html | 0 .../eperson-search-box.component.spec.ts | 2 +- .../eperson-search-box.component.ts | 2 +- .../group-search-box.component.html | 0 .../group-search-box.component.spec.ts | 2 +- .../group-search-box.component.ts | 2 +- ...-search-result-list-element.component.html | 13 + ...-search-result-list-element.component.scss | 6 + ...arch-result-list-element.component.spec.ts | 66 +++++ ...em-search-result-list-element.component.ts | 76 ++++- .../resource-policy-form.component.spec.ts | 2 +- .../resource-policies.module.ts | 6 - src/app/shared/search/search.component.ts | 6 +- src/app/shared/shared.module.ts | 12 + src/assets/i18n/en.json5 | 56 ++++ 47 files changed, 1531 insertions(+), 52 deletions(-) create mode 100644 src/app/core/supervision-order/models/action-type.model.ts create mode 100644 src/app/core/supervision-order/models/supervision-order.model.ts create mode 100644 src/app/core/supervision-order/models/supervision-order.resource-type.ts create mode 100644 src/app/core/supervision-order/supervision-order-data.service.spec.ts create mode 100644 src/app/core/supervision-order/supervision-order-data.service.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.html rename src/app/shared/{resource-policies/form/eperson-group-list/eperson-group-list.component.scss => dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.scss} (100%) create mode 100644 src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.spec.ts create mode 100644 src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts rename src/app/shared/{resource-policies => }/form/eperson-group-list/eperson-group-list.component.html (100%) create mode 100644 src/app/shared/form/eperson-group-list/eperson-group-list.component.scss rename src/app/shared/{resource-policies => }/form/eperson-group-list/eperson-group-list.component.spec.ts (88%) rename src/app/shared/{resource-policies => }/form/eperson-group-list/eperson-group-list.component.ts (81%) rename src/app/shared/{resource-policies => }/form/eperson-group-list/eperson-search-box/eperson-search-box.component.html (100%) rename src/app/shared/{resource-policies => }/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts (97%) rename src/app/shared/{resource-policies => }/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts (96%) rename src/app/shared/{resource-policies => }/form/eperson-group-list/group-search-box/group-search-box.component.html (100%) rename src/app/shared/{resource-policies => }/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts (97%) rename src/app/shared/{resource-policies => }/form/eperson-group-list/group-search-box/group-search-box.component.ts (96%) diff --git a/src/app/admin/admin-workflow-page/admin-workflow-page.component.html b/src/app/admin/admin-workflow-page/admin-workflow-page.component.html index 404af131d1..d12cefb331 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-page.component.html +++ b/src/app/admin/admin-workflow-page/admin-workflow-page.component.html @@ -1 +1 @@ - + diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.html b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.html index 192cc751f2..77ece5254d 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.html +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.html @@ -1,6 +1,9 @@ -
+
{{ "admin.workflow.item.workflow" | translate }}
+
+ {{ "admin.workflow.item.workspace" | translate }} +
{{"admin.workflow.item.send-back" | translate}} + + {{'admin.workflow.item.policies' | translate}} + + + {{'admin.workflow.item.supervision' | translate}} +
diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component.spec.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component.spec.ts index 75e0e2e7a8..9b2bffffef 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component.spec.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component.spec.ts @@ -11,17 +11,25 @@ import { getWorkflowItemDeleteRoute, getWorkflowItemSendBackRoute } from '../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; +import { of } from 'rxjs'; +import { Item } from 'src/app/core/shared/item.model'; +import { RemoteData } from 'src/app/core/data/remote-data'; +import { RequestEntryState } from 'src/app/core/data/request-entry-state.model'; describe('WorkflowItemAdminWorkflowActionsComponent', () => { let component: WorkflowItemAdminWorkflowActionsComponent; let fixture: ComponentFixture; let id; let wfi; + let item = new Item(); + item.uuid = 'itemUUID1111'; + const rd = new RemoteData(undefined, undefined, undefined, RequestEntryState.Success, undefined, item, 200); function init() { id = '780b2588-bda5-4112-a1cd-0b15000a5339'; wfi = new WorkflowItem(); wfi.id = id; + wfi.item = of(rd); } beforeEach(waitForAsync(() => { @@ -59,4 +67,11 @@ describe('WorkflowItemAdminWorkflowActionsComponent', () => { const link = a.nativeElement.href; expect(link).toContain(new URLCombiner(getWorkflowItemSendBackRoute(wfi.id)).toString()); }); + + it('should render a policies button with the correct link', () => { + const a = fixture.debugElement.query(By.css('a.policies-link')); + const link = a.nativeElement.href; + expect(link).toContain(new URLCombiner('/items/itemUUID1111/edit/bitstreams').toString()); + }); + }); diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component.ts index 32725a0e7a..924f24e738 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/workflow-item-admin-workflow-actions.component.ts @@ -1,4 +1,9 @@ import { Component, Input } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { map, Observable } from 'rxjs'; +import { Item } from '../../../core/shared/item.model'; +import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; +import { SupervisionGroupSelectorComponent } from '../../../shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component'; import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; import { getWorkflowItemSendBackRoute, @@ -25,6 +30,10 @@ export class WorkflowItemAdminWorkflowActionsComponent { */ @Input() public small: boolean; + constructor( + private modalService: NgbModal + ) { } + /** * Returns the path to the delete page of this workflow item */ @@ -39,4 +48,28 @@ export class WorkflowItemAdminWorkflowActionsComponent { getSendBackRoute(): string { return getWorkflowItemSendBackRoute(this.wfi.id); } + + /** + * Returns the path to the to administrative edit page policies tab + */ + getPoliciesRoute(): Observable { + return this.wfi.item.pipe( + getFirstSucceededRemoteDataPayload(), + map((item: Item) => { + return '/items/' + item.uuid + '/edit/bitstreams'; + }) + ); + } + + /** + * Opens the Supervision Modal to create a supervision order + */ + openSupervisionModal() { + this.wfi.item.pipe( + getFirstSucceededRemoteDataPayload(), + ).subscribe((item: Item) => { + const supervisionModal = this.modalService.open(SupervisionGroupSelectorComponent, { size: 'lg', backdrop: 'static' }); + supervisionModal.componentInstance.itemUUID = item.uuid; + }); + } } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index ede23ba43b..845bab2ea8 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -170,6 +170,7 @@ import { OrcidHistory } from './orcid/model/orcid-history.model'; import { OrcidAuthService } from './orcid/orcid-auth.service'; import { VocabularyDataService } from './submission/vocabularies/vocabulary.data.service'; import { VocabularyEntryDetailsDataService } from './submission/vocabularies/vocabulary-entry-details.data.service'; +import { SupervisionOrderDataService } from './supervision-order/supervision-order-data.service'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -292,6 +293,7 @@ const PROVIDERS = [ OrcidAuthService, OrcidQueueDataService, OrcidHistoryDataService, + SupervisionOrderDataService ]; /** diff --git a/src/app/core/shared/context.model.ts b/src/app/core/shared/context.model.ts index 126896e3e1..0f1130ebda 100644 --- a/src/app/core/shared/context.model.ts +++ b/src/app/core/shared/context.model.ts @@ -8,6 +8,7 @@ export enum Context { Search = 'search', Workflow = 'workflow', Workspace = 'workspace', + SupervisedItems = 'otherworkspace', AdminMenu = 'adminMenu', EntitySearchModalWithNameVariants = 'EntitySearchModalWithNameVariants', EntitySearchModal = 'EntitySearchModal', diff --git a/src/app/core/submission/models/submission-object.model.ts b/src/app/core/submission/models/submission-object.model.ts index 3d7c0a3678..3d373d2522 100644 --- a/src/app/core/submission/models/submission-object.model.ts +++ b/src/app/core/submission/models/submission-object.model.ts @@ -14,6 +14,7 @@ import { ITEM } from '../../shared/item.resource-type'; import { excludeFromEquals } from '../../utilities/equals.decorators'; import { WorkspaceitemSectionsObject } from './workspaceitem-sections.model'; import { CacheableObject } from '../../cache/cacheable-object.model'; +import { SUPERVISION_ORDER } from '../../supervision-order/models/supervision-order.resource-type'; export interface SubmissionObjectError { message: string; @@ -65,6 +66,7 @@ export abstract class SubmissionObject extends DSpaceObject implements Cacheable item: HALLink; submissionDefinition: HALLink; submitter: HALLink; + supervisionOrders: HALLink; }; get self(): string { @@ -93,4 +95,12 @@ export abstract class SubmissionObject extends DSpaceObject implements Cacheable @link(EPERSON) submitter?: Observable> | EPerson; + /** + * The submission supervision order + * Will be undefined unless the workspace item {@link HALLink} has been resolved. + */ + @link(SUPERVISION_ORDER) + /* This was changed from 'Observable> | WorkspaceItem' to 'any' to prevent issues in templates with async */ + supervisionOrders?: any; + } diff --git a/src/app/core/supervision-order/models/action-type.model.ts b/src/app/core/supervision-order/models/action-type.model.ts new file mode 100644 index 0000000000..0e55689069 --- /dev/null +++ b/src/app/core/supervision-order/models/action-type.model.ts @@ -0,0 +1,44 @@ +/** + * Enum representing the Action Type of a Resource Policy + */ +export enum ActionType { + /** + * Action of reading, viewing or downloading something + */ + READ = 'READ', + + /** + * Action of modifying something + */ + WRITE = 'WRITE', + + /** + * Action of deleting something + */ + DELETE = 'DELETE', + + /** + * Action of adding something to a container + */ + ADD = 'ADD', + + /** + * Action of removing something from a container + */ + REMOVE = 'REMOVE', + + /** + * None Type of Supervision Order + */ + NONE = 'NONE', + + /** + * Editor Type of Supervision Order + */ + EDITOR = 'EDITOR', + + /** + * Observer Type of Supervision Order + */ + OBSERVER = 'OBSERVER', +} diff --git a/src/app/core/supervision-order/models/supervision-order.model.ts b/src/app/core/supervision-order/models/supervision-order.model.ts new file mode 100644 index 0000000000..e881c06d1b --- /dev/null +++ b/src/app/core/supervision-order/models/supervision-order.model.ts @@ -0,0 +1,74 @@ +import { autoserialize, deserialize, deserializeAs } from 'cerialize'; +import { link, typedObject } from '../../cache/builders/build-decorators'; +import { IDToUUIDSerializer } from '../../cache/id-to-uuid-serializer'; +import { HALLink } from '../../shared/hal-link.model'; +import { SUPERVISION_ORDER } from './supervision-order.resource-type'; +import { excludeFromEquals } from '../../utilities/equals.decorators'; +import { ResourceType } from '../../shared/resource-type'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../data/remote-data'; +import { GROUP } from '../../eperson/models/group.resource-type'; +import { Group } from '../../eperson/models/group.model'; +import { CacheableObject } from '../../cache/cacheable-object.model'; +import { ITEM } from '../../shared/item.resource-type'; +import { Item } from '../../shared/item.model'; + +/** + * Model class for a Supervision Order + */ +@typedObject +export class SupervisionOrder implements CacheableObject { + static type = SUPERVISION_ORDER; + + /** + * The identifier for this Supervision Order + */ + @autoserialize + id: string; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + ordertype: string; + + /** + * The universally unique identifier for this Supervision Order + * This UUID is generated client-side and isn't used by the backend. + * It is based on the ID, so it will be the same for each refresh. + */ + @deserializeAs(new IDToUUIDSerializer('supervision-order'), 'id') + uuid: string; + + /** + * The {@link HALLink}s for this SupervisionOrder + */ + @deserialize + _links: { + item: HALLink, + group: HALLink, + self: HALLink, + }; + + /** + * The related supervision Item + * Will be undefined unless the item {@link HALLink} has been resolved. + */ + @link(ITEM) + item?: Observable>; + + /** + * The group linked by this supervision order + * Will be undefined unless the version {@link HALLink} has been resolved. + */ + @link(GROUP) + group?: Observable>; +} diff --git a/src/app/core/supervision-order/models/supervision-order.resource-type.ts b/src/app/core/supervision-order/models/supervision-order.resource-type.ts new file mode 100644 index 0000000000..994c93ab74 --- /dev/null +++ b/src/app/core/supervision-order/models/supervision-order.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../../shared/resource-type'; + +/** + * The resource type for SupervisionOrder + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const SUPERVISION_ORDER = new ResourceType('supervisionorder'); diff --git a/src/app/core/supervision-order/supervision-order-data.service.spec.ts b/src/app/core/supervision-order/supervision-order-data.service.spec.ts new file mode 100644 index 0000000000..b12817fa1a --- /dev/null +++ b/src/app/core/supervision-order/supervision-order-data.service.spec.ts @@ -0,0 +1,277 @@ +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 { SupervisionOrderDataService } from './supervision-order-data.service'; +import { ActionType } from './models/action-type.model'; +import { RequestParam } from '../cache/models/request-param.model'; +import { PageInfo } from '../shared/page-info.model'; +import { buildPaginatedList } from '../data/paginated-list.model'; +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 { GroupDataService } from '../eperson/group-data.service'; + +describe('SupervisionOrderService', () => { + let scheduler: TestScheduler; + let service: SupervisionOrderDataService; + let requestService: RequestService; + let rdbService: RemoteDataBuildService; + let objectCache: ObjectCacheService; + let halService: HALEndpointService; + let responseCacheEntry: RequestEntry; + let groupService: GroupDataService; + + const supervisionOrder: any = { + id: '1', + name: null, + description: null, + action: ActionType.READ, + startDate: null, + endDate: null, + type: 'supervisionOrder', + uuid: 'supervision-order-1', + _links: { + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorder/1' + }, + } + }; + + const anothersupervisionOrder: any = { + id: '2', + name: null, + description: null, + action: ActionType.WRITE, + startDate: null, + endDate: null, + type: 'supervisionOrder', + uuid: 'supervision-order-2', + _links: { + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorder/1' + }, + } + }; + const endpointURL = `https://rest.api/rest/api/supervisionorder`; + const requestURL = `https://rest.api/rest/api/supervisionorder/${supervisionOrder.id}`; + const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a'; + const supervisionOrderId = '1'; + const groupUUID = '8b39g7ya-5a4b-438b-9686-be1d5b4a1c5a'; + const itemUUID = '8b39g7ya-5a4b-438b-851f-be1d5b4a1c5a'; + const supervisionOrderType = 'NONE'; + + const pageInfo = new PageInfo(); + const array = [supervisionOrder, anothersupervisionOrder]; + const paginatedList = buildPaginatedList(pageInfo, array); + const supervisionOrderRD = createSuccessfulRemoteDataObject(supervisionOrder); + const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + + const groupEndpoint = 'group_EP'; + + 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: {}, + }); + rdbService = jasmine.createSpyObj('rdbService', { + buildSingle: hot('a|', { + a: supervisionOrderRD + }), + buildList: hot('a|', { + a: paginatedListRD + }), + buildFromRequestUUID: hot('a|', { + a: supervisionOrderRD + }), + buildFromRequestUUIDAndAwait: hot('a|', { + a: supervisionOrderRD + }) + }); + groupService = jasmine.createSpyObj('groupService', { + getBrowseEndpoint: hot('a', { + a: groupEndpoint + }), + getIDHrefObs: cold('a', { + a: 'https://rest.api/rest/api/group/groups/' + groupUUID + }), + }); + groupService = jasmine.createSpyObj('groupService', { + getIDHrefObs: cold('a', { + a: 'https://rest.api/rest/api/group/groups/' + groupUUID + }), + }); + objectCache = {} as ObjectCacheService; + const notificationsService = {} as NotificationsService; + const comparator = {} as any; + + service = new SupervisionOrderDataService( + requestService, + rdbService, + objectCache, + halService, + notificationsService, + comparator, + groupService, + ); + + spyOn(service, 'findById').and.callThrough(); + spyOn(service, 'findByHref').and.callThrough(); + spyOn(service, 'invalidateByHref').and.returnValue(observableOf(true)); + spyOn((service as any).createData, 'create').and.callThrough(); + spyOn((service as any).deleteData, 'delete').and.callThrough(); + spyOn((service as any).patchData, 'update').and.callThrough(); + spyOn((service as any).searchData, 'searchBy').and.callThrough(); + spyOn((service as any).searchData, 'getSearchByHref').and.returnValue(observableOf(requestURL)); + }); + + describe('create', () => { + it('should proxy the call to createData.create with group UUID', () => { + scheduler.schedule(() => service.create(supervisionOrder, itemUUID, groupUUID, supervisionOrderType)); + const params = [ + new RequestParam('uuid', itemUUID), + new RequestParam('group', groupUUID), + new RequestParam('type', supervisionOrderType), + ]; + scheduler.flush(); + + expect((service as any).createData.create).toHaveBeenCalledWith(supervisionOrder, ...params); + }); + + it('should proxy the call to createData.create with group UUID', () => { + scheduler.schedule(() => service.create(supervisionOrder, itemUUID, groupUUID, supervisionOrderType)); + const params = [ + new RequestParam('uuid', itemUUID), + new RequestParam('group', groupUUID), + new RequestParam('type', supervisionOrderType), + ]; + scheduler.flush(); + + expect((service as any).createData.create).toHaveBeenCalledWith(supervisionOrder, ...params); + }); + + it('should return a RemoteData for the object with the given id', () => { + const result = service.create(supervisionOrder, itemUUID, groupUUID, supervisionOrderType); + const expected = cold('a|', { + a: supervisionOrderRD + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('delete', () => { + it('should proxy the call to deleteData.delete', () => { + scheduler.schedule(() => service.delete(supervisionOrderId)); + scheduler.flush(); + + expect((service as any).deleteData.delete).toHaveBeenCalledWith(supervisionOrderId); + }); + }); + + describe('update', () => { + it('should proxy the call to updateData.update', () => { + scheduler.schedule(() => service.update(supervisionOrder)); + scheduler.flush(); + + expect((service as any).patchData.update).toHaveBeenCalledWith(supervisionOrder); + }); + }); + + describe('findById', () => { + it('should return a RemoteData for the object with the given id', () => { + const result = service.findById(supervisionOrderId); + const expected = cold('a|', { + a: supervisionOrderRD + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('findByHref', () => { + it('should return a RemoteData for the object with the given URL', () => { + const result = service.findByHref(requestURL); + const expected = cold('a|', { + a: supervisionOrderRD + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('searchByGroup', () => { + it('should proxy the call to searchData.searchBy', () => { + const options = new FindListOptions(); + options.searchParams = [new RequestParam('uuid', groupUUID)]; + scheduler.schedule(() => service.searchByGroup(groupUUID)); + scheduler.flush(); + + expect((service as any).searchData.searchBy).toHaveBeenCalledWith((service as any).searchByGroupMethod, options, true, true); + }); + + it('should proxy the call to searchData.searchBy with additional search param', () => { + const options = new FindListOptions(); + options.searchParams = [ + new RequestParam('uuid', groupUUID), + new RequestParam('item', itemUUID), + ]; + scheduler.schedule(() => service.searchByGroup(groupUUID, itemUUID)); + scheduler.flush(); + + expect((service as any).searchData.searchBy).toHaveBeenCalledWith((service as any).searchByGroupMethod, options, true, true); + }); + + it('should return a RemoteData) for the search', () => { + const result = service.searchByGroup(groupUUID); + const expected = cold('a|', { + a: paginatedListRD + }); + expect(result).toBeObservable(expected); + }); + + }); + + describe('searchByItem', () => { + it('should proxy the call to searchData.searchBy', () => { + const options = new FindListOptions(); + options.searchParams = [new RequestParam('uuid', itemUUID)]; + scheduler.schedule(() => service.searchByItem(itemUUID)); + scheduler.flush(); + + expect((service as any).searchData.searchBy).toHaveBeenCalledWith((service as any).searchByItemMethod, options, true, true); + }); + + it('should return a RemoteData) for the search', () => { + const result = service.searchByItem(itemUUID); + const expected = cold('a|', { + a: paginatedListRD + }); + expect(result).toBeObservable(expected); + }); + }); + +}); diff --git a/src/app/core/supervision-order/supervision-order-data.service.ts b/src/app/core/supervision-order/supervision-order-data.service.ts new file mode 100644 index 0000000000..fed0c57b09 --- /dev/null +++ b/src/app/core/supervision-order/supervision-order-data.service.ts @@ -0,0 +1,181 @@ +import { Injectable } from '@angular/core'; +import { HttpHeaders } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; +import { RequestService } from '../data/request.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { SupervisionOrder } from './models/supervision-order.model'; +import { RemoteData } from '../data/remote-data'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { SUPERVISION_ORDER } from './models/supervision-order.resource-type'; +import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; +import { PaginatedList } from '../data/paginated-list.model'; +import { RequestParam } from '../cache/models/request-param.model'; +import { isNotEmpty } from '../../shared/empty.util'; +import { first, map } from 'rxjs/operators'; +import { NoContent } from '../shared/NoContent.model'; +import { getFirstCompletedRemoteData } from '../shared/operators'; +import { FindListOptions } from '../data/find-list-options.model'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; +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'; +import { GroupDataService } from '../eperson/group-data.service'; +import { IdentifiableDataService } from '../data/base/identifiable-data.service'; +import { CreateDataImpl } from '../data/base/create-data'; +import { SearchDataImpl } from '../data/base/search-data'; +import { PatchDataImpl } from '../data/base/patch-data'; +import { DeleteDataImpl } from '../data/base/delete-data'; +import { dataService } from '../data/base/data-service.decorator'; + +/** + * A service responsible for fetching/sending data from/to the REST API on the supervisionorders endpoint + */ +@Injectable() +@dataService(SUPERVISION_ORDER) +export class SupervisionOrderDataService extends IdentifiableDataService { + protected searchByGroupMethod = 'group'; + protected searchByItemMethod = 'byItem'; + + private createData: CreateDataImpl; + private searchData: SearchDataImpl; + private patchData: PatchDataImpl; + private deleteData: DeleteDataImpl; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected comparator: DefaultChangeAnalyzer, + protected groupService: GroupDataService, + ) { + super('supervisionorders', requestService, rdbService, objectCache, halService); + + this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive); + this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); + this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, comparator, this.responseMsToLive, this.constructIdEndpoint); + this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint); + } + + /** + * Create a new SupervisionOrder on the server, and store the response + * in the object cache + * + * @param {SupervisionOrder} supervisionOrder + * The supervision order to create + * @param {string} itemUUID + * The uuid of the item that will be grant of the permission. + * @param {string} groupUUID + * The uuid of the group that will be grant of the permission. + * @param {string} type + * The type of the supervision order that will be grant of the permission. + */ + create(supervisionOrder: SupervisionOrder, itemUUID: string, groupUUID: string, type: string): Observable> { + const params = []; + params.push(new RequestParam('uuid', itemUUID)); + params.push(new RequestParam('group', groupUUID)); + params.push(new RequestParam('type', type)); + return this.createData.create(supervisionOrder, ...params); + } + + /** + * Delete an existing SupervisionOrder on the server + * + * @param supervisionOrderID The supervision order's id to be removed + * @return an observable that emits true when the deletion was successful, false when it failed + */ + delete(supervisionOrderID: string): Observable { + return this.deleteData.delete(supervisionOrderID).pipe( + getFirstCompletedRemoteData(), + map((response: RemoteData) => response.hasSucceeded), + ); + } + + /** + * Add a new patch to the object cache + * The patch is derived from the differences between the given object and its version in the object cache + * @param {SupervisionOrder} object The given object + */ + update(object: SupervisionOrder): Observable> { + return this.patchData.update(object); + } + + /** + * Return the {@link SupervisionOrder} list for a {@link Group} + * + * @param UUID UUID of a given {@link Group} + * @param itemUUID Limit the returned policies to the specified DSO + * @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 + */ + searchByGroup(UUID: string, itemUUID?: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const options = new FindListOptions(); + options.searchParams = [new RequestParam('uuid', UUID)]; + if (isNotEmpty(itemUUID)) { + options.searchParams.push(new RequestParam('item', itemUUID)); + } + return this.searchData.searchBy(this.searchByGroupMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + + /** + * Return the {@link SupervisionOrder} list for a given DSO + * + * @param UUID UUID of a given DSO + * @param action Limit the returned policies to the specified {@link ActionType} + * @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 + */ + searchByItem(UUID: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const options = new FindListOptions(); + options.searchParams = [new RequestParam('uuid', UUID)]; + return this.searchData.searchBy(this.searchByItemMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + + /** + * Update the target of the supervision order + * @param supervisionOrderId the ID of the supervision order + * @param supervisionOrderHref the link to the supervision order + * @param targetUUID the UUID of the target to which the permission is being granted + * @param targetType the type of the target (eperson or group) to which the permission is being granted + */ + updateTarget(supervisionOrderId: string, supervisionOrderHref: string, targetUUID: string, targetType: string): Observable> { + const targetService = this.groupService; + const targetEndpoint$ = targetService.getIDHrefObs(targetUUID); + + 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(); + + targetEndpoint$.pipe( + first(), + ).subscribe((targetEndpoint) => { + const resourceEndpoint = supervisionOrderHref + '/' + targetType; + const request = new PutRequest(requestId, resourceEndpoint, targetEndpoint, options); + Object.assign(request, { + getResponseParser(): GenericConstructor { + return StatusCodeOnlyResponseParsingService; + } + }); + this.requestService.send(request); + }); + + return this.rdbService.buildFromRequestUUIDAndAwait(requestId, () => this.invalidateByHref(supervisionOrderHref)); + } + +} diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts index 178ed86c40..be110db101 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts @@ -10,6 +10,14 @@ import { TruncatableService } from '../../../../../shared/truncatable/truncatabl import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; +import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; +import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; +import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; +import { GroupMock } from '../../../../../shared/testing/group-mock'; +import { hot } from 'jasmine-marbles'; let journalIssueListElementComponent: JournalIssueSearchResultListElementComponent; let fixture: ComponentFixture; @@ -70,12 +78,61 @@ const enviromentNoThumbs = { } }; +const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { + searchByItem: jasmine.createSpy('searchByItem'), +}); + +const supervisionOrder: any = { + id: '1', + type: 'supervisionOrder', + uuid: 'supervision-order-1', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; +const anothersupervisionOrder: any = { + id: '2', + type: 'supervisionOrder', + uuid: 'supervision-order-2', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; + +const pageInfo = new PageInfo(); +const array = [supervisionOrder, anothersupervisionOrder]; +const paginatedList = buildPaginatedList(pageInfo, array); +const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + describe('JournalIssueSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, + { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + {provide: NotificationsService, useValue: {}}, + {provide: TranslateService, useValue: {}}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -87,6 +144,9 @@ describe('JournalIssueSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(JournalIssueSearchResultListElementComponent); journalIssueListElementComponent = fixture.componentInstance; @@ -164,6 +224,9 @@ describe('JournalIssueSearchResultListElementComponent', () => { declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, + {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + {provide: NotificationsService, useValue: {}}, + {provide: TranslateService, useValue: {}}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -181,7 +244,9 @@ describe('JournalIssueSearchResultListElementComponent', () => { describe('with environment.browseBy.showThumbnails set to false', () => { beforeEach(() => { - + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); journalIssueListElementComponent.object = mockItemWithMetadata; fixture.detectChanges(); }); diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts index 71fa83a3b1..76d516e76e 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts @@ -10,6 +10,14 @@ import { TruncatableService } from '../../../../../shared/truncatable/truncatabl import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; +import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; +import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; +import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; +import { GroupMock } from '../../../../../shared/testing/group-mock'; +import { hot } from 'jasmine-marbles'; let journalVolumeListElementComponent: JournalVolumeSearchResultListElementComponent; let fixture: ComponentFixture; @@ -69,12 +77,61 @@ const enviromentNoThumbs = { } }; +const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { + searchByItem: jasmine.createSpy('searchByItem'), +}); + +const supervisionOrder: any = { + id: '1', + type: 'supervisionOrder', + uuid: 'supervision-order-1', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; +const anothersupervisionOrder: any = { + id: '2', + type: 'supervisionOrder', + uuid: 'supervision-order-2', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; + +const pageInfo = new PageInfo(); +const array = [supervisionOrder, anothersupervisionOrder]; +const paginatedList = buildPaginatedList(pageInfo, array); +const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + describe('JournalVolumeSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [JournalVolumeSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, + { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + { provide: NotificationsService, useValue: {}}, + { provide: TranslateService, useValue: {}}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -86,6 +143,9 @@ describe('JournalVolumeSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(JournalVolumeSearchResultListElementComponent); journalVolumeListElementComponent = fixture.componentInstance; @@ -162,6 +222,9 @@ describe('JournalVolumeSearchResultListElementComponent', () => { declarations: [JournalVolumeSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, + {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + {provide: NotificationsService, useValue: {}}, + {provide: TranslateService, useValue: {}}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -173,6 +236,9 @@ describe('JournalVolumeSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(JournalVolumeSearchResultListElementComponent); journalVolumeListElementComponent = fixture.componentInstance; })); diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts index 07970d7128..55f0875731 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts @@ -10,6 +10,14 @@ import { ItemSearchResult } from '../../../../../shared/object-collection/shared import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; +import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; +import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; +import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; +import { GroupMock } from '../../../../../shared/testing/group-mock'; +import { hot } from 'jasmine-marbles'; let journalListElementComponent: JournalSearchResultListElementComponent; let fixture: ComponentFixture; @@ -65,12 +73,61 @@ const enviromentNoThumbs = { } }; +const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { + searchByItem: jasmine.createSpy('searchByItem'), +}); + +const supervisionOrder: any = { + id: '1', + type: 'supervisionOrder', + uuid: 'supervision-order-1', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; +const anothersupervisionOrder: any = { + id: '2', + type: 'supervisionOrder', + uuid: 'supervision-order-2', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; + +const pageInfo = new PageInfo(); +const array = [supervisionOrder, anothersupervisionOrder]; +const paginatedList = buildPaginatedList(pageInfo, array); +const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + describe('JournalSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [JournalSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, + { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + { provide: NotificationsService, useValue: {}}, + { provide: TranslateService, useValue: {}}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -82,6 +139,9 @@ describe('JournalSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(JournalSearchResultListElementComponent); journalListElementComponent = fixture.componentInstance; @@ -134,6 +194,9 @@ describe('JournalSearchResultListElementComponent', () => { declarations: [JournalSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, + {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + {provide: NotificationsService, useValue: {}}, + {provide: TranslateService, useValue: {}}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -145,6 +208,9 @@ describe('JournalSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(JournalSearchResultListElementComponent); journalListElementComponent = fixture.componentInstance; })); diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts index 9609a9582a..b8f30fe66e 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts @@ -12,6 +12,13 @@ import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../../shared/mocks/translate-loader.mock'; +import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; +import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; +import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; +import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; +import { GroupMock } from '../../../../../shared/testing/group-mock'; +import { hot } from 'jasmine-marbles'; let orgUnitListElementComponent: OrgUnitSearchResultListElementComponent; let fixture: ComponentFixture; @@ -65,6 +72,52 @@ const enviromentNoThumbs = { } }; +const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { + searchByItem: jasmine.createSpy('searchByItem'), +}); + +const supervisionOrder: any = { + id: '1', + type: 'supervisionOrder', + uuid: 'supervision-order-1', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; +const anothersupervisionOrder: any = { + id: '2', + type: 'supervisionOrder', + uuid: 'supervision-order-2', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; + +const pageInfo = new PageInfo(); +const array = [supervisionOrder, anothersupervisionOrder]; +const paginatedList = buildPaginatedList(pageInfo, array); +const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + describe('OrgUnitSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -78,6 +131,8 @@ describe('OrgUnitSearchResultListElementComponent', () => { declarations: [ OrgUnitSearchResultListElementComponent , TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, + {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + {provide: NotificationsService, useValue: {}}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -89,6 +144,9 @@ describe('OrgUnitSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(OrgUnitSearchResultListElementComponent); orgUnitListElementComponent = fixture.componentInstance; @@ -148,6 +206,8 @@ describe('OrgUnitSearchResultListElementComponent', () => { declarations: [OrgUnitSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, + {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + {provide: NotificationsService, useValue: {}}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -165,7 +225,9 @@ describe('OrgUnitSearchResultListElementComponent', () => { describe('with environment.browseBy.showThumbnails set to false', () => { beforeEach(() => { - + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); orgUnitListElementComponent.object = mockItemWithMetadata; fixture.detectChanges(); }); diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts index 31018520f6..3f4cc1d294 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts @@ -12,9 +12,19 @@ import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../../shared/mocks/translate-loader.mock'; +import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; +import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; +import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { hot } from 'jasmine-marbles'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; +import { GroupMock } from '../../../../../shared/testing/group-mock'; +import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; let personListElementComponent: PersonSearchResultListElementComponent; let fixture: ComponentFixture; +const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { + searchByItem: jasmine.createSpy('searchByItem'), +}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), @@ -65,6 +75,48 @@ const enviromentNoThumbs = { } }; +const supervisionOrder: any = { + id: '1', + type: 'supervisionOrder', + uuid: 'supervision-order-1', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; +const anothersupervisionOrder: any = { + id: '2', + type: 'supervisionOrder', + uuid: 'supervision-order-2', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; + +const pageInfo = new PageInfo(); +const array = [supervisionOrder, anothersupervisionOrder]; +const paginatedList = buildPaginatedList(pageInfo, array); +const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + describe('PersonSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -78,6 +130,8 @@ describe('PersonSearchResultListElementComponent', () => { declarations: [PersonSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, + { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + { provide: NotificationsService, useValue: {} }, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -89,6 +143,9 @@ describe('PersonSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(PersonSearchResultListElementComponent); personListElementComponent = fixture.componentInstance; @@ -148,6 +205,8 @@ describe('PersonSearchResultListElementComponent', () => { declarations: [PersonSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, + {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService}, + {provide: NotificationsService, useValue: {}}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -165,7 +224,9 @@ describe('PersonSearchResultListElementComponent', () => { describe('with environment.browseBy.showThumbnails set to false', () => { beforeEach(() => { - + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); personListElementComponent.object = mockItemWithMetadata; fixture.detectChanges(); }); 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 217d7baef9..6f78c709ba 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 @@ -9,6 +9,10 @@ import { import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface'; +import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; @listableObjectComponent('PersonSearchResult', ViewMode.ListElement) @Component({ @@ -24,9 +28,13 @@ export class PersonSearchResultListElementComponent extends ItemSearchResultList public constructor( protected truncatableService: TruncatableService, protected dsoNameService: DSONameService, - @Inject(APP_CONFIG) protected appConfig: AppConfig + @Inject(APP_CONFIG) protected appConfig: AppConfig, + protected supervisionOrderDataService: SupervisionOrderDataService, + protected modalService: NgbModal, + protected notificationsService: NotificationsService, + protected translateService: TranslateService, ) { - super(truncatableService, dsoNameService, appConfig); + super(truncatableService, dsoNameService, appConfig, supervisionOrderDataService, modalService, notificationsService, translateService); } /** diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts index 0cb3e63e87..f3ef14e4f6 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts @@ -10,6 +10,14 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { By } from '@angular/platform-browser'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; +import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; +import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { PageInfo } from '../../../../../core/shared/page-info.model'; +import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; +import { GroupMock } from '../../../../../shared/testing/group-mock'; +import { hot } from 'jasmine-marbles'; let projectListElementComponent: ProjectSearchResultListElementComponent; let fixture: ComponentFixture; @@ -64,12 +72,61 @@ const enviromentNoThumbs = { } }; +const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { + searchByItem: jasmine.createSpy('searchByItem'), +}); + +const supervisionOrder: any = { + id: '1', + type: 'supervisionOrder', + uuid: 'supervision-order-1', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; +const anothersupervisionOrder: any = { + id: '2', + type: 'supervisionOrder', + uuid: 'supervision-order-2', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; + +const pageInfo = new PageInfo(); +const array = [supervisionOrder, anothersupervisionOrder]; +const paginatedList = buildPaginatedList(pageInfo, array); +const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + describe('ProjectSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ProjectSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, + { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + { provide: NotificationsService, useValue: {}}, + {provide: TranslateService, useValue: {}}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -81,6 +138,9 @@ describe('ProjectSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(ProjectSearchResultListElementComponent); projectListElementComponent = fixture.componentInstance; @@ -133,6 +193,9 @@ describe('ProjectSearchResultListElementComponent', () => { declarations: [ProjectSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, + {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + {provide: NotificationsService, useValue: {}}, + {provide: TranslateService, useValue: {}}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } @@ -151,7 +214,9 @@ describe('ProjectSearchResultListElementComponent', () => { describe('with environment.browseBy.showThumbnails set to false', () => { beforeEach(() => { - + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); projectListElementComponent.object = mockItemWithMetadata; fixture.detectChanges(); }); diff --git a/src/app/my-dspace-page/my-dspace-configuration-value-type.ts b/src/app/my-dspace-page/my-dspace-configuration-value-type.ts index baf2f0b920..52e6b01c11 100644 --- a/src/app/my-dspace-page/my-dspace-configuration-value-type.ts +++ b/src/app/my-dspace-page/my-dspace-configuration-value-type.ts @@ -1,4 +1,5 @@ export enum MyDSpaceConfigurationValueType { Workspace = 'workspace', + SupervisedItems = 'otherworkspace', Workflow = 'workflow' } diff --git a/src/app/my-dspace-page/my-dspace-configuration.service.spec.ts b/src/app/my-dspace-page/my-dspace-configuration.service.spec.ts index 669a97764a..34bfd946b6 100644 --- a/src/app/my-dspace-page/my-dspace-configuration.service.spec.ts +++ b/src/app/my-dspace-page/my-dspace-configuration.service.spec.ts @@ -184,7 +184,8 @@ describe('MyDSpaceConfigurationService', () => { expect(list$).toBeObservable(cold('(b|)', { b: [ - MyDSpaceConfigurationValueType.Workspace + MyDSpaceConfigurationValueType.Workspace, + MyDSpaceConfigurationValueType.SupervisedItems ] })); }); @@ -227,6 +228,7 @@ describe('MyDSpaceConfigurationService', () => { expect(list$).toBeObservable(cold('(b|)', { b: [ MyDSpaceConfigurationValueType.Workspace, + MyDSpaceConfigurationValueType.SupervisedItems, MyDSpaceConfigurationValueType.Workflow ] })); diff --git a/src/app/my-dspace-page/my-dspace-configuration.service.ts b/src/app/my-dspace-page/my-dspace-configuration.service.ts index dec8658910..86bbf0050c 100644 --- a/src/app/my-dspace-page/my-dspace-configuration.service.ts +++ b/src/app/my-dspace-page/my-dspace-configuration.service.ts @@ -20,6 +20,7 @@ import { Context } from '../core/shared/context.model'; export const MyDSpaceConfigurationToContextMap = new Map([ [MyDSpaceConfigurationValueType.Workspace, Context.Workspace], + [MyDSpaceConfigurationValueType.SupervisedItems, Context.SupervisedItems], [MyDSpaceConfigurationValueType.Workflow, Context.Workflow] ]); @@ -107,6 +108,7 @@ export class MyDSpaceConfigurationService extends SearchConfigurationService { const availableConf: MyDSpaceConfigurationValueType[] = []; if (isSubmitter) { availableConf.push(MyDSpaceConfigurationValueType.Workspace); + availableConf.push(MyDSpaceConfigurationValueType.SupervisedItems); } if (isController || isAdmin) { availableConf.push(MyDSpaceConfigurationValueType.Workflow); diff --git a/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.html new file mode 100644 index 0000000000..f6a5c339ce --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.html @@ -0,0 +1,40 @@ +
+ + +
\ No newline at end of file diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.scss b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.scss similarity index 100% rename from src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.scss rename to src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.scss diff --git a/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.spec.ts new file mode 100644 index 0000000000..b5e62ef8ac --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.spec.ts @@ -0,0 +1,70 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { SupervisionGroupSelectorComponent } from './supervision-group-selector.component'; +import { SupervisionOrderDataService } from '../../../../core/supervision-order/supervision-order-data.service'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { Group } from '../../../../core/eperson/models/group.model'; +import { SupervisionOrder } from '../../../../core/supervision-order/models/supervision-order.model'; +import { of } from 'rxjs'; + +describe('SupervisionGroupSelectorComponent', () => { + let component: SupervisionGroupSelectorComponent; + let fixture: ComponentFixture; + let debugElement: DebugElement; + + const modalStub = jasmine.createSpyObj('modalStub', ['close']); + + const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { + create: of(new SupervisionOrder()) + }); + + const selectedOrderType = 'NONE'; + const itemUUID = 'itemUUID1234'; + + const selectedGroup = new Group(); + selectedGroup.uuid = 'GroupUUID1234'; + + const supervisionDataObject = new SupervisionOrder(); + supervisionDataObject.ordertype = selectedOrderType; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [SupervisionGroupSelectorComponent], + providers: [ + { provide: NgbActiveModal, useValue: modalStub }, + { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + { provide: NotificationsService, useValue: {} }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + + })); + + beforeEach(waitForAsync(() => { + fixture = TestBed.createComponent(SupervisionGroupSelectorComponent); + component = fixture.componentInstance; + + })); + + beforeEach(() => { + component.itemUUID = itemUUID; + component.selectedGroup = selectedGroup; + component.selectedOrderType = selectedOrderType; + debugElement = fixture.debugElement; + fixture.detectChanges(); + }); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); + + it('should call create for supervision order', () => { + component.save(); + fixture.detectChanges(); + expect(supervisionOrderDataService.create).toHaveBeenCalledWith(supervisionDataObject, itemUUID, selectedGroup.uuid, selectedOrderType); + }); + +}); diff --git a/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts new file mode 100644 index 0000000000..c69c5d5177 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts @@ -0,0 +1,90 @@ +import { Component } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateService } from '@ngx-translate/core'; +import { getFirstCompletedRemoteData } from 'src/app/core/shared/operators'; +import { NotificationsService } from 'src/app/shared/notifications/notifications.service'; +import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { Group } from '../../../../core/eperson/models/group.model'; +import { SupervisionOrder } from '../../../../core/supervision-order/models/supervision-order.model'; +import { SupervisionOrderDataService } from '../../../../core/supervision-order/supervision-order-data.service'; + +/** + * Component to wrap a dropdown - for type of order - + * and a list of groups + * inside a modal + * Used to create a new supervision order + */ + +@Component({ + selector: 'ds-supervision-group-selector', + styleUrls: ['./supervision-group-selector.component.scss'], + templateUrl: './supervision-group-selector.component.html', +}) +export class SupervisionGroupSelectorComponent { + + /** + * The item to perform the actions on + */ + itemUUID: string; + + /** + * The selected supervision order type + */ + selectedOrderType: string; + + /** + * selected group for supervision + */ + selectedGroup: Group; + + /** + * boolean flag for the validations + */ + isSubmitted = false; + + constructor( + public dsoNameService: DSONameService, + private activeModal: NgbActiveModal, + private supervisionOrderDataService: SupervisionOrderDataService, + protected notificationsService: NotificationsService, + protected translateService: TranslateService, + ) { } + + /** + * Close the modal + */ + close() { + this.activeModal.close(); + } + + /** + * Assign the value of group on select + */ + updateGroupObjectSelected(object) { + this.selectedGroup = object; + } + + /** + * Save the supervision order + */ + save() { + this.isSubmitted = true; + if (this.selectedOrderType && this.selectedGroup) { + let supervisionDataObject = new SupervisionOrder(); + supervisionDataObject.ordertype = this.selectedOrderType; + this.supervisionOrderDataService.create(supervisionDataObject, this.itemUUID, this.selectedGroup.uuid, this.selectedOrderType).pipe( + getFirstCompletedRemoteData(), + ).subscribe(rd => { + if (rd.state === 'Success') { + this.notificationsService.success(this.translateService.get('supervision-group-selector.notification.create.success.title', { name: this.selectedGroup.name })); + } else { + this.notificationsService.error( + this.translateService.get('supervision-group-selector.notification.create.failure.title'), + rd.errorMessage); + } + }); + this.close(); + } + } + +} diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html b/src/app/shared/form/eperson-group-list/eperson-group-list.component.html similarity index 100% rename from src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.html rename to src/app/shared/form/eperson-group-list/eperson-group-list.component.html diff --git a/src/app/shared/form/eperson-group-list/eperson-group-list.component.scss b/src/app/shared/form/eperson-group-list/eperson-group-list.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts b/src/app/shared/form/eperson-group-list/eperson-group-list.component.spec.ts similarity index 88% rename from src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts rename to src/app/shared/form/eperson-group-list/eperson-group-list.component.spec.ts index cec67e721c..399080c0ef 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.spec.ts +++ b/src/app/shared/form/eperson-group-list/eperson-group-list.component.spec.ts @@ -6,21 +6,21 @@ import { TranslateModule } from '@ngx-translate/core'; import { cold } from 'jasmine-marbles'; import uniqueId from 'lodash/uniqueId'; -import { createSuccessfulRemoteDataObject } from '../../../remote-data.utils'; -import { createTestComponent } from '../../../testing/utils.test'; -import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; -import { GroupDataService } from '../../../../core/eperson/group-data.service'; -import { RequestService } from '../../../../core/data/request.service'; -import { getMockRequestService } from '../../../mocks/request.service.mock'; +import { createSuccessfulRemoteDataObject } from '../../remote-data.utils'; +import { createTestComponent } from '../../testing/utils.test'; +import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; +import { GroupDataService } from '../../../core/eperson/group-data.service'; +import { RequestService } from '../../../core/data/request.service'; +import { getMockRequestService } from '../../mocks/request.service.mock'; import { EpersonGroupListComponent, SearchEvent } from './eperson-group-list.component'; -import { EPersonMock } from '../../../testing/eperson.mock'; -import { GroupMock } from '../../../testing/group-mock'; -import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model'; -import { buildPaginatedList } from '../../../../core/data/paginated-list.model'; -import { PageInfo } from '../../../../core/shared/page-info.model'; +import { EPersonMock } from '../../testing/eperson.mock'; +import { GroupMock } from '../../testing/group-mock'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { buildPaginatedList } from '../../../core/data/paginated-list.model'; +import { PageInfo } from '../../../core/shared/page-info.model'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { PaginationServiceStub } from '../../../testing/pagination-service.stub'; +import { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../testing/pagination-service.stub'; describe('EpersonGroupListComponent test suite', () => { let comp: EpersonGroupListComponent; diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts b/src/app/shared/form/eperson-group-list/eperson-group-list.component.ts similarity index 81% rename from src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts rename to src/app/shared/form/eperson-group-list/eperson-group-list.component.ts index b859184845..e2b56859dc 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts +++ b/src/app/shared/form/eperson-group-list/eperson-group-list.component.ts @@ -4,22 +4,22 @@ import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { map } from 'rxjs/operators'; import uniqueId from 'lodash/uniqueId'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { PaginatedList } from '../../../../core/data/paginated-list.model'; -import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; -import { PaginationComponentOptions } from '../../../pagination/pagination-component-options.model'; -import { hasValue, isNotEmpty } from '../../../empty.util'; -import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; -import { EPERSON } from '../../../../core/eperson/models/eperson.resource-type'; -import { GROUP } from '../../../../core/eperson/models/group.resource-type'; -import { ResourceType } from '../../../../core/shared/resource-type'; -import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; -import { GroupDataService } from '../../../../core/eperson/group-data.service'; -import { fadeInOut } from '../../../animations/fade'; -import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { FindListOptions } from '../../../../core/data/find-list-options.model'; -import { getDataServiceFor } from '../../../../core/data/base/data-service.decorator'; +import { RemoteData } from '../../../core/data/remote-data'; +import { PaginatedList } from '../../../core/data/paginated-list.model'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; +import { PaginationComponentOptions } from '../../pagination/pagination-component-options.model'; +import { hasValue, isNotEmpty } from '../../empty.util'; +import { DSONameService } from '../../../core/breadcrumbs/dso-name.service'; +import { EPERSON } from '../../../core/eperson/models/eperson.resource-type'; +import { GROUP } from '../../../core/eperson/models/group.resource-type'; +import { ResourceType } from '../../../core/shared/resource-type'; +import { EPersonDataService } from '../../../core/eperson/eperson-data.service'; +import { GroupDataService } from '../../../core/eperson/group-data.service'; +import { fadeInOut } from '../../animations/fade'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { PaginationService } from '../../../core/pagination/pagination.service'; +import { FindListOptions } from '../../../core/data/find-list-options.model'; +import { getDataServiceFor } from '../../../core/data/base/data-service.decorator'; export interface SearchEvent { scope: string; diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.html b/src/app/shared/form/eperson-group-list/eperson-search-box/eperson-search-box.component.html similarity index 100% rename from src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.html rename to src/app/shared/form/eperson-group-list/eperson-search-box/eperson-search-box.component.html diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts b/src/app/shared/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts similarity index 97% rename from src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts rename to src/app/shared/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts index 311d911b26..db16af5edf 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts +++ b/src/app/shared/form/eperson-group-list/eperson-search-box/eperson-search-box.component.spec.ts @@ -4,7 +4,7 @@ import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { createTestComponent } from '../../../../testing/utils.test'; +import { createTestComponent } from '../../../testing/utils.test'; import { EpersonSearchBoxComponent } from './eperson-search-box.component'; import { SearchEvent } from '../eperson-group-list.component'; diff --git a/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts b/src/app/shared/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts similarity index 96% rename from src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts rename to src/app/shared/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts index 11c3dcd102..6ae3200d0b 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts +++ b/src/app/shared/form/eperson-group-list/eperson-search-box/eperson-search-box.component.ts @@ -4,7 +4,7 @@ import { FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs'; import { SearchEvent } from '../eperson-group-list.component'; -import { isNotNull } from '../../../../empty.util'; +import { isNotNull } from '../../../empty.util'; /** * A component used to show a search box for epersons. diff --git a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.html b/src/app/shared/form/eperson-group-list/group-search-box/group-search-box.component.html similarity index 100% rename from src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.html rename to src/app/shared/form/eperson-group-list/group-search-box/group-search-box.component.html diff --git a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts b/src/app/shared/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts similarity index 97% rename from src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts rename to src/app/shared/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts index dc39e30d3b..17e96fc71c 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts +++ b/src/app/shared/form/eperson-group-list/group-search-box/group-search-box.component.spec.ts @@ -4,7 +4,7 @@ import { FormBuilder, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { TranslateModule } from '@ngx-translate/core'; -import { createTestComponent } from '../../../../testing/utils.test'; +import { createTestComponent } from '../../../testing/utils.test'; import { GroupSearchBoxComponent } from './group-search-box.component'; import { SearchEvent } from '../eperson-group-list.component'; diff --git a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts b/src/app/shared/form/eperson-group-list/group-search-box/group-search-box.component.ts similarity index 96% rename from src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts rename to src/app/shared/form/eperson-group-list/group-search-box/group-search-box.component.ts index baef19f14a..ad181bdb64 100644 --- a/src/app/shared/resource-policies/form/eperson-group-list/group-search-box/group-search-box.component.ts +++ b/src/app/shared/form/eperson-group-list/group-search-box/group-search-box.component.ts @@ -4,7 +4,7 @@ import { FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs'; import { SearchEvent } from '../eperson-group-list.component'; -import { isNotNull } from '../../../../empty.util'; +import { isNotNull } from '../../../empty.util'; /** * A component used to show a search box for groups. diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html index 6b2951495d..64108681f6 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html @@ -41,6 +41,19 @@ [innerHTML]="firstMetadataValue('dc.description.abstract')"> + +
+
+ {{'item.search.result.list.element.supervised-by' | translate}} +
+
+ + {{supervisionOrder.group._name}} + X + +
+
+
diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.scss b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.scss index 5e4536cf95..0d93ec744b 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.scss +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.scss @@ -1 +1,7 @@ @import '../../../../../../../styles/variables'; + +.item-list-supervision { + a { + cursor: pointer; + } +} \ No newline at end of file diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts index d1e6c27ba4..887ecb30a0 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts @@ -10,6 +10,14 @@ import { ItemSearchResult } from '../../../../../object-collection/shared/item-s import { DSONameService } from '../../../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock, UNDEFINED_NAME } from '../../../../../mocks/dso-name.service.mock'; import { APP_CONFIG } from '../../../../../../../config/app-config.interface'; +import { SupervisionOrderDataService } from '../../../../../../core/supervision-order/supervision-order-data.service'; +import { NotificationsService } from '../../../../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { createSuccessfulRemoteDataObject } from '../../../../../../shared/remote-data.utils'; +import { PageInfo } from '../../../../../../core/shared/page-info.model'; +import { buildPaginatedList } from '../../../../../../core/data/paginated-list.model'; +import { GroupMock } from '../../../../../../shared/testing/group-mock'; +import { hot } from 'jasmine-marbles'; let publicationListElementComponent: ItemSearchResultListElementComponent; let fixture: ComponentFixture; @@ -72,12 +80,61 @@ const enviromentNoThumbs = { } }; +const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { + searchByItem: jasmine.createSpy('searchByItem'), +}); + +const supervisionOrder: any = { + id: '1', + type: 'supervisionOrder', + uuid: 'supervision-order-1', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; +const anothersupervisionOrder: any = { + id: '2', + type: 'supervisionOrder', + uuid: 'supervision-order-2', + _links: { + item: { + href: 'https://rest.api/rest/api/eperson' + }, + group: { + href: 'https://rest.api/rest/api/group' + }, + self: { + href: 'https://rest.api/rest/api/supervisionorders/1' + }, + }, + item: observableOf(createSuccessfulRemoteDataObject({})), + group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) +}; + +const pageInfo = new PageInfo(); +const array = [supervisionOrder, anothersupervisionOrder]; +const paginatedList = buildPaginatedList(pageInfo, array); +const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + describe('ItemSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ItemSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, + { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + { provide: NotificationsService, useValue: {}}, + { provide: TranslateService, useValue: {}}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -89,6 +146,9 @@ describe('ItemSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(ItemSearchResultListElementComponent); publicationListElementComponent = fixture.componentInstance; @@ -225,6 +285,9 @@ describe('ItemSearchResultListElementComponent', () => { declarations: [ItemSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, + {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, + {provide: NotificationsService, useValue: {}}, + {provide: TranslateService, useValue: {}}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -236,6 +299,9 @@ describe('ItemSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { + supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { + a: paginatedListRD + })); fixture = TestBed.createComponent(ItemSearchResultListElementComponent); publicationListElementComponent = fixture.componentInstance; })); diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts index f84ae642ad..b1b5b0d1a7 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts @@ -1,10 +1,24 @@ -import { Component } from '@angular/core'; +import { Component, Inject } from '@angular/core'; import { listableObjectComponent } from '../../../../../object-collection/shared/listable-object/listable-object.decorator'; import { ViewMode } from '../../../../../../core/shared/view-mode.model'; import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model'; import { SearchResultListElementComponent } from '../../../search-result-list-element.component'; import { Item } from '../../../../../../core/shared/item.model'; import { getItemPageRoute } from '../../../../../../item-page/item-page-routing-paths'; +import { SupervisionOrderDataService } from '../../../../../../core/supervision-order/supervision-order-data.service'; +import { TruncatableService } from '../../../../../../shared/truncatable/truncatable.service'; +import { DSONameService } from '../../../../../../core/breadcrumbs/dso-name.service'; +import { AppConfig, APP_CONFIG } from '../../../../../../../config/app-config.interface'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { combineLatest, map, Observable, switchMap, take } from 'rxjs'; +import { ConfirmationModalComponent } from '../../../../../../shared/confirmation-modal/confirmation-modal.component'; +import { hasValue } from '../../../../../../shared/empty.util'; +import { NotificationsService } from '../../../../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { followLink } from '../../../../../../shared/utils/follow-link-config.model'; +import { getAllSucceededRemoteListPayload, getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators'; +import { SupervisionOrder } from '../../../../../../core/supervision-order/models/supervision-order.model'; +import { Group } from '../../../../../../core/eperson/models/group.model'; @listableObjectComponent('PublicationSearchResult', ViewMode.ListElement) @listableObjectComponent(ItemSearchResult, ViewMode.ListElement) @@ -17,6 +31,8 @@ import { getItemPageRoute } from '../../../../../../item-page/item-page-routing- * The component for displaying a list element for an item search result of the type Publication */ export class ItemSearchResultListElementComponent extends SearchResultListElementComponent { + messagePrefix = 'item.search.result'; + /** * Route to the item's page */ @@ -27,9 +43,67 @@ export class ItemSearchResultListElementComponent extends SearchResultListElemen */ showThumbnails: boolean; + /** + * List of the supervision orders combined with the group + */ + supervisionOrder$: Observable<{ supervisionOrder: SupervisionOrder; group: Group; }[]>; + + constructor( + protected truncatableService: TruncatableService, + protected dsoNameService: DSONameService, + @Inject(APP_CONFIG) protected appConfig: AppConfig, + protected supervisionOrderDataService: SupervisionOrderDataService, + protected modalService: NgbModal, + protected notificationsService: NotificationsService, + protected translateService: TranslateService, + ) { super(truncatableService, dsoNameService, appConfig); } + ngOnInit(): void { super.ngOnInit(); this.showThumbnails = this.appConfig.browseBy.showThumbnails; this.itemPageRoute = getItemPageRoute(this.dso); + this.supervisionOrder$ = this.supervisionOrderDataService.searchByItem(this.dso.uuid, null, null, followLink('group')).pipe( + getAllSucceededRemoteListPayload(), + switchMap((supervisionOrders: SupervisionOrder[]) => { + const supervisionOrdersArray = supervisionOrders.map((supervisionOrder: SupervisionOrder) => { + return supervisionOrder.group.pipe( + getFirstSucceededRemoteDataPayload(), + map((group: Group) => ({ supervisionOrder, group })) + ); + }); + return combineLatest(supervisionOrdersArray); + }) + ); + } + + /** + * Deletes the Group from the Repository. The Group will be the only that this form is showing. + * It'll either show a success or error message depending on whether the delete was successful or not. + */ + deleteSupervisionOrder(supervisionOrder) { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.dso = supervisionOrder.group; + modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-supervision.modal.header'; + modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-supervision.modal.info'; + modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-supervision.modal.cancel'; + modalRef.componentInstance.confirmLabel = this.messagePrefix + '.delete-supervision.modal.confirm'; + modalRef.componentInstance.brandColor = 'danger'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { + if (confirm) { + if (hasValue(supervisionOrder.supervisionOrder.id)) { + this.supervisionOrderDataService.delete(supervisionOrder.supervisionOrder.id) + .subscribe((rd: boolean) => { + if (rd) { + this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.deleted.success', { name: supervisionOrder.group._name })); + } else { + this.notificationsService.error( + this.translateService.get(this.messagePrefix + '.notification.deleted.failure.title', { name: supervisionOrder.group._name }), + this.translateService.get(this.messagePrefix + '.notification.deleted.failure.content')); + } + }); + } + } + }); } } 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 e555522c79..1a84270f1b 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 @@ -24,7 +24,7 @@ import { ResourcePolicyEvent, ResourcePolicyFormComponent } from './resource-pol import { FormService } from '../../form/form.service'; import { getMockFormService } from '../../mocks/form-service.mock'; import { FormBuilderService } from '../../form/builder/form-builder.service'; -import { EpersonGroupListComponent } from './eperson-group-list/eperson-group-list.component'; +import { EpersonGroupListComponent } from '../../form/eperson-group-list/eperson-group-list.component'; import { FormComponent } from '../../form/form.component'; import { stringToNgbDateStruct, dateToISOFormat } from '../../date.util'; import { ResourcePolicy } from '../../../core/resource-policy/models/resource-policy.model'; diff --git a/src/app/shared/resource-policies/resource-policies.module.ts b/src/app/shared/resource-policies/resource-policies.module.ts index a7dc19b83e..8a7f6dca79 100644 --- a/src/app/shared/resource-policies/resource-policies.module.ts +++ b/src/app/shared/resource-policies/resource-policies.module.ts @@ -10,9 +10,6 @@ import { ResourcePolicyCreateComponent } from './create/resource-policy-create.c import { FormModule } from '../form/form.module'; import { ResourcePolicyResolver } from './resolvers/resource-policy.resolver'; import { ResourcePolicyTargetResolver } from './resolvers/resource-policy-target.resolver'; -import { EpersonGroupListComponent } from './form/eperson-group-list/eperson-group-list.component'; -import { GroupSearchBoxComponent } from './form/eperson-group-list/group-search-box/group-search-box.component'; -import { EpersonSearchBoxComponent } from './form/eperson-group-list/eperson-search-box/eperson-search-box.component'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { SharedModule } from '../shared.module'; import { ResourcePolicyEntryComponent } from './entry/resource-policy-entry.component'; @@ -23,9 +20,6 @@ const COMPONENTS = [ ResourcePolicyFormComponent, ResourcePolicyEditComponent, ResourcePolicyCreateComponent, - EpersonGroupListComponent, - EpersonSearchBoxComponent, - GroupSearchBoxComponent ]; const PROVIDERS = [ diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index c094e37ef2..e9a72ae338 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -34,6 +34,7 @@ import { CollectionElementLinkType } from '../object-collection/collection-eleme import { environment } from 'src/environments/environment'; import { SubmissionObject } from '../../core/submission/models/submission-object.model'; import { SearchFilterConfig } from './models/search-filter-config.model'; +import { WorkspaceItem } from '../..//core/submission/models/workspaceitem.model'; @Component({ selector: 'ds-search', @@ -405,8 +406,9 @@ export class SearchComponent implements OnInit { true, followLink('thumbnail', { isOptional: true }), followLink('item', { isOptional: true }, followLink('thumbnail', { isOptional: true })) as any, - followLink('accessStatus', { isOptional: true, shouldEmbed: environment.item.showAccessStatuses }) - ).pipe(getFirstCompletedRemoteData()) + followLink('supervisionOrders', { isOptional: true }) as any, + followLink('accessStatus', { isOptional: true, shouldEmbed: environment.item.showAccessStatuses }), + ).pipe(getFirstCompletedRemoteData()) .subscribe((results: RemoteData>) => { if (results.hasSucceeded) { if (this.trackStatistics) { diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 13fdd3e12c..bd87b35c82 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -315,6 +315,10 @@ import { MenuModule } from './menu/menu.module'; import { ListableNotificationObjectComponent } from './object-list/listable-notification-object/listable-notification-object.component'; +import { SupervisionGroupSelectorComponent } from './dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component'; +import { EpersonGroupListComponent } from './form/eperson-group-list/eperson-group-list.component'; +import { EpersonSearchBoxComponent } from './form/eperson-group-list/eperson-search-box/eperson-search-box.component'; +import { GroupSearchBoxComponent } from './form/eperson-group-list/group-search-box/group-search-box.component'; const MODULES = [ CommonModule, @@ -436,6 +440,10 @@ const COMPONENTS = [ ItemPageTitleFieldComponent, ThemedSearchNavbarComponent, ListableNotificationObjectComponent, + EpersonGroupListComponent, + EpersonSearchBoxComponent, + GroupSearchBoxComponent, + SupervisionGroupSelectorComponent ]; const ENTRY_COMPONENTS = [ @@ -498,6 +506,10 @@ const ENTRY_COMPONENTS = [ CommunitySidebarSearchListElementComponent, ScopeSelectorModalComponent, ListableNotificationObjectComponent, + EpersonGroupListComponent, + EpersonSearchBoxComponent, + GroupSearchBoxComponent, + SupervisionGroupSelectorComponent ]; const SHARED_ITEM_PAGE_COMPONENTS = [ diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index d043d61ae5..26f9983c17 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -536,10 +536,16 @@ "admin.workflow.item.workflow": "Workflow", + "admin.workflow.item.workspace": "Workspace", + "admin.workflow.item.delete": "Delete", "admin.workflow.item.send-back": "Send back", + "admin.workflow.item.policies": "Policies", + + "admin.workflow.item.supervision": "Supervision", + "admin.metadata-import.breadcrumbs": "Import Metadata", @@ -1418,6 +1424,28 @@ "dso-selector.results-could-not-be-retrieved": "Something went wrong, please refresh again ↻", + "supervision-group-selector.header": "Supervision Group Selector", + + "supervision-group-selector.select.type-of-order.label": "Select a type of Order", + + "supervision-group-selector.select.type-of-order.option.none": "NONE", + + "supervision-group-selector.select.type-of-order.option.editor": "EDITOR", + + "supervision-group-selector.select.type-of-order.option.observer": "OBSERVER", + + "supervision-group-selector.select.group.label": "Select a Group", + + "supervision-group-selector.button.save": "Save", + + "supervision-group-selector.select.type-of-order.error": "Please select a type of order", + + "supervision-group-selector.select.group.error": "Please select a group", + + "supervision-group-selector.notification.create.success.title": "Successfully created supervision order for group {{ name }}", + + "supervision-group-selector.notification.create.failure.title": "Error", + "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", "confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}", @@ -2222,6 +2250,22 @@ "item.truncatable-part.show-less": "Collapse", + "item.search.result.delete-supervision.modal.header": "Delete Supervision Order", + + "item.search.result.delete-supervision.modal.info": "Are you sure you want to delete Supervision Order", + + "item.search.result.delete-supervision.modal.cancel": "Cancel", + + "item.search.result.delete-supervision.modal.confirm": "Delete", + + "item.search.result.notification.deleted.success": "Successfully deleted supervision order \"{{name}}\"", + + "item.search.result.notification.deleted.failure.title": "Failed to delete supervision order \"{{name}}\"", + + "item.search.result.notification.deleted.failure.content": "Failed to delete supervision order", + + "item.search.result.list.element.supervised-by": "Supervised by:", + "item.page.abstract": "Abstract", @@ -2851,6 +2895,8 @@ "mydspace.show.workspace": "Your Submissions", + "mydspace.show.otherworkspace": "Supervised items", + "mydspace.status.archived": "Archived", "mydspace.status.validation": "Validation", @@ -3640,6 +3686,12 @@ "search.filters.filter.submitter.label": "Search submitter", + "search.filters.filter.supervisedBy.head": "Supervised By", + + "search.filters.filter.supervisedBy.placeholder": "Supervised By", + + "search.filters.filter.supervisedBy.label": "Search Supervised By", + "search.filters.entityType.JournalIssue": "Journal Issue", @@ -4534,12 +4586,16 @@ + "otherworkspace.search.results.head": "Supervised Items", + "workspace.search.results.head": "Your submissions", "workflowAdmin.search.results.head": "Administer Workflow", "workflow.search.results.head": "Workflow tasks", + "supervision.search.results.head": "Supervision tasks", + "workflow-item.edit.breadcrumbs": "Edit workflowitem", From 710b11655d99ce72a47fe2328c2c716a201d27e1 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 29 Dec 2022 18:46:19 +0100 Subject: [PATCH 032/173] [CST-7757] Rename type property to subscriptionType --- .../subscription-modal.component.spec.ts | 6 +++--- .../subscription-modal/subscription-modal.component.ts | 8 ++++---- .../subscription-view/subscription-view.component.spec.ts | 6 +++--- .../subscription-view/subscription-view.component.ts | 4 ++-- ...scription.service.ts => subscriptions-data.service.ts} | 4 ++-- .../subscriptions-page.component.spec.ts | 6 +++--- .../subscriptions-page/subscriptions-page.component.ts | 5 ++--- 7 files changed, 19 insertions(+), 20 deletions(-) rename src/app/shared/subscriptions/{subscription.service.ts => subscriptions-data.service.ts} (97%) diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts index 05fa1c4a77..221f70f77d 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts @@ -8,7 +8,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { SubscriptionModalComponent } from './subscription-modal.component'; import { TranslateLoaderMock } from '../../mocks/translate-loader.mock'; import { NotificationsService } from '../../notifications/notifications.service'; -import { SubscriptionService } from '../subscription.service'; +import { SubscriptionsDataService } from '../subscriptions-data.service'; import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; import { Item } from '../../../core/shared/item.model'; import { AuthService } from '../../../core/auth/auth.service'; @@ -67,7 +67,7 @@ describe('SubscriptionModalComponent', () => { getAuthenticatedUserFromStore: createSuccessfulRemoteDataObject$(mockEperson) }); - subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { + subscriptionServiceStub = jasmine.createSpyObj('SubscriptionsDataService', { getSubscriptionsByPersonDSO: jasmine.createSpy('getSubscriptionsByPersonDSO'), createSubscription: createSuccessfulRemoteDataObject$({}), updateSubscription: createSuccessfulRemoteDataObject$({}), @@ -92,7 +92,7 @@ describe('SubscriptionModalComponent', () => { NgbActiveModal, { provide: AuthService, useValue: authService }, { provide: NotificationsService, useValue: notificationServiceStub }, - { provide: SubscriptionService, useValue: subscriptionServiceStub }, + { provide: SubscriptionsDataService, useValue: subscriptionServiceStub }, ], schemas: [ NO_ERRORS_SCHEMA diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts index 23faf33c54..c7ff9882ac 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts @@ -10,7 +10,7 @@ import findIndex from 'lodash/findIndex'; import { Subscription } from '../models/subscription.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; -import { SubscriptionService } from '../subscription.service'; +import { SubscriptionsDataService } from '../subscriptions-data.service'; import { NotificationsService } from '../../notifications/notifications.service'; import { PaginatedList } from '../../../core/data/paginated-list.model'; import { RemoteData } from '../../../core/data/remote-data'; @@ -59,7 +59,7 @@ export class SubscriptionModalComponent implements OnInit { /** * Types of subscription to be shown on select */ - private subscriptionDefaultTypes = ['content', 'statistics']; + private subscriptionDefaultTypes = ['content']; /** * Frequencies to be shown as checkboxes @@ -75,7 +75,7 @@ export class SubscriptionModalComponent implements OnInit { private formBuilder: FormBuilder, private modalService: NgbModal, private notificationsService: NotificationsService, - private subscriptionService: SubscriptionService, + private subscriptionService: SubscriptionsDataService, public activeModal: NgbActiveModal, private authService: AuthService, private translate: TranslateService, @@ -239,7 +239,7 @@ export class SubscriptionModalComponent implements OnInit { private createBody(subscriptionId: string, subscriptionType: string, frequencies: FormGroup): Partial { const body = { id: (isNotEmpty(subscriptionId) ? subscriptionId : null), - type: subscriptionType, + subscriptionType: subscriptionType, subscriptionParameterList: [] }; diff --git a/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts b/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts index 3ec826d6c5..a0caba97ee 100644 --- a/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts +++ b/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts @@ -19,7 +19,7 @@ import { findByEPersonAndDsoResEmpty, subscriptionMock } from '../../testing/sub // Import utils import { NotificationsService } from '../../notifications/notifications.service'; import { NotificationsServiceStub } from '../../testing/notifications-service.stub'; -import { SubscriptionService } from '../subscription.service'; +import { SubscriptionsDataService } from '../subscriptions-data.service'; import { Subscription } from '../models/subscription.model'; import { of as observableOf } from 'rxjs'; @@ -34,7 +34,7 @@ describe('SubscriptionViewComponent', () => { let de: DebugElement; let modalService; - const subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { + const subscriptionServiceStub = jasmine.createSpyObj('SubscriptionsDataService', { getSubscriptionByPersonDSO: observableOf(findByEPersonAndDsoResEmpty), deleteSubscription: createSuccessfulRemoteDataObject$({}), updateSubscription: createSuccessfulRemoteDataObject$({}), @@ -73,7 +73,7 @@ describe('SubscriptionViewComponent', () => { providers: [ { provide: ComponentFixtureAutoDetect, useValue: true }, { provide: NotificationsService, useValue: NotificationsServiceStub }, - { provide: SubscriptionService, useValue: subscriptionServiceStub }, + { provide: SubscriptionsDataService, useValue: subscriptionServiceStub }, ] }) .compileComponents(); diff --git a/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts b/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts index b8aa4d2375..9f7fca7caf 100644 --- a/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts +++ b/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts @@ -7,7 +7,7 @@ import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { hasValue } from '../../empty.util'; import { ConfirmationModalComponent } from '../../confirmation-modal/confirmation-modal.component'; -import { SubscriptionService } from '../subscription.service'; +import { SubscriptionsDataService } from '../subscriptions-data.service'; import { getCommunityModuleRoute } from '../../../community-page/community-page-routing-paths'; import { getCollectionModuleRoute } from '../../../collection-page/collection-page-routing-paths'; import { getItemModuleRoute } from '../../../item-page/item-page-routing-paths'; @@ -48,7 +48,7 @@ export class SubscriptionViewComponent { constructor( private modalService: NgbModal, - private subscriptionService: SubscriptionService, + private subscriptionService: SubscriptionsDataService, ) { } /** diff --git a/src/app/shared/subscriptions/subscription.service.ts b/src/app/shared/subscriptions/subscriptions-data.service.ts similarity index 97% rename from src/app/shared/subscriptions/subscription.service.ts rename to src/app/shared/subscriptions/subscriptions-data.service.ts index 480598a15a..9576d98536 100644 --- a/src/app/shared/subscriptions/subscription.service.ts +++ b/src/app/shared/subscriptions/subscriptions-data.service.ts @@ -41,7 +41,7 @@ import { followLink } from '../utils/follow-link-config.model'; providedIn: 'root' }) @dataService(SUBSCRIPTION) -export class SubscriptionService extends IdentifiableDataService { +export class SubscriptionsDataService extends IdentifiableDataService { protected findByEpersonLinkPath = 'findByEPerson'; private deleteData: DeleteDataImpl; @@ -89,7 +89,7 @@ export class SubscriptionService extends IdentifiableDataService { * @param ePerson The ePerson to create for * @param uuid The uuid of the dsobjcet to create for */ - createSubscription(subscription, ePerson: string, uuid: string): Observable> { + createSubscription(subscription: Subscription, ePerson: string, uuid: string): Observable> { return this.halService.getEndpoint(this.linkPath).pipe( isNotEmptyOperator(), diff --git a/src/app/subscriptions-page/subscriptions-page.component.spec.ts b/src/app/subscriptions-page/subscriptions-page.component.spec.ts index 9b17d72deb..4f44392428 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.spec.ts +++ b/src/app/subscriptions-page/subscriptions-page.component.spec.ts @@ -11,7 +11,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { SubscriptionsPageComponent } from './subscriptions-page.component'; import { PaginationService } from '../core/pagination/pagination.service'; -import { SubscriptionService } from '../shared/subscriptions/subscription.service'; +import { SubscriptionsDataService } from '../shared/subscriptions/subscriptions-data.service'; import { PaginationServiceStub } from '../shared/testing/pagination-service.stub'; import { AuthService } from '../core/auth/auth.service'; import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock'; @@ -36,7 +36,7 @@ describe('SubscriptionsPageComponent', () => { getAuthenticatedUserFromStore: observableOf(mockSubscriptionEperson) }); - const subscriptionServiceStub = jasmine.createSpyObj('SubscriptionService', { + const subscriptionServiceStub = jasmine.createSpyObj('SubscriptionsDataService', { findByEPerson: jasmine.createSpy('findByEPerson') }); @@ -68,7 +68,7 @@ describe('SubscriptionsPageComponent', () => { ], declarations: [SubscriptionsPageComponent, SubscriptionViewComponent, VarDirective], providers: [ - { provide: SubscriptionService, useValue: subscriptionServiceStub }, + { provide: SubscriptionsDataService, useValue: subscriptionServiceStub }, { provide: ActivatedRoute, useValue: new MockActivatedRoute() }, { provide: AuthService, useValue: authServiceStub }, { provide: PaginationService, useValue: paginationService } diff --git a/src/app/subscriptions-page/subscriptions-page.component.ts b/src/app/subscriptions-page/subscriptions-page.component.ts index 242d5145ac..7307218d0a 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.ts +++ b/src/app/subscriptions-page/subscriptions-page.component.ts @@ -5,7 +5,7 @@ import { map, switchMap, take, tap } from 'rxjs/operators'; import { Subscription } from '../shared/subscriptions/models/subscription.model'; import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model'; -import { SubscriptionService } from '../shared/subscriptions/subscription.service'; +import { SubscriptionsDataService } from '../shared/subscriptions/subscriptions-data.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginationService } from '../core/pagination/pagination.service'; import { PageInfo } from '../core/shared/page-info.model'; @@ -54,7 +54,7 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { constructor( private paginationService: PaginationService, private authService: AuthService, - private subscriptionService: SubscriptionService + private subscriptionService: SubscriptionsDataService ) { } @@ -78,7 +78,6 @@ export class SubscriptionsPageComponent implements OnInit, OnDestroy { */ private retrieveSubscriptions(): void { this.sub = this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( - tap(console.log), combineLatestWith(this.ePersonId$), tap(() => this.loading$.next(true)), switchMap(([currentPagination, ePersonId]) => this.subscriptionService.findByEPerson(ePersonId,{ From 928431284f2a49b0a466c3ddc55a3cdbc88f1690 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 30 Dec 2022 10:12:15 +0100 Subject: [PATCH 033/173] [CST-7217] Improve fix --- .../search-facet-option/search-facet-option.component.html | 6 +++--- .../search-facet-option/search-facet-option.component.scss | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html index bf165274d5..967f0ff0d8 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html @@ -2,9 +2,9 @@ [tabIndex]="-1" [routerLink]="[searchLink]" [queryParams]="addQueryParams" queryParamsHandling="merge"> -
{{'subscriptions.table.dso' | translate}}{{'subscriptions.table.dso' | translate}} {{'subscriptions.table.subscription_type' | translate}} {{'subscriptions.table.subscription_frequency' | translate}} {{'subscriptions.table.action' | translate}}
+ + + + + + + + + + + + + + + + +
{{messagePrefix + '.table.id' | translate}}{{messagePrefix + '.table.name' | translate}}{{messagePrefix + '.table.identity' | translate}}{{messagePrefix + '.table.edit' | translate}}
{{ePerson.eperson.id}}{{ePerson.eperson.name}} + {{messagePrefix + '.table.email' | translate}}: {{ ePerson.eperson.email ? ePerson.eperson.email : '-' }}
+ {{messagePrefix + '.table.netid' | translate}}: {{ ePerson.eperson.netid ? ePerson.eperson.netid : '-' }} +
+
+ + + +
+
+
+ +
+ + + +

{{messagePrefix + '.headMembers' | translate}}

+ + + +
+ + + + + + + + + + + + + + + + + +
{{messagePrefix + '.table.id' | translate}}{{messagePrefix + '.table.name' | translate}}{{messagePrefix + '.table.identity' | translate}}{{messagePrefix + '.table.edit' | translate}}
{{ePerson.eperson.id}}{{ePerson.eperson.name}} + {{messagePrefix + '.table.email' | translate}}: {{ ePerson.eperson.email ? ePerson.eperson.email : '-' }}
+ {{messagePrefix + '.table.netid' | translate}}: {{ ePerson.eperson.netid ? ePerson.eperson.netid : '-' }} +
+
+ + +
+
+
+ +
+ + + +
diff --git a/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.spec.ts b/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.spec.ts new file mode 100644 index 0000000000..8077139026 --- /dev/null +++ b/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.spec.ts @@ -0,0 +1,247 @@ +import { CommonModule } from '@angular/common'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { BrowserModule, By } from '@angular/platform-browser'; +import { Router } from '@angular/router'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { Observable, of as observableOf } from 'rxjs'; +import { RestResponse } from '../../../../core/cache/response.models'; +import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; +import { GroupDataService } from '../../../../core/eperson/group-data.service'; +import { EPerson } from '../../../../core/eperson/models/eperson.model'; +import { Group } from '../../../../core/eperson/models/group.model'; +import { PageInfo } from '../../../../core/shared/page-info.model'; +import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock'; +import { EPersonListComponent } from './eperson-list.component'; +import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock'; +import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; +import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock'; +import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; +import { RouterMock } from '../../../../shared/mocks/router.mock'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; + +describe('EPersonListComponent', () => { + let component: EPersonListComponent; + let fixture: ComponentFixture; + let translateService: TranslateService; + let builderService: FormBuilderService; + let ePersonDataServiceStub: any; + let groupsDataServiceStub: any; + let activeGroup; + let allEPersons; + let allGroups; + let epersonMembers; + let subgroupMembers; + let paginationService; + + beforeEach(waitForAsync(() => { + activeGroup = GroupMock; + epersonMembers = [EPersonMock2]; + subgroupMembers = [GroupMock2]; + allEPersons = [EPersonMock, EPersonMock2]; + allGroups = [GroupMock, GroupMock2]; + ePersonDataServiceStub = { + activeGroup: activeGroup, + epersonMembers: epersonMembers, + subgroupMembers: subgroupMembers, + findAllByHref(href: string): Observable>> { + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupsDataServiceStub.getEPersonMembers())); + }, + searchByScope(scope: string, query: string): Observable>> { + if (query === '') { + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allEPersons)); + } + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])); + }, + clearEPersonRequests() { + // empty + }, + clearLinkRequests() { + // empty + }, + getEPeoplePageRouterLink(): string { + return '/access-control/epeople'; + } + }; + groupsDataServiceStub = { + activeGroup: activeGroup, + epersonMembers: epersonMembers, + subgroupMembers: subgroupMembers, + allGroups: allGroups, + getActiveGroup(): Observable { + return observableOf(activeGroup); + }, + getEPersonMembers() { + return this.epersonMembers; + }, + searchGroups(query: string): Observable>> { + if (query === '') { + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), this.allGroups)); + } + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])); + }, + addMemberToGroup(parentGroup, eperson: EPerson): Observable { + this.epersonMembers = [...this.epersonMembers, eperson]; + return observableOf(new RestResponse(true, 200, 'Success')); + }, + clearGroupsRequests() { + // empty + }, + clearGroupLinkRequests() { + // empty + }, + getGroupEditPageRouterLink(group: Group): string { + return '/access-control/groups/' + group.id; + }, + deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable { + this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => { + if (eperson.id !== epersonToDelete.id) { + return eperson; + } + }); + if (this.epersonMembers === undefined) { + this.epersonMembers = []; + } + return observableOf(new RestResponse(true, 200, 'Success')); + } + }; + builderService = getMockFormBuilderService(); + translateService = getMockTranslateService(); + + paginationService = new PaginationServiceStub(); + TestBed.configureTestingModule({ + imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [EPersonListComponent], + providers: [EPersonListComponent, + { provide: EPersonDataService, useValue: ePersonDataServiceStub }, + { provide: GroupDataService, useValue: groupsDataServiceStub }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: FormBuilderService, useValue: builderService }, + { provide: Router, useValue: new RouterMock() }, + { provide: PaginationService, useValue: paginationService }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EPersonListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + afterEach(fakeAsync(() => { + fixture.destroy(); + flush(); + component = null; + fixture.debugElement.nativeElement.remove(); + })); + + it('should create EpeopleListComponent', inject([EPersonListComponent], (comp: EPersonListComponent) => { + expect(comp).toBeDefined(); + })); + + it('should show list of eperson members of current active group', () => { + const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); + expect(epersonIdsFound.length).toEqual(1); + epersonMembers.map((eperson: EPerson) => { + expect(epersonIdsFound.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === eperson.uuid); + })).toBeTruthy(); + }); + }); + + describe('search', () => { + describe('when searching without query', () => { + let epersonsFound; + beforeEach(fakeAsync(() => { + component.search({ scope: 'metadata', query: '' }); + tick(); + fixture.detectChanges(); + epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr')); + })); + + it('should display all epersons', () => { + expect(epersonsFound.length).toEqual(2); + }); + + describe('if eperson is already a eperson', () => { + it('should have delete button, else it should have add button', () => { + activeGroup.epersons.map((eperson: EPerson) => { + epersonsFound.map((foundEPersonRowElement) => { + if (foundEPersonRowElement.debugElement !== undefined) { + const epersonId = foundEPersonRowElement.debugElement.query(By.css('td:first-child')); + const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus')); + const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt')); + if (epersonId.nativeElement.textContent === eperson.id) { + expect(addButton).toBeUndefined(); + expect(deleteButton).toBeDefined(); + } else { + expect(deleteButton).toBeUndefined(); + expect(addButton).toBeDefined(); + } + } + }); + }); + }); + }); + + describe('if first add button is pressed', () => { + beforeEach(fakeAsync(() => { + const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus')); + addButton.nativeElement.click(); + tick(); + fixture.detectChanges(); + })); + it('all groups in search member of selected group', () => { + epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr')); + expect(epersonsFound.length).toEqual(2); + epersonsFound.map((foundEPersonRowElement) => { + if (foundEPersonRowElement.debugElement !== undefined) { + const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus')); + const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt')); + expect(addButton).toBeUndefined(); + expect(deleteButton).toBeDefined(); + } + }); + }); + }); + + describe('if first delete button is pressed', () => { + beforeEach(fakeAsync(() => { + const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-trash-alt')); + addButton.nativeElement.click(); + tick(); + fixture.detectChanges(); + })); + it('first eperson in search delete button, because now member', () => { + epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr')); + epersonsFound.map((foundEPersonRowElement) => { + if (foundEPersonRowElement.debugElement !== undefined) { + const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus')); + const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt')); + expect(deleteButton).toBeUndefined(); + expect(addButton).toBeDefined(); + } + }); + }); + }); + }); + }); + +}); diff --git a/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.ts b/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.ts new file mode 100644 index 0000000000..9eafe10daa --- /dev/null +++ b/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.ts @@ -0,0 +1,358 @@ +import { Component, OnDestroy, OnInit, Input } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; +import { GroupDataService } from '../../../../core/eperson/group-data.service'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { Group } from '../../../../core/eperson/models/group.model'; +import { + getAllCompletedRemoteData, + getFirstSucceededRemoteData, + getRemoteDataPayload, + getFirstCompletedRemoteData +} from '../../../../core/shared/operators'; +import { + BehaviorSubject, + Subscription, + combineLatest as observableCombineLatest, + Observable, + ObservedValueOf, + of as observableOf +} from 'rxjs'; +import { PaginatedList, buildPaginatedList } from '../../../../core/data/paginated-list.model'; +import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { switchMap, map, take, mergeMap } from 'rxjs/operators'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { EPerson } from '../../../../core/eperson/models/eperson.model'; + +/** + * Keys to keep track of specific subscriptions + */ +enum SubKey { + ActiveGroup, + MembersDTO, + SearchResultsDTO, +} + +export interface EPersonActionConfig { + css?: string; + disabled: boolean; + icon: string; +} + +export interface EPersonListActionConfig { + add: EPersonActionConfig; + remove: EPersonActionConfig; +} + +@Component({ + selector: 'ds-eperson-list', + templateUrl: './eperson-list.component.html' +}) +export class EPersonListComponent implements OnInit, OnDestroy { + + @Input() + messagePrefix: string; + + @Input() + actionConfig: EPersonListActionConfig = { + add: { + css: 'btn-outline-primary', + disabled: false, + icon: 'fas fa-plus fa-fw', + }, + remove: { + css: 'btn-outline-danger', + disabled: false, + icon: 'fas fa-trash-alt fa-fw' + }, + }; + + /** + * EPeople being displayed in search result, initially all members, after search result of search + */ + ePeopleSearchDtos: BehaviorSubject> = new BehaviorSubject>(undefined); + /** + * List of EPeople members of currently active group being edited + */ + ePeopleMembersOfGroupDtos: BehaviorSubject> = new BehaviorSubject>(undefined); + + /** + * Pagination config used to display the list of EPeople that are result of EPeople search + */ + configSearch: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { + id: 'sml', + pageSize: 5, + currentPage: 1 + }); + /** + * Pagination config used to display the list of EPerson Membes of active group being edited + */ + config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { + id: 'ml', + pageSize: 5, + currentPage: 1 + }); + + /** + * Map of active subscriptions + */ + subs: Map = new Map(); + + // The search form + searchForm; + + // Current search in edit group - epeople search form + currentSearchQuery: string; + currentSearchScope: string; + + // Whether or not user has done a EPeople search yet + searchDone: boolean; + + // current active group being edited + groupBeingEdited: Group; + + constructor( + protected groupDataService: GroupDataService, + public ePersonDataService: EPersonDataService, + protected translateService: TranslateService, + protected notificationsService: NotificationsService, + protected formBuilder: FormBuilder, + protected paginationService: PaginationService, + private router: Router + ) { + this.currentSearchQuery = ''; + this.currentSearchScope = 'metadata'; + } + + ngOnInit(): void { + this.searchForm = this.formBuilder.group(({ + scope: 'metadata', + query: '', + })); + this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => { + if (activeGroup != null) { + this.groupBeingEdited = activeGroup; + this.retrieveMembers(this.config.currentPage); + } + })); + } + + /** + * Retrieve the EPersons that are members of the group + * + * @param page the number of the page to retrieve + * @private + */ + retrieveMembers(page: number): void { + this.unsubFrom(SubKey.MembersDTO); + this.subs.set(SubKey.MembersDTO, + this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( + switchMap((currentPagination) => { + return this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, { + currentPage: currentPagination.currentPage, + elementsPerPage: currentPagination.pageSize + } + ); + }), + getAllCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasFailed) { + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage })); + } else { + return rd; + } + }), + switchMap((epersonListRD: RemoteData>) => { + const dtos$ = observableCombineLatest(...epersonListRD.payload.page.map((member: EPerson) => { + const dto$: Observable = observableCombineLatest( + this.isMemberOfGroup(member), (isMember: ObservedValueOf>) => { + const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel(); + epersonDtoModel.eperson = member; + epersonDtoModel.memberOfGroup = isMember; + return epersonDtoModel; + }); + return dto$; + })); + return dtos$.pipe(map((dtos: EpersonDtoModel[]) => { + return buildPaginatedList(epersonListRD.payload.pageInfo, dtos); + })); + })) + .subscribe((paginatedListOfDTOs: PaginatedList) => { + this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs); + })); + } + + /** + * Whether the given ePerson is a member of the group currently being edited + * @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited + */ + isMemberOfGroup(possibleMember: EPerson): Observable { + return this.groupDataService.getActiveGroup().pipe(take(1), + mergeMap((group: Group) => { + if (group != null) { + return this.ePersonDataService.findAllByHref(group._links.epersons.href, { + currentPage: 1, + elementsPerPage: 9999 + }, false) + .pipe( + getFirstSucceededRemoteData(), + getRemoteDataPayload(), + map((listEPeopleInGroup: PaginatedList) => listEPeopleInGroup.page.filter((ePersonInList: EPerson) => ePersonInList.id === possibleMember.id)), + map((epeople: EPerson[]) => epeople.length > 0)); + } else { + return observableOf(false); + } + })); + } + + /** + * Unsubscribe from a subscription if it's still subscribed, and remove it from the map of + * active subscriptions + * + * @param key The key of the subscription to unsubscribe from + * @private + */ + protected unsubFrom(key: SubKey) { + if (this.subs.has(key)) { + this.subs.get(key).unsubscribe(); + this.subs.delete(key); + } + } + + /** + * Deletes a given EPerson from the members list of the group currently being edited + * @param ePerson EPerson we want to delete as member from group that is currently being edited + */ + deleteMemberFromGroup(ePerson: EpersonDtoModel) { + this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => { + if (activeGroup != null) { + const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson.eperson); + this.showNotifications('deleteMember', response, ePerson.eperson.name, activeGroup); + this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); + } else { + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup')); + } + }); + } + + /** + * Adds a given EPerson to the members list of the group currently being edited + * @param ePerson EPerson we want to add as member to group that is currently being edited + */ + addMemberToGroup(ePerson: EpersonDtoModel) { + ePerson.memberOfGroup = true; + this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => { + if (activeGroup != null) { + const response = this.groupDataService.addMemberToGroup(activeGroup, ePerson.eperson); + this.showNotifications('addMember', response, ePerson.eperson.name, activeGroup); + } else { + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup')); + } + }); + } + + /** + * Search in the EPeople by name, email or metadata + * @param data Contains scope and query param + */ + search(data: any) { + this.unsubFrom(SubKey.SearchResultsDTO); + this.subs.set(SubKey.SearchResultsDTO, + this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe( + switchMap((paginationOptions) => { + + const query: string = data.query; + const scope: string = data.scope; + if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchQuery = query; + this.paginationService.resetPage(this.configSearch.id); + } + if (scope != null && this.currentSearchScope !== scope && this.groupBeingEdited) { + this.router.navigate([], { + queryParamsHandling: 'merge' + }); + this.currentSearchScope = scope; + this.paginationService.resetPage(this.configSearch.id); + } + this.searchDone = true; + + return this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { + currentPage: paginationOptions.currentPage, + elementsPerPage: paginationOptions.pageSize + }); + }), + getAllCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasFailed) { + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage })); + } else { + return rd; + } + }), + switchMap((epersonListRD: RemoteData>) => { + const dtos$ = observableCombineLatest(...epersonListRD.payload.page.map((member: EPerson) => { + const dto$: Observable = observableCombineLatest( + this.isMemberOfGroup(member), (isMember: ObservedValueOf>) => { + const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel(); + epersonDtoModel.eperson = member; + epersonDtoModel.memberOfGroup = isMember; + return epersonDtoModel; + }); + return dto$; + })); + return dtos$.pipe(map((dtos: EpersonDtoModel[]) => { + return buildPaginatedList(epersonListRD.payload.pageInfo, dtos); + })); + })) + .subscribe((paginatedListOfDTOs: PaginatedList) => { + this.ePeopleSearchDtos.next(paginatedListOfDTOs); + })); + } + + /** + * unsub all subscriptions + */ + ngOnDestroy(): void { + for (const key of this.subs.keys()) { + this.unsubFrom(key); + } + this.paginationService.clearPagination(this.config.id); + this.paginationService.clearPagination(this.configSearch.id); + } + + /** + * Shows a notification based on the success/failure of the request + * @param messageSuffix Suffix for message + * @param response RestResponse observable containing success/failure request + * @param nameObject Object request was about + * @param activeGroup Group currently being edited + */ + showNotifications(messageSuffix: string, response: Observable>, nameObject: string, activeGroup: Group) { + response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData) => { + if (rd.hasSucceeded) { + this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject })); + this.ePersonDataService.clearLinkRequests(activeGroup._links.epersons.href); + } else { + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject })); + } + }); + } + + /** + * Reset all input-fields to be empty and search all search + */ + clearFormAndResetResult() { + this.searchForm.patchValue({ + query: '', + }); + this.search({ query: '' }); + } + +} diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts index 0b19b17100..0fa405a1c9 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts @@ -149,6 +149,7 @@ describe('MembersListComponent', () => { fixture.destroy(); flush(); component = null; + fixture.debugElement.nativeElement.remove(); })); it('should create MembersListComponent', inject([MembersListComponent], (comp: MembersListComponent) => { diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts index 54d144da51..8bc540641e 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts @@ -94,13 +94,15 @@ export class MembersListComponent implements OnInit, OnDestroy { paginationSub: Subscription; - constructor(private groupDataService: GroupDataService, - public ePersonDataService: EPersonDataService, - private translateService: TranslateService, - private notificationsService: NotificationsService, - private formBuilder: FormBuilder, - private paginationService: PaginationService, - private router: Router) { + constructor( + protected groupDataService: GroupDataService, + public ePersonDataService: EPersonDataService, + private translateService: TranslateService, + private notificationsService: NotificationsService, + protected formBuilder: FormBuilder, + private paginationService: PaginationService, + private router: Router + ) { this.currentSearchQuery = ''; this.currentSearchScope = 'metadata'; } @@ -124,7 +126,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * @param page the number of the page to retrieve * @private */ - private retrieveMembers(page: number) { + protected retrieveMembers(page: number) { this.unsubFrom(SubKey.MembersDTO); this.subs.set(SubKey.MembersDTO, this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( diff --git a/src/app/core/data/request.service.spec.ts b/src/app/core/data/request.service.spec.ts index 7a07f6fe10..fb4edff76f 100644 --- a/src/app/core/data/request.service.spec.ts +++ b/src/app/core/data/request.service.spec.ts @@ -592,6 +592,19 @@ describe('RequestService', () => { 'property1=multiple%0Alines%0Ato%0Asend&property2=sp%26ci%40l%20characters&sp%26ci%40l-chars%20in%20prop=test123' ); }); + + it('should properly encode the body with an array', () => { + const body = { + 'property1': 'multiple\nlines\nto\nsend', + 'property2': 'sp&ci@l characters', + 'sp&ci@l-chars in prop': 'test123', + 'arrayParam': ['arrayValue1', 'arrayValue2'], + }; + const queryParams = service.uriEncodeBody(body); + expect(queryParams).toEqual( + 'property1=multiple%0Alines%0Ato%0Asend&property2=sp%26ci%40l%20characters&sp%26ci%40l-chars%20in%20prop=test123&arrayParam=arrayValue1&arrayParam=arrayValue2' + ); + }); }); }); diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts index 14499b8214..f26b36da08 100644 --- a/src/app/core/data/request.service.ts +++ b/src/app/core/data/request.service.ts @@ -252,8 +252,8 @@ export class RequestService { /** * Convert request Payload to a URL-encoded string * - * e.g. uriEncodeBody({param: value, param1: value1}) - * returns: param=value¶m1=value1 + * e.g. uriEncodeBody({param: value, param1: value1, param2: [value3, value4]}) + * returns: param=value¶m1=value1¶m2=value3¶m2=value4 * * @param body * The request Payload to convert @@ -264,11 +264,19 @@ export class RequestService { let queryParams = ''; if (isNotEmpty(body) && typeof body === 'object') { Object.keys(body) - .forEach((param) => { + .forEach((param: string) => { const encodedParam = encodeURIComponent(param); - const encodedBody = encodeURIComponent(body[param]); - const paramValue = `${encodedParam}=${encodedBody}`; - queryParams = isEmpty(queryParams) ? queryParams.concat(paramValue) : queryParams.concat('&', paramValue); + if (Array.isArray(body[param])) { + for (const element of body[param]) { + const encodedBody = encodeURIComponent(element); + const paramValue = `${encodedParam}=${encodedBody}`; + queryParams = isEmpty(queryParams) ? queryParams.concat(paramValue) : queryParams.concat('&', paramValue); + } + } else { + const encodedBody = encodeURIComponent(body[param]); + const paramValue = `${encodedParam}=${encodedBody}`; + queryParams = isEmpty(queryParams) ? queryParams.concat(paramValue) : queryParams.concat('&', paramValue); + } }); } return queryParams; diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.html index 64e5638de6..3009cc0771 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.html +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.html @@ -1,5 +1,15 @@
- advancedInfo: {{ (workflowAction$ | async)?.advancedInfo | json }} +

{{ 'advanced-workflow-action.select-reviewer.description-multiple' | translate }}

+

{{ 'advanced-workflow-action.select-reviewer.description-single' | translate }}

+ + diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.scss b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.scss index e69de29bb2..65f38247c8 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.scss +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.scss @@ -0,0 +1,7 @@ +:host ::ng-deep { + .reviewersListWithGroup { + #search, #search + form, #search + form + ds-pagination { + display: none !important; + } + } +} diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index df867d5595..29949a0ae3 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -1,8 +1,17 @@ -import { Component } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { rendersAdvancedWorkflowTaskOption } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component'; +import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; +import { + SelectReviewerActionAdvancedInfo +} from '../../../core/tasks/models/select-reviewer-action-advanced-info.model'; +import { + EPersonListActionConfig +} from '../../../access-control/group-registry/group-form/eperson-list/eperson-list.component'; +import { Subscription } from 'rxjs'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; export const WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer'; export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction'; @@ -13,7 +22,63 @@ export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction'; templateUrl: './advanced-workflow-action-select-reviewer.component.html', styleUrls: ['./advanced-workflow-action-select-reviewer.component.scss'], }) -export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkflowActionComponent { +export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkflowActionComponent implements OnInit, OnDestroy { + + multipleReviewers = true; + + selectedReviewers: EPerson[]; + + reviewersListActionConfig: EPersonListActionConfig; + + /** + * When the component is created the value is `undefined`, afterwards it will be set to either the group id or `null`. + * It needs to be subscribed in the **ngOnInit()** because otherwise some unnecessary request will be made. + */ + groupId?: string | null; + + subs: Subscription[] = []; + + ngOnDestroy(): void { + this.subs.forEach((subscription: Subscription) => subscription.unsubscribe()); + } + + ngOnInit(): void { + super.ngOnInit(); + if (this.multipleReviewers) { + this.reviewersListActionConfig = { + add: { + css: 'btn-outline-primary', + disabled: false, + icon: 'fas fa-plus', + }, + remove: { + css: 'btn-outline-danger', + disabled: false, + icon: 'fas fa-minus' + }, + }; + } else { + this.reviewersListActionConfig = { + add: { + css: 'btn-outline-primary', + disabled: false, + icon: 'fas fa-check', + }, + remove: { + css: 'btn-primary', + disabled: true, + icon: 'fas fa-check' + }, + }; + } + this.subs.push(this.workflowAction$.subscribe((workflowAction: WorkflowAction) => { + if (workflowAction) { + this.groupId = (workflowAction.advancedInfo as SelectReviewerActionAdvancedInfo[])[0].group; + } else { + this.groupId = null; + } + })); + } getType(): string { return ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER; @@ -22,6 +87,7 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf createBody(): any { return { [WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER]: true, + eperson: this.selectedReviewers.map((ePerson: EPerson) => ePerson.id), }; } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts new file mode 100644 index 0000000000..bf27b1e79f --- /dev/null +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts @@ -0,0 +1,212 @@ +import { CommonModule } from '@angular/common'; +import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, inject, TestBed, waitForAsync } from '@angular/core/testing'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { BrowserModule, By } from '@angular/platform-browser'; +import { Router } from '@angular/router'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; +import { Observable, of as observableOf } from 'rxjs'; +import { RestResponse } from '../../../../core/cache/response.models'; +import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; +import { GroupDataService } from '../../../../core/eperson/group-data.service'; +import { EPerson } from '../../../../core/eperson/models/eperson.model'; +import { Group } from '../../../../core/eperson/models/group.model'; +import { PageInfo } from '../../../../core/shared/page-info.model'; +import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock'; +import { ReviewersListComponent } from './reviewers-list.component'; +import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock'; +import { + createSuccessfulRemoteDataObject$, + createNoContentRemoteDataObject$ +} from '../../../../shared/remote-data.utils'; +import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock'; +import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; +import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock'; +import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; +import { RouterMock } from '../../../../shared/mocks/router.mock'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; + +describe('ReviewersListComponent', () => { + let component: ReviewersListComponent; + let fixture: ComponentFixture; + let translateService: TranslateService; + let builderService: FormBuilderService; + let ePersonDataServiceStub: any; + let groupsDataServiceStub: any; + let activeGroup; + let allEPersons; + let allGroups; + let epersonMembers; + let subgroupMembers; + let paginationService; + + beforeEach(waitForAsync(() => { + activeGroup = GroupMock; + epersonMembers = [EPersonMock2]; + subgroupMembers = [GroupMock2]; + allEPersons = [EPersonMock, EPersonMock2]; + allGroups = [GroupMock, GroupMock2]; + ePersonDataServiceStub = { + activeGroup: activeGroup, + epersonMembers: epersonMembers, + subgroupMembers: subgroupMembers, + findAllByHref(href: string): Observable>> { + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupsDataServiceStub.getEPersonMembers())); + }, + searchByScope(scope: string, query: string): Observable>> { + if (query === '') { + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allEPersons)); + } + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])); + }, + clearEPersonRequests() { + // empty + }, + clearLinkRequests() { + // empty + }, + getEPeoplePageRouterLink(): string { + return '/access-control/epeople'; + } + }; + groupsDataServiceStub = { + activeGroup: activeGroup, + epersonMembers: epersonMembers, + subgroupMembers: subgroupMembers, + allGroups: allGroups, + getActiveGroup(): Observable { + return observableOf(activeGroup); + }, + getEPersonMembers() { + return this.epersonMembers; + }, + searchGroups(query: string): Observable>> { + if (query === '') { + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), this.allGroups)); + } + return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])); + }, + addMemberToGroup(parentGroup, eperson: EPerson): Observable { + this.epersonMembers = [...this.epersonMembers, eperson]; + return observableOf(new RestResponse(true, 200, 'Success')); + }, + clearGroupsRequests() { + // empty + }, + clearGroupLinkRequests() { + // empty + }, + getGroupEditPageRouterLink(group: Group): string { + return '/access-control/groups/' + group.id; + }, + deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable { + this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => { + if (eperson.id !== epersonToDelete.id) { + return eperson; + } + }); + if (this.epersonMembers === undefined) { + this.epersonMembers = []; + } + return observableOf(new RestResponse(true, 200, 'Success')); + }, + findById(id: string) { + for (const group of allGroups) { + if (group.id === id) { + console.log('found', group); + return createSuccessfulRemoteDataObject$(group); + } + } + return createNoContentRemoteDataObject$(); + }, + editGroup() { + // empty + } + }; + builderService = getMockFormBuilderService(); + translateService = getMockTranslateService(); + + paginationService = new PaginationServiceStub(); + TestBed.configureTestingModule({ + imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }), + ], + declarations: [ReviewersListComponent], + providers: [ReviewersListComponent, + { provide: EPersonDataService, useValue: ePersonDataServiceStub }, + { provide: GroupDataService, useValue: groupsDataServiceStub }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: FormBuilderService, useValue: builderService }, + { provide: Router, useValue: new RouterMock() }, + { provide: PaginationService, useValue: paginationService }, + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ReviewersListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + afterEach(fakeAsync(() => { + fixture.destroy(); + flush(); + component = null; + fixture.debugElement.nativeElement.remove(); + })); + + it('should create ReviewersListComponent', inject([ReviewersListComponent], (comp: ReviewersListComponent) => { + expect(comp).toBeDefined(); + })); + + describe('when no group is selected', () => { + beforeEach(() => { + component.ngOnChanges({ + groupId: new SimpleChange(undefined, null, true) + }); + fixture.detectChanges(); + }); + + it('should show no epersons because no group is selected', () => { + const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); + expect(epersonIdsFound.length).toEqual(0); + epersonMembers.map((eperson: EPerson) => { + expect(epersonIdsFound.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === eperson.uuid); + })).not.toBeTruthy(); + }); + }); + }); + + describe('when group is selected', () => { + beforeEach(() => { + component.ngOnChanges({ + groupId: new SimpleChange(undefined, GroupMock.id, true) + }); + fixture.detectChanges(); + }); + + it('should show all eperson members of group', () => { + const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); + expect(epersonIdsFound.length).toEqual(1); + epersonMembers.map((eperson: EPerson) => { + expect(epersonIdsFound.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === eperson.uuid); + })).toBeTruthy(); + }); + }); + }); + +}); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts new file mode 100644 index 0000000000..14159450ff --- /dev/null +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts @@ -0,0 +1,124 @@ +import { Component, OnDestroy, OnInit, Input, OnChanges, SimpleChanges, EventEmitter, Output } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { Router } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; +import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; +import { GroupDataService } from '../../../../core/eperson/group-data.service'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { Group } from '../../../../core/eperson/models/group.model'; +import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; +import { + EPersonListComponent, + EPersonListActionConfig +} from '../../../../access-control/group-registry/group-form/eperson-list/eperson-list.component'; +import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model'; +import { EPerson } from '../../../../core/eperson/models/eperson.model'; +import { Observable, of as observableOf } from 'rxjs'; +import { hasValue } from '../../../../shared/empty.util'; +import { PaginatedList } from '../../../../core/data/paginated-list.model'; + +/** + * Keys to keep track of specific subscriptions + */ +enum SubKey { + ActiveGroup, + MembersDTO, + SearchResultsDTO, +} + +@Component({ + selector: 'ds-reviewers-list', + // templateUrl: './reviewers-list.component.html', + templateUrl: '../../../../access-control/group-registry/group-form/eperson-list/eperson-list.component.html', +}) +export class ReviewersListComponent extends EPersonListComponent implements OnInit, OnChanges, OnDestroy { + + @Input() + groupId: string | null; + + @Input() + actionConfig: EPersonListActionConfig; + + @Input() + multipleReviewers: boolean; + + @Output() + selectedReviewersUpdated: EventEmitter = new EventEmitter(); + + selectedReviewers: EpersonDtoModel[] = []; + + constructor(protected groupService: GroupDataService, + public ePersonDataService: EPersonDataService, + translateService: TranslateService, + notificationsService: NotificationsService, + formBuilder: FormBuilder, + paginationService: PaginationService, + router: Router) { + super(groupService, ePersonDataService, translateService, notificationsService, formBuilder, paginationService, router); + } + + ngOnInit() { + this.searchForm = this.formBuilder.group(({ + scope: 'metadata', + query: '', + })); + } + + ngOnChanges(changes: SimpleChanges): void { + this.groupId = changes.groupId.currentValue; + if (changes.groupId.currentValue !== changes.groupId.previousValue) { + if (this.groupId === null) { + this.retrieveMembers(this.config.currentPage); + } else { + this.subs.set(SubKey.ActiveGroup, this.groupService.findById(this.groupId).pipe( + getFirstSucceededRemoteDataPayload(), + ).subscribe((activeGroup: Group) => { + if (activeGroup != null) { + this.groupDataService.editGroup(activeGroup); + this.groupBeingEdited = activeGroup; + this.retrieveMembers(this.config.currentPage); + } + })); + } + } + } + + retrieveMembers(page: number): void { + this.config.currentPage = page; + if (this.groupId === null) { + this.unsubFrom(SubKey.MembersDTO); + const paginatedListOfDTOs: PaginatedList = new PaginatedList(); + paginatedListOfDTOs.page = this.selectedReviewers; + this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs); + } else { + super.retrieveMembers(page); + } + } + + isMemberOfGroup(possibleMember: EPerson): Observable { + return observableOf(hasValue(this.selectedReviewers.find((reviewer: EpersonDtoModel) => reviewer.eperson.id === possibleMember.id))); + } + + deleteMemberFromGroup(ePerson: EpersonDtoModel) { + ePerson.memberOfGroup = false; + const index = this.selectedReviewers.indexOf(ePerson); + if (index !== -1) { + this.selectedReviewers.splice(index, 1); + } + this.selectedReviewersUpdated.emit(this.selectedReviewers.map((epersonDtoModel: EpersonDtoModel) => epersonDtoModel.eperson)); + } + + addMemberToGroup(ePerson: EpersonDtoModel) { + ePerson.memberOfGroup = true; + if (!this.multipleReviewers) { + for (const selectedReviewer of this.selectedReviewers) { + selectedReviewer.memberOfGroup = false; + } + this.selectedReviewers = []; + } + this.selectedReviewers.push(ePerson); + this.selectedReviewersUpdated.emit(this.selectedReviewers.map((epersonDtoModel: EpersonDtoModel) => epersonDtoModel.eperson)); + } + +} diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts index b230ffe65c..080ca96468 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts @@ -18,16 +18,21 @@ import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action/ import { AdvancedClaimedTaskActionsDirective } from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive'; +import { AccessControlModule } from '../access-control/access-control.module'; +import { + ReviewersListComponent +} from './advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component'; @NgModule({ - imports: [ - WorkflowItemsEditPageRoutingModule, - CommonModule, - SharedModule, - SubmissionModule, - StatisticsModule, - ItemPageModule - ], + imports: [ + WorkflowItemsEditPageRoutingModule, + CommonModule, + SharedModule, + SubmissionModule, + StatisticsModule, + ItemPageModule, + AccessControlModule + ], declarations: [ WorkflowItemDeleteComponent, ThemedWorkflowItemDeleteComponent, @@ -38,6 +43,7 @@ import { AdvancedWorkflowActionSelectReviewerComponent, AdvancedWorkflowActionPageComponent, AdvancedClaimedTaskActionsDirective, + ReviewersListComponent, ] }) /** diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index b410a26ac1..9d455fb422 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -539,6 +539,54 @@ "admin.metadata-import.page.error.addFile": "Select file first!", + "advanced-workflow-action.select-reviewer.description-single": "Please select a single reviewer below before submitting", + + "advanced-workflow-action.select-reviewer.description-multiple": "Please select one or more reviewers below before submitting", + + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.head": "EPeople", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.head": "Add EPeople", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.button.see-all": "Browse All", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.headMembers": "Current Members", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.scope.metadata": "Metadata", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.scope.email": "E-mail (exact)", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.search.button": "Search", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.id": "ID", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.name": "Name", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.identity": "Identity", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.email": "Email", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.netid": "NetID", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.edit": "Remove / Add", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.edit.buttons.remove": "Remove member with name \"{{name}}\"", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.success.addMember": "Successfully added member: \"{{name}}\"", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.failure.addMember": "Failed to add member: \"{{name}}\"", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.success.deleteMember": "Successfully deleted member: \"{{name}}\"", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.failure.deleteMember": "Failed to delete member: \"{{name}}\"", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.table.edit.buttons.add": "Add member with name \"{{name}}\"", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.notification.failure.noActiveGroup": "No current active group, submit a name first.", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.no-members-yet": "No members in group yet, search and add.", + + "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.no-items": "No EPeople found in that search", "auth.errors.invalid-user": "Invalid email address or password.", From f4a303f80854f27cce069d437f37a30372a38854 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 9 Jan 2023 17:30:00 +0100 Subject: [PATCH 044/173] 98344: Added decline task simple workflow step --- ...d-task-actions-decline-task.component.html | 12 +++ ...d-task-actions-decline-task.component.scss | 0 ...ask-actions-decline-task.component.spec.ts | 90 +++++++++++++++++++ ...med-task-actions-decline-task.component.ts | 34 +++++++ src/app/shared/shared.module.ts | 3 + .../testing/claimed-task-data-service.stub.ts | 6 ++ src/assets/i18n/en.json5 | 4 + 7 files changed, 149 insertions(+) create mode 100644 src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.html create mode 100644 src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.scss create mode 100644 src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.spec.ts create mode 100644 src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.ts diff --git a/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.html b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.html new file mode 100644 index 0000000000..38ed36c0f1 --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.html @@ -0,0 +1,12 @@ + diff --git a/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.scss b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.spec.ts new file mode 100644 index 0000000000..ef132ff522 --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.spec.ts @@ -0,0 +1,90 @@ +import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { TranslateLoaderMock } from '../../../mocks/translate-loader.mock'; +import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; +import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; +import { getMockSearchService } from '../../../mocks/search-service.mock'; +import { getMockRequestService } from '../../../mocks/request.service.mock'; +import { PoolTaskDataService } from '../../../../core/tasks/pool-task-data.service'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; +import { Router } from '@angular/router'; +import { RouterStub } from '../../../testing/router.stub'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; +import { ClaimedTaskActionsDeclineTaskComponent } from './claimed-task-actions-decline-task.component'; +import { ClaimedTaskDataServiceStub } from '../../../testing/claimed-task-data-service.stub'; + +let component: ClaimedTaskActionsDeclineTaskComponent; +let fixture: ComponentFixture; + +const searchService = getMockSearchService(); + +const requestService = getMockRequestService(); + +let mockPoolTaskDataService: PoolTaskDataService; + +describe('ClaimedTaskActionsDeclineTaskComponent', () => { + const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); + + let claimedTaskService: ClaimedTaskDataServiceStub; + + beforeEach(waitForAsync(() => { + claimedTaskService = new ClaimedTaskDataServiceStub(); + + mockPoolTaskDataService = new PoolTaskDataService(null, null, null, null, null, null, null, null); + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + }) + ], + providers: [ + { provide: ClaimedTaskDataService, useValue: claimedTaskService }, + { provide: Injector, useValue: {} }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: Router, useValue: new RouterStub() }, + { provide: SearchService, useValue: searchService }, + { provide: RequestService, useValue: requestService }, + { provide: PoolTaskDataService, useValue: mockPoolTaskDataService }, + ], + declarations: [ClaimedTaskActionsDeclineTaskComponent], + schemas: [NO_ERRORS_SCHEMA] + }).overrideComponent(ClaimedTaskActionsDeclineTaskComponent, { + set: { changeDetection: ChangeDetectionStrategy.Default } + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ClaimedTaskActionsDeclineTaskComponent); + component = fixture.componentInstance; + component.object = object; + spyOn(component, 'initReloadAnchor').and.returnValue(undefined); + fixture.detectChanges(); + }); + + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + it('should display decline button', () => { + const btn = fixture.debugElement.query(By.css('.declineTaskAction')); + + expect(btn).not.toBeNull(); + }); + + it('should display spin icon when decline is pending', () => { + component.processing$.next(true); + fixture.detectChanges(); + + const span = fixture.debugElement.query(By.css('.declineTaskAction .fa-spin')); + + expect(span).not.toBeNull(); + }); + +}); diff --git a/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.ts b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.ts new file mode 100644 index 0000000000..ab867d62d6 --- /dev/null +++ b/src/app/shared/mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component.ts @@ -0,0 +1,34 @@ +import { Component, Injector } from '@angular/core'; +import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; +import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; +import { Router } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; + +export const WORKFLOW_TASK_OPTION_DECLINE_TASK = 'submit_decline_task'; + +@rendersWorkflowTaskOption(WORKFLOW_TASK_OPTION_DECLINE_TASK) +@Component({ + selector: 'ds-claimed-task-actions-decline-task', + templateUrl: './claimed-task-actions-decline-task.component.html', + styleUrls: ['./claimed-task-actions-decline-task.component.scss'] +}) +/** + * Component for displaying and processing the decline task action on a workflow task item + */ +export class ClaimedTaskActionsDeclineTaskComponent extends ClaimedTaskActionsAbstractComponent { + + option = WORKFLOW_TASK_OPTION_DECLINE_TASK; + + constructor(protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService) { + super(injector, router, notificationsService, translate, searchService, requestService); + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 9a859dff9b..1cc0a86115 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -181,6 +181,7 @@ import { AdvancedClaimedTaskActionSelectReviewerComponent } from './mydspace-act import { AdvancedClaimedTaskActionRatingReviewerComponent } from './mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component'; +import { ClaimedTaskActionsDeclineTaskComponent } from './mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -265,6 +266,7 @@ const COMPONENTS = [ ClaimedTaskActionsRejectComponent, ClaimedTaskActionsReturnToPoolComponent, ClaimedTaskActionsEditMetadataComponent, + ClaimedTaskActionsDeclineTaskComponent, ClaimedTaskActionsLoaderComponent, ItemActionsComponent, PoolTaskActionsComponent, @@ -389,6 +391,7 @@ const ENTRY_COMPONENTS = [ LogInOidcComponent, BundleListElementComponent, ClaimedTaskActionsApproveComponent, + ClaimedTaskActionsDeclineTaskComponent, ClaimedTaskActionsRejectComponent, ClaimedTaskActionsReturnToPoolComponent, ClaimedTaskActionsEditMetadataComponent, diff --git a/src/app/shared/testing/claimed-task-data-service.stub.ts b/src/app/shared/testing/claimed-task-data-service.stub.ts index ef04e3bf27..3a9f3d852d 100644 --- a/src/app/shared/testing/claimed-task-data-service.stub.ts +++ b/src/app/shared/testing/claimed-task-data-service.stub.ts @@ -1,5 +1,7 @@ import { Observable, EMPTY } from 'rxjs'; import { ProcessTaskResponse } from '../../core/tasks/models/process-task-response'; +import { ClaimedTask } from '../../core/tasks/models/claimed-task-object.model'; +import { RemoteData } from '../../core/data/remote-data'; export class ClaimedTaskDataServiceStub { @@ -7,4 +9,8 @@ export class ClaimedTaskDataServiceStub { return EMPTY; } + public findByItem(_uuid: string): Observable> { + return EMPTY; + } + } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 9d455fb422..6addb5a81b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4069,6 +4069,10 @@ "submission.workflow.tasks.claimed.edit_help": "Select this option to change the item's metadata.", + "submission.workflow.tasks.claimed.decline": "Decline", + + "submission.workflow.tasks.claimed.decline_help": "", + "submission.workflow.tasks.claimed.reject.reason.info": "Please enter your reason for rejecting the submission into the box below, indicating whether the submitter may fix a problem and resubmit.", "submission.workflow.tasks.claimed.reject.reason.placeholder": "Describe the reason of reject", From b3510534895b15882c0bc5ef68a6102307a0bfcd Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 10 Jan 2023 14:10:44 +0100 Subject: [PATCH 045/173] 98344: Fixed workflow id passed to backend instead of claimed task id --- ...claimed-task-actions-abstract.component.ts | 24 +++++++++++++++++-- ...k-action-rating-reviewer.component.spec.ts | 14 +++++++++-- ...d-task-action-rating-reviewer.component.ts | 17 ------------- ...k-action-select-reviewer.component.spec.ts | 14 +++++++++-- ...d-task-action-select-reviewer.component.ts | 12 ---------- ...rkflow-action-select-reviewer.component.ts | 2 +- .../advanced-workflow-action.component.ts | 18 ++++++++++++++ 7 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts index 8738f7745b..3774b60f1a 100644 --- a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts @@ -1,5 +1,8 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ClaimedTaskActionsAbstractComponent } from './claimed-task-actions-abstract.component'; +import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; /** * Abstract component for rendering an advanced claimed task's action @@ -12,7 +15,7 @@ import { ClaimedTaskActionsAbstractComponent } from './claimed-task-actions-abst selector: 'ds-advanced-claimed-task-action-abstract', template: '' }) -export abstract class AdvancedClaimedTaskActionsAbstractComponent extends ClaimedTaskActionsAbstractComponent { +export abstract class AdvancedClaimedTaskActionsAbstractComponent extends ClaimedTaskActionsAbstractComponent implements OnInit { workflowType: string; @@ -21,10 +24,27 @@ export abstract class AdvancedClaimedTaskActionsAbstractComponent extends Claime */ workflowTaskPageRoute: string; + ngOnInit(): void { + super.ngOnInit(); + this.initPageRoute(); + } + + /** + * Initialise the route to the advanced workflow page + */ + initPageRoute() { + this.subs.push(this.object.workflowitem.pipe( + getFirstSucceededRemoteDataPayload() + ).subscribe((workflowItem: WorkflowItem) => { + this.workflowTaskPageRoute = getAdvancedWorkflowRoute(workflowItem.id); + })); + } + openAdvancedClaimedTaskTab(): void { void this.router.navigate([this.workflowTaskPageRoute], { queryParams: { workflow: this.workflowType, + claimedTask: this.object.id, }, }); } diff --git a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.spec.ts index ed2fc42310..563af2810e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.spec.ts @@ -19,11 +19,19 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ADVANCED_WORKFLOW_ACTION_RATING_REVIEWER } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component'; +import { of as observableOf } from 'rxjs'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; const taskId = 'claimed-task-1'; +const workflowId = 'workflow-1'; describe('AdvancedClaimedTaskActionRatingReviewerComponent', () => { - const object = Object.assign(new ClaimedTask(), { id: taskId }); + const object = Object.assign(new ClaimedTask(), { + id: taskId, + workflowitem: observableOf(Object.assign(new WorkflowItem(), { + id: workflowId, + })), + }); let component: AdvancedClaimedTaskActionRatingReviewerComponent; let fixture: ComponentFixture; @@ -75,11 +83,13 @@ describe('AdvancedClaimedTaskActionRatingReviewerComponent', () => { }); it('should navigate to the advanced workflow page when clicked', () => { + component.workflowTaskPageRoute = `/workflowitems/${workflowId}/advanced`; fixture.debugElement.query(By.css('.ratingReviewerAction')).nativeElement.click(); - expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${taskId}/advanced`], { + expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${workflowId}/advanced`], { queryParams: { workflow: ADVANCED_WORKFLOW_ACTION_RATING_REVIEWER, + claimedTask: taskId, }, }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.ts b/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.ts index bfa676b30e..20fe58c80e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.ts @@ -4,7 +4,6 @@ import { NotificationsService } from '../../../notifications/notifications.servi import { TranslateService } from '@ngx-translate/core'; import { SearchService } from '../../../../core/shared/search/search.service'; import { RequestService } from '../../../../core/data/request.service'; -import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advanced-claimed-task-actions-abstract.component'; @@ -28,11 +27,6 @@ export class AdvancedClaimedTaskActionRatingReviewerComponent extends AdvancedCl workflowType = ADVANCED_WORKFLOW_ACTION_RATING_REVIEWER; - /** - * Route to the workflow's task page - */ - workflowTaskPageRoute: string; - constructor( protected injector: Injector, protected router: Router, @@ -44,15 +38,4 @@ export class AdvancedClaimedTaskActionRatingReviewerComponent extends AdvancedCl super(injector, router, notificationsService, translate, searchService, requestService); } - ngOnInit(): void { - this.initPageRoute(); - } - - /** - * Initialise the route to the rating reviewer's page - */ - initPageRoute(): void { - this.workflowTaskPageRoute = getAdvancedWorkflowRoute(this.object.id); - } - } diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts index 0c91898a97..db670e3cdc 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts @@ -20,11 +20,19 @@ import { Location } from '@angular/common'; import { ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { of as observableOf } from 'rxjs'; const taskId = 'claimed-task-1'; +const workflowId = 'workflow-1'; describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { - const object = Object.assign(new ClaimedTask(), { id: taskId }); + const object = Object.assign(new ClaimedTask(), { + id: taskId, + workflowitem: observableOf(Object.assign(new WorkflowItem(), { + id: workflowId, + })), + }); let component: AdvancedClaimedTaskActionSelectReviewerComponent; let fixture: ComponentFixture; @@ -77,11 +85,13 @@ describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { }); it('should navigate to the advanced workflow page when clicked', () => { + component.workflowTaskPageRoute = `/workflowitems/${workflowId}/advanced`; fixture.debugElement.query(By.css('.selectReviewerAction')).nativeElement.click(); - expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${taskId}/advanced`], { + expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${workflowId}/advanced`], { queryParams: { workflow: ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, + claimedTask: taskId, }, }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts index 7b2aa015af..c7a2a2e545 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts @@ -8,7 +8,6 @@ import { NotificationsService } from '../../../notifications/notifications.servi import { TranslateService } from '@ngx-translate/core'; import { SearchService } from '../../../../core/shared/search/search.service'; import { RequestService } from '../../../../core/data/request.service'; -import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; import { ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER @@ -40,15 +39,4 @@ export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedCl super(injector, router, notificationsService, translate, searchService, requestService); } - ngOnInit(): void { - this.initPageRoute(); - } - - /** - * Initialise the route to the select reviewer's page - */ - initPageRoute() { - this.workflowTaskPageRoute = getAdvancedWorkflowRoute(this.object.id); - } - } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index 29949a0ae3..d7a885f067 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -26,7 +26,7 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf multipleReviewers = true; - selectedReviewers: EPerson[]; + selectedReviewers: EPerson[] = []; reviewersListActionConfig: EPersonListActionConfig; diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts index bef619ce45..f40cde6fa6 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts @@ -41,6 +41,24 @@ export abstract class AdvancedWorkflowActionComponent extends WorkflowItemAction ); } + /** + * Performs the action and shows a notification based on the outcome of the action + */ + performAction() { + this.sendRequest(this.route.snapshot.queryParams.claimedTask).subscribe((successful: boolean) => { + if (successful) { + const title = this.translationService.get('workflow-item.' + this.type + '.notification.success.title'); + const content = this.translationService.get('workflow-item.' + this.type + '.notification.success.content'); + this.notificationsService.success(title, content); + this.previousPage(); + } else { + const title = this.translationService.get('workflow-item.' + this.type + '.notification.error.title'); + const content = this.translationService.get('workflow-item.' + this.type + '.notification.error.content'); + this.notificationsService.error(title, content); + } + }); + } + /** * Submits the task with the given {@link createBody}. * From 36891ef3bfe685669e924add39ee184c07a612db Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 30 Dec 2022 10:11:57 +0100 Subject: [PATCH 046/173] 98204: Edit collection assign roles tab breaks when no workflow groups --- .../collection-roles/collection-roles.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts b/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts index a6c37cbc45..68bdf7b561 100644 --- a/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts @@ -62,7 +62,7 @@ export class CollectionRolesComponent implements OnInit { name: 'bitstream_read', href: collection._links.bitstreamReadGroup.href, }, - ...collection._links.workflowGroups, + ...(collection._links.workflowGroups ? collection._links.workflowGroups : []), ]), ); } From ef9373baec155a16cef6df7a6b04f7fc8570b288 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 10 Jan 2023 17:56:32 +0100 Subject: [PATCH 047/173] 98376: Fixed collection-roles page for collections with single workflowGroup --- .../collection-roles.component.ts | 45 +++++++++++-------- src/assets/i18n/en.json5 | 5 +++ 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts b/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts index 68bdf7b561..0177cc3a38 100644 --- a/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts +++ b/src/app/collection-page/edit-collection-page/collection-roles/collection-roles.component.ts @@ -6,6 +6,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { Collection } from '../../../core/shared/collection.model'; import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { HALLink } from '../../../core/shared/hal-link.model'; +import { hasValue } from '../../../shared/empty.util'; /** * Component for managing a collection's roles @@ -45,25 +46,31 @@ export class CollectionRolesComponent implements OnInit { ); this.comcolRoles$ = this.collection$.pipe( - map((collection) => [ - { - name: 'collection-admin', - href: collection._links.adminGroup.href, - }, - { - name: 'submitters', - href: collection._links.submittersGroup.href, - }, - { - name: 'item_read', - href: collection._links.itemReadGroup.href, - }, - { - name: 'bitstream_read', - href: collection._links.bitstreamReadGroup.href, - }, - ...(collection._links.workflowGroups ? collection._links.workflowGroups : []), - ]), + map((collection) => { + let workflowGroups: HALLink[] | HALLink = hasValue(collection._links.workflowGroups) ? collection._links.workflowGroups : []; + if (!Array.isArray(workflowGroups)) { + workflowGroups = [workflowGroups]; + } + return [ + { + name: 'collection-admin', + href: collection._links.adminGroup.href, + }, + { + name: 'submitters', + href: collection._links.submittersGroup.href, + }, + { + name: 'item_read', + href: collection._links.itemReadGroup.href, + }, + { + name: 'bitstream_read', + href: collection._links.bitstreamReadGroup.href, + }, + ...workflowGroups, + ]; + }), ); } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 6addb5a81b..97af59d159 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1179,6 +1179,11 @@ "comcol-role.edit.reviewer.description": "Reviewers are able to accept or reject incoming submissions. However, they are not able to edit the submission's metadata.", + "comcol-role.edit.scorereviewers.name": "Score Reviewers", + + "comcol-role.edit.scorereviewers.description": "Reviewers are able to give a score to incoming submissions, this will define whether the submission will be rejected or not.", + + "community.form.abstract": "Short Description", From 7cee2aac6f277989c5ed0ce8425b27665d07a7e0 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 10 Jan 2023 18:59:06 +0100 Subject: [PATCH 048/173] 98376: Added rating button to ratingreviewaction claimed task and created page --- package.json | 1 + ...claimed-task-action-rating.component.html} | 2 +- ...claimed-task-action-rating.component.scss} | 0 ...imed-task-action-rating.component.spec.ts} | 20 +++--- ...d-claimed-task-action-rating.component.ts} | 19 +++--- src/app/shared/shared.module.ts | 8 +-- ...flow-action-rating-reviewer.component.html | 1 - ...rkflow-action-rating-reviewer.component.ts | 15 ----- ...nced-workflow-action-rating.component.html | 44 +++++++++++++ ...ced-workflow-action-rating.component.scss} | 0 ...-workflow-action-rating.component.spec.ts} | 31 +++++++-- ...vanced-workflow-action-rating.component.ts | 66 +++++++++++++++++++ .../advanced-workflow-action.component.ts | 2 +- .../workflowitems-edit-page.module.ts | 40 +++++++---- src/assets/i18n/en.json5 | 30 ++++++++- yarn.lock | 7 ++ 16 files changed, 220 insertions(+), 66 deletions(-) rename src/app/shared/mydspace-actions/claimed-task/{rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.html => rating/advanced-claimed-task-action-rating.component.html} (66%) rename src/app/shared/mydspace-actions/claimed-task/{rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.scss => rating/advanced-claimed-task-action-rating.component.scss} (100%) rename src/app/shared/mydspace-actions/claimed-task/{rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.spec.ts => rating/advanced-claimed-task-action-rating.component.spec.ts} (83%) rename src/app/shared/mydspace-actions/claimed-task/{rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.ts => rating/advanced-claimed-task-action-rating.component.ts} (62%) delete mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.html delete mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.ts create mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.html rename src/app/workflowitems-edit-page/advanced-workflow-action/{advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.scss => advanced-workflow-action-rating/advanced-workflow-action-rating.component.scss} (100%) rename src/app/workflowitems-edit-page/advanced-workflow-action/{advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.spec.ts => advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts} (72%) create mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts diff --git a/package.json b/package.json index 278afdf6c3..71b744fe77 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "ng-mocks": "11.11.2", "ng2-file-upload": "1.4.0", "ng2-nouislider": "^1.8.3", + "ngx-bootstrap": "7.1.2", "ngx-infinite-scroll": "^10.0.1", "ngx-moment": "^5.0.0", "ngx-pagination": "5.0.0", diff --git a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.html b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.html similarity index 66% rename from src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.html rename to src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.html index 856b585fb3..5b63aa381e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.html @@ -1,4 +1,4 @@ diff --git a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.scss b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.scss similarity index 100% rename from src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.scss rename to src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.scss diff --git a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts similarity index 83% rename from src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.spec.ts rename to src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts index 563af2810e..c192a68aa7 100644 --- a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts @@ -1,7 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { - AdvancedClaimedTaskActionRatingReviewerComponent -} from './advanced-claimed-task-action-rating-reviewer.component'; +import { AdvancedClaimedTaskActionRatingComponent } from './advanced-claimed-task-action-rating.component'; import { By } from '@angular/platform-browser'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { ClaimedTaskDataServiceStub } from '../../../testing/claimed-task-data-service.stub'; @@ -17,23 +15,23 @@ import { Location } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { - ADVANCED_WORKFLOW_ACTION_RATING_REVIEWER -} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component'; + ADVANCED_WORKFLOW_ACTION_RATING +} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component'; import { of as observableOf } from 'rxjs'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; const taskId = 'claimed-task-1'; const workflowId = 'workflow-1'; -describe('AdvancedClaimedTaskActionRatingReviewerComponent', () => { +describe('AdvancedClaimedTaskActionRatingComponent', () => { const object = Object.assign(new ClaimedTask(), { id: taskId, workflowitem: observableOf(Object.assign(new WorkflowItem(), { id: workflowId, })), }); - let component: AdvancedClaimedTaskActionRatingReviewerComponent; - let fixture: ComponentFixture; + let component: AdvancedClaimedTaskActionRatingComponent; + let fixture: ComponentFixture; let claimedTaskDataService: ClaimedTaskDataServiceStub; let notificationService: NotificationsServiceStub; @@ -51,7 +49,7 @@ describe('AdvancedClaimedTaskActionRatingReviewerComponent', () => { TranslateModule.forRoot(), ], declarations: [ - AdvancedClaimedTaskActionRatingReviewerComponent, + AdvancedClaimedTaskActionRatingComponent, ], providers: [ { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, @@ -66,7 +64,7 @@ describe('AdvancedClaimedTaskActionRatingReviewerComponent', () => { }); beforeEach(() => { - fixture = TestBed.createComponent(AdvancedClaimedTaskActionRatingReviewerComponent); + fixture = TestBed.createComponent(AdvancedClaimedTaskActionRatingComponent); component = fixture.componentInstance; component.object = object; fixture.detectChanges(); @@ -88,7 +86,7 @@ describe('AdvancedClaimedTaskActionRatingReviewerComponent', () => { expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${workflowId}/advanced`], { queryParams: { - workflow: ADVANCED_WORKFLOW_ACTION_RATING_REVIEWER, + workflow: ADVANCED_WORKFLOW_ACTION_RATING, claimedTask: taskId, }, }); diff --git a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.ts b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts similarity index 62% rename from src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.ts rename to src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts index 20fe58c80e..2c2983c25c 100644 --- a/src/app/shared/mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts @@ -8,24 +8,25 @@ import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advanced-claimed-task-actions-abstract.component'; import { - ADVANCED_WORKFLOW_ACTION_RATING_REVIEWER -} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component'; - -export const WORKFLOW_ADVANCED_TASK_OPTION_RATING_REVIEWER = 'submit_rating_reviewer'; + ADVANCED_WORKFLOW_ACTION_RATING, + WORKFLOW_ADVANCED_TASK_OPTION_RATING, +} from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component'; +import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; +@rendersWorkflowTaskOption(WORKFLOW_ADVANCED_TASK_OPTION_RATING) @Component({ selector: 'ds-advanced-claimed-task-action-rating-reviewer', - templateUrl: './advanced-claimed-task-action-rating-reviewer.component.html', - styleUrls: ['./advanced-claimed-task-action-rating-reviewer.component.scss'] + templateUrl: './advanced-claimed-task-action-rating.component.html', + styleUrls: ['./advanced-claimed-task-action-rating.component.scss'] }) -export class AdvancedClaimedTaskActionRatingReviewerComponent extends AdvancedClaimedTaskActionsAbstractComponent { +export class AdvancedClaimedTaskActionRatingComponent extends AdvancedClaimedTaskActionsAbstractComponent { /** * This component represents the advanced select option */ - option = WORKFLOW_ADVANCED_TASK_OPTION_RATING_REVIEWER; + option = WORKFLOW_ADVANCED_TASK_OPTION_RATING; - workflowType = ADVANCED_WORKFLOW_ACTION_RATING_REVIEWER; + workflowType = ADVANCED_WORKFLOW_ACTION_RATING; constructor( protected injector: Injector, diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 1cc0a86115..d5bb76c295 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -179,8 +179,8 @@ import { DsSelectComponent } from './ds-select/ds-select.component'; import { LogInOidcComponent } from './log-in/methods/oidc/log-in-oidc.component'; import { AdvancedClaimedTaskActionSelectReviewerComponent } from './mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component'; import { - AdvancedClaimedTaskActionRatingReviewerComponent -} from './mydspace-actions/claimed-task/rating-reviewer/advanced-claimed-task-action-rating-reviewer.component'; + AdvancedClaimedTaskActionRatingComponent +} from './mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component'; import { ClaimedTaskActionsDeclineTaskComponent } from './mydspace-actions/claimed-task/decline-task/claimed-task-actions-decline-task.component'; const MODULES = [ @@ -353,7 +353,7 @@ const COMPONENTS = [ SearchNavbarComponent, ScopeSelectorModalComponent, AdvancedClaimedTaskActionSelectReviewerComponent, - AdvancedClaimedTaskActionRatingReviewerComponent, + AdvancedClaimedTaskActionRatingComponent, ]; const ENTRY_COMPONENTS = [ @@ -412,7 +412,7 @@ const ENTRY_COMPONENTS = [ TextMenuItemComponent, ScopeSelectorModalComponent, AdvancedClaimedTaskActionSelectReviewerComponent, - AdvancedClaimedTaskActionRatingReviewerComponent, + AdvancedClaimedTaskActionRatingComponent, ]; const SHARED_ITEM_PAGE_COMPONENTS = [ diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.html deleted file mode 100644 index 1b26ba3599..0000000000 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.html +++ /dev/null @@ -1 +0,0 @@ -

advanced-workflow-action-rating-reviewer works!

diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.ts deleted file mode 100644 index 4e0185401d..0000000000 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from '@angular/core'; -import { - rendersAdvancedWorkflowTaskOption -} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; - -export const ADVANCED_WORKFLOW_ACTION_RATING_REVIEWER = 'ratingrevieweraction'; - -@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_RATING_REVIEWER) -@Component({ - selector: 'ds-advanced-workflow-action-rating-reviewer', - templateUrl: './advanced-workflow-action-rating-reviewer.component.html', - styleUrls: ['./advanced-workflow-action-rating-reviewer.component.scss'] -}) -export class AdvancedWorkflowActionRatingReviewerComponent { -} diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.html new file mode 100644 index 0000000000..c74006550c --- /dev/null +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.html @@ -0,0 +1,44 @@ +
+

+ {{ 'advanced-workflow-action.rating.description-requiredDescription' | translate }} +

+

+ {{ 'advanced-workflow-action.rating.description' | translate }} +

+ +
+
+ + + + {{ 'advanced-workflow-action.rating.form.review.error' | translate }} + +
+ +
+ + + +
+
+ + + + +
+ + +
+
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.scss b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.scss similarity index 100% rename from src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.scss rename to src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.scss diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts similarity index 72% rename from src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.spec.ts rename to src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts index 244e397303..a75d4e30a2 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AdvancedWorkflowActionRatingReviewerComponent } from './advanced-workflow-action-rating-reviewer.component'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { AdvancedWorkflowActionRatingComponent } from './advanced-workflow-action-rating.component'; +import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -12,12 +12,17 @@ import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { WorkflowActionDataServiceStub } from '../../../shared/testing/workflow-action-data-service.stub'; import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub'; +import { RouterStub } from '../../../shared/testing/router.stub'; +import { TranslateModule } from '@ngx-translate/core'; +import { VarDirective } from '../../../shared/utils/var.directive'; +import { RatingModule } from 'ngx-bootstrap/rating'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; const workflowId = '1'; -describe('AdvancedWorkflowActionRatingReviewerComponent', () => { - let component: AdvancedWorkflowActionRatingReviewerComponent; - let fixture: ComponentFixture; +describe('AdvancedWorkflowActionRatingComponent', () => { + let component: AdvancedWorkflowActionRatingComponent; + let fixture: ComponentFixture; let claimedTaskDataService: ClaimedTaskDataServiceStub; let notificationService: NotificationsServiceStub; @@ -31,8 +36,15 @@ describe('AdvancedWorkflowActionRatingReviewerComponent', () => { workflowItemDataService = new WorkflowItemDataServiceStub(); await TestBed.configureTestingModule({ + imports: [ + FormsModule, + RatingModule, + ReactiveFormsModule, + TranslateModule.forRoot(), + ], declarations: [ - AdvancedWorkflowActionRatingReviewerComponent, + AdvancedWorkflowActionRatingComponent, + VarDirective, ], providers: [ { @@ -51,6 +63,7 @@ describe('AdvancedWorkflowActionRatingReviewerComponent', () => { { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, { provide: NotificationsService, useValue: notificationService }, { provide: RouteService, useValue: routeServiceStub }, + { provide: Router, useValue: new RouterStub() }, { provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService }, ], @@ -58,11 +71,15 @@ describe('AdvancedWorkflowActionRatingReviewerComponent', () => { }); beforeEach(() => { - fixture = TestBed.createComponent(AdvancedWorkflowActionRatingReviewerComponent); + fixture = TestBed.createComponent(AdvancedWorkflowActionRatingComponent); component = fixture.componentInstance; fixture.detectChanges(); }); + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + it('should create', () => { expect(component).toBeTruthy(); }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts new file mode 100644 index 0000000000..c669cfcfe9 --- /dev/null +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit } from '@angular/core'; +import { + rendersAdvancedWorkflowTaskOption +} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; +import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component'; +import { FormGroup, FormControl } from '@angular/forms'; +import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; +import { + RatingReviewerActionAdvancedInfo +} from '../../../core/tasks/models/rating-reviewer-action-advanced-info.model'; + +export const WORKFLOW_ADVANCED_TASK_OPTION_RATING = 'rating'; +export const ADVANCED_WORKFLOW_ACTION_RATING = 'ratingreviewaction'; + +@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_RATING) +@Component({ + selector: 'ds-advanced-workflow-action-rating-reviewer', + templateUrl: './advanced-workflow-action-rating.component.html', + styleUrls: ['./advanced-workflow-action-rating.component.scss'], + preserveWhitespaces: false, +}) +export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActionComponent implements OnInit { + + ratingForm: FormGroup; + + ngOnInit() { + super.ngOnInit(); + this.ratingForm = new FormGroup({ + review: new FormControl(''), + rating: new FormControl(0), + }); + } + + /** + * Only run **performAction()** when the form has been correctly filled in + */ + performAction(): void { + this.ratingForm.updateValueAndValidity(); + if (this.ratingForm.valid) { + super.performAction(); + } else { + this.ratingForm.markAllAsTouched(); + } + } + + createBody(): any { + const body = { + [WORKFLOW_ADVANCED_TASK_OPTION_RATING]: true, + score: this.ratingForm.get('rating').value, + }; + if (this.ratingForm.get('review').value !== '') { + const description: string = this.ratingForm.get('review').value; + Object.assign(body, { description }); + } + return body; + } + + getType(): string { + return ADVANCED_WORKFLOW_ACTION_RATING; + } + + getAdvancedInfo(workflowAction: WorkflowAction | null): RatingReviewerActionAdvancedInfo | null { + return workflowAction ? (workflowAction.advancedInfo[0] as RatingReviewerActionAdvancedInfo) : null; + } + +} diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts index f40cde6fa6..256a4d8915 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts @@ -44,7 +44,7 @@ export abstract class AdvancedWorkflowActionComponent extends WorkflowItemAction /** * Performs the action and shows a notification based on the outcome of the action */ - performAction() { + performAction(): void { this.sendRequest(this.route.snapshot.queryParams.claimedTask).subscribe((successful: boolean) => { if (successful) { const title = this.translationService.get('workflow-item.' + this.type + '.notification.success.title'); diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts index 080ca96468..dc60bd7d2b 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts @@ -6,15 +6,23 @@ import { SubmissionModule } from '../submission/submission.module'; import { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-item-delete.component'; import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component'; import { ThemedWorkflowItemDeleteComponent } from './workflow-item-delete/themed-workflow-item-delete.component'; -import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/themed-workflow-item-send-back.component'; +import { + ThemedWorkflowItemSendBackComponent +} from './workflow-item-send-back/themed-workflow-item-send-back.component'; import { StatisticsModule } from '../statistics/statistics.module'; import { ItemPageModule } from '../item-page/item-page.module'; import { AdvancedWorkflowActionsLoaderComponent } from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component'; -import { AdvancedWorkflowActionRatingReviewerComponent } from './advanced-workflow-action/advanced-workflow-action-rating-reviewer/advanced-workflow-action-rating-reviewer.component'; -import { AdvancedWorkflowActionSelectReviewerComponent } from './advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; -import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component'; +import { + AdvancedWorkflowActionRatingComponent +} from './advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component'; +import { + AdvancedWorkflowActionSelectReviewerComponent +} from './advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; +import { + AdvancedWorkflowActionPageComponent +} from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component'; import { AdvancedClaimedTaskActionsDirective } from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive'; @@ -22,24 +30,28 @@ import { AccessControlModule } from '../access-control/access-control.module'; import { ReviewersListComponent } from './advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component'; +import { FormModule } from '../shared/form/form.module'; +import { RatingModule } from 'ngx-bootstrap/rating'; @NgModule({ - imports: [ - WorkflowItemsEditPageRoutingModule, - CommonModule, - SharedModule, - SubmissionModule, - StatisticsModule, - ItemPageModule, - AccessControlModule - ], + imports: [ + WorkflowItemsEditPageRoutingModule, + CommonModule, + SharedModule, + SubmissionModule, + StatisticsModule, + ItemPageModule, + AccessControlModule, + FormModule, + RatingModule, + ], declarations: [ WorkflowItemDeleteComponent, ThemedWorkflowItemDeleteComponent, WorkflowItemSendBackComponent, ThemedWorkflowItemSendBackComponent, AdvancedWorkflowActionsLoaderComponent, - AdvancedWorkflowActionRatingReviewerComponent, + AdvancedWorkflowActionRatingComponent, AdvancedWorkflowActionSelectReviewerComponent, AdvancedWorkflowActionPageComponent, AdvancedClaimedTaskActionsDirective, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 97af59d159..57f93ee906 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -539,6 +539,17 @@ "admin.metadata-import.page.error.addFile": "Select file first!", + "advanced-workflow-action.rating.form.rating.label": "Rating", + + "advanced-workflow-action.rating.form.review.label": "Review", + + "advanced-workflow-action.rating.form.review.error": "You must enter a review to submit this rating", + + "advanced-workflow-action.rating.description": "Please select a rating below", + + "advanced-workflow-action.rating.description-requiredDescription": "Please select a rating below and also add a review", + + "advanced-workflow-action.select-reviewer.description-single": "Please select a single reviewer below before submitting", "advanced-workflow-action.select-reviewer.description-multiple": "Please select one or more reviewers below before submitting", @@ -4061,9 +4072,9 @@ "submission.workflow.generic.submit_select_reviewer-help": "", - "submission.workflow.generic.submit_rating_reviewer": "Rating Reviewer", + "submission.workflow.generic.rating": "Rate", - "submission.workflow.generic.submit_rating_reviewer-help": "", + "submission.workflow.generic.rating-help": "", "submission.workflow.tasks.claimed.approve": "Approve", @@ -4241,8 +4252,21 @@ "workflow-item.selectrevieweraction.button.confirm": "Confirm", - "workflow-item.ratingrevieweraction.header": "Rating Reviewer", + "workflow-item.ratingreviewaction.notification.success.title": "Rating review", + "workflow-item.ratingreviewaction.notification.success.content": "The rating for this item workflow item has been successfully submitted", + + "workflow-item.ratingreviewaction.notification.error.title": "Something went wrong", + + "workflow-item.ratingreviewaction.notification.error.content": "Couldn't rate this item", + + "workflow-item.ratingreviewaction.title": "Rate this item", + + "workflow-item.ratingreviewaction.header": "Rate this item", + + "workflow-item.ratingreviewaction.button.cancel": "Cancel", + + "workflow-item.ratingreviewaction.button.confirm": "Confirm", "idle-modal.header": "Session will expire soon", diff --git a/yarn.lock b/yarn.lock index 0f8ef0a6e6..555bce24ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9357,6 +9357,13 @@ ng2-nouislider@^1.8.3: resolved "https://registry.yarnpkg.com/ng2-nouislider/-/ng2-nouislider-1.8.3.tgz#c9c1ec77b6b5a909f87368ba799914208baa13a6" integrity sha512-Vl8tHCcJ/ioJLAs2t6FBC35sZq1P/O5ZdqdFwYxOCOMVbILGWNg+2gWZIjFstvv9pqb/mVvVUYe6qGG/mA/RBQ== +ngx-bootstrap@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/ngx-bootstrap/-/ngx-bootstrap-7.1.2.tgz#cf06a05cc22a74b882b45882a1f64f72ddece23d" + integrity sha512-VsZwlGVM+qYOAdvEzzH3wqCjvj3yHec4n82Tho0ve6mhN1GTRu9WPrPmvxp3cFjIu8VQbREUeKPfK0ooO8U5eg== + dependencies: + tslib "^2.0.0" + ngx-infinite-scroll@^10.0.1: version "10.0.1" resolved "https://registry.yarnpkg.com/ngx-infinite-scroll/-/ngx-infinite-scroll-10.0.1.tgz#6f51f2f8775a7c50d1dd8bad125d4e748abbe880" From 11b6ec9a9e86f2b39a1fd6b588681b483f879e5a Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 11 Jan 2023 20:28:54 +0100 Subject: [PATCH 049/173] 98376: Replaced EPersonListComponent with MembersListComponent & display error for empty reviewers list --- .../access-control/access-control.module.ts | 4 +- .../eperson-list/eperson-list.component.html | 146 ------- .../eperson-list.component.spec.ts | 247 ------------ .../eperson-list/eperson-list.component.ts | 358 ------------------ .../members-list/members-list.component.html | 29 +- .../members-list/members-list.component.ts | 131 ++++--- .../modify-item-overview.component.html | 2 +- ...flow-action-select-reviewer.component.html | 5 +- ...rkflow-action-select-reviewer.component.ts | 13 +- .../reviewers-list.component.ts | 11 +- src/assets/i18n/en.json5 | 2 + 11 files changed, 123 insertions(+), 825 deletions(-) delete mode 100644 src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.html delete mode 100644 src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.spec.ts delete mode 100644 src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.ts diff --git a/src/app/access-control/access-control.module.ts b/src/app/access-control/access-control.module.ts index 2c9932d387..99aaaaf133 100644 --- a/src/app/access-control/access-control.module.ts +++ b/src/app/access-control/access-control.module.ts @@ -10,7 +10,6 @@ import { MembersListComponent } from './group-registry/group-form/members-list/m import { SubgroupsListComponent } from './group-registry/group-form/subgroup-list/subgroups-list.component'; import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; import { FormModule } from '../shared/form/form.module'; -import { EPersonListComponent } from './group-registry/group-form/eperson-list/eperson-list.component'; @NgModule({ imports: [ @@ -21,7 +20,7 @@ import { EPersonListComponent } from './group-registry/group-form/eperson-list/e FormModule, ], exports: [ - EPersonListComponent, + MembersListComponent, ], declarations: [ EPeopleRegistryComponent, @@ -30,7 +29,6 @@ import { EPersonListComponent } from './group-registry/group-form/eperson-list/e GroupFormComponent, SubgroupsListComponent, MembersListComponent, - EPersonListComponent, ], }) /** diff --git a/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.html b/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.html deleted file mode 100644 index e4a507ae19..0000000000 --- a/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.html +++ /dev/null @@ -1,146 +0,0 @@ - -

{{messagePrefix + '.head' | translate}}

- - -
-
- -
-
-
- - - - -
-
-
- -
-
- - - -
- - - - - - - - - - - - - - - - - -
{{messagePrefix + '.table.id' | translate}}{{messagePrefix + '.table.name' | translate}}{{messagePrefix + '.table.identity' | translate}}{{messagePrefix + '.table.edit' | translate}}
{{ePerson.eperson.id}}{{ePerson.eperson.name}} - {{messagePrefix + '.table.email' | translate}}: {{ ePerson.eperson.email ? ePerson.eperson.email : '-' }}
- {{messagePrefix + '.table.netid' | translate}}: {{ ePerson.eperson.netid ? ePerson.eperson.netid : '-' }} -
-
- - - -
-
-
- -
- - - -

{{messagePrefix + '.headMembers' | translate}}

- - - -
- - - - - - - - - - - - - - - - - -
{{messagePrefix + '.table.id' | translate}}{{messagePrefix + '.table.name' | translate}}{{messagePrefix + '.table.identity' | translate}}{{messagePrefix + '.table.edit' | translate}}
{{ePerson.eperson.id}}{{ePerson.eperson.name}} - {{messagePrefix + '.table.email' | translate}}: {{ ePerson.eperson.email ? ePerson.eperson.email : '-' }}
- {{messagePrefix + '.table.netid' | translate}}: {{ ePerson.eperson.netid ? ePerson.eperson.netid : '-' }} -
-
- - -
-
-
- -
- - - -
diff --git a/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.spec.ts b/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.spec.ts deleted file mode 100644 index 8077139026..0000000000 --- a/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.spec.ts +++ /dev/null @@ -1,247 +0,0 @@ -import { CommonModule } from '@angular/common'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { BrowserModule, By } from '@angular/platform-browser'; -import { Router } from '@angular/router'; -import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core'; -import { Observable, of as observableOf } from 'rxjs'; -import { RestResponse } from '../../../../core/cache/response.models'; -import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; -import { GroupDataService } from '../../../../core/eperson/group-data.service'; -import { EPerson } from '../../../../core/eperson/models/eperson.model'; -import { Group } from '../../../../core/eperson/models/group.model'; -import { PageInfo } from '../../../../core/shared/page-info.model'; -import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; -import { NotificationsService } from '../../../../shared/notifications/notifications.service'; -import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock'; -import { EPersonListComponent } from './eperson-list.component'; -import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock'; -import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; -import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock'; -import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock'; -import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock'; -import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub'; -import { RouterMock } from '../../../../shared/mocks/router.mock'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; - -describe('EPersonListComponent', () => { - let component: EPersonListComponent; - let fixture: ComponentFixture; - let translateService: TranslateService; - let builderService: FormBuilderService; - let ePersonDataServiceStub: any; - let groupsDataServiceStub: any; - let activeGroup; - let allEPersons; - let allGroups; - let epersonMembers; - let subgroupMembers; - let paginationService; - - beforeEach(waitForAsync(() => { - activeGroup = GroupMock; - epersonMembers = [EPersonMock2]; - subgroupMembers = [GroupMock2]; - allEPersons = [EPersonMock, EPersonMock2]; - allGroups = [GroupMock, GroupMock2]; - ePersonDataServiceStub = { - activeGroup: activeGroup, - epersonMembers: epersonMembers, - subgroupMembers: subgroupMembers, - findAllByHref(href: string): Observable>> { - return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupsDataServiceStub.getEPersonMembers())); - }, - searchByScope(scope: string, query: string): Observable>> { - if (query === '') { - return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), allEPersons)); - } - return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])); - }, - clearEPersonRequests() { - // empty - }, - clearLinkRequests() { - // empty - }, - getEPeoplePageRouterLink(): string { - return '/access-control/epeople'; - } - }; - groupsDataServiceStub = { - activeGroup: activeGroup, - epersonMembers: epersonMembers, - subgroupMembers: subgroupMembers, - allGroups: allGroups, - getActiveGroup(): Observable { - return observableOf(activeGroup); - }, - getEPersonMembers() { - return this.epersonMembers; - }, - searchGroups(query: string): Observable>> { - if (query === '') { - return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), this.allGroups)); - } - return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])); - }, - addMemberToGroup(parentGroup, eperson: EPerson): Observable { - this.epersonMembers = [...this.epersonMembers, eperson]; - return observableOf(new RestResponse(true, 200, 'Success')); - }, - clearGroupsRequests() { - // empty - }, - clearGroupLinkRequests() { - // empty - }, - getGroupEditPageRouterLink(group: Group): string { - return '/access-control/groups/' + group.id; - }, - deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable { - this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => { - if (eperson.id !== epersonToDelete.id) { - return eperson; - } - }); - if (this.epersonMembers === undefined) { - this.epersonMembers = []; - } - return observableOf(new RestResponse(true, 200, 'Success')); - } - }; - builderService = getMockFormBuilderService(); - translateService = getMockTranslateService(); - - paginationService = new PaginationServiceStub(); - TestBed.configureTestingModule({ - imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, - TranslateModule.forRoot({ - loader: { - provide: TranslateLoader, - useClass: TranslateLoaderMock - } - }), - ], - declarations: [EPersonListComponent], - providers: [EPersonListComponent, - { provide: EPersonDataService, useValue: ePersonDataServiceStub }, - { provide: GroupDataService, useValue: groupsDataServiceStub }, - { provide: NotificationsService, useValue: new NotificationsServiceStub() }, - { provide: FormBuilderService, useValue: builderService }, - { provide: Router, useValue: new RouterMock() }, - { provide: PaginationService, useValue: paginationService }, - ], - schemas: [NO_ERRORS_SCHEMA] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(EPersonListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - afterEach(fakeAsync(() => { - fixture.destroy(); - flush(); - component = null; - fixture.debugElement.nativeElement.remove(); - })); - - it('should create EpeopleListComponent', inject([EPersonListComponent], (comp: EPersonListComponent) => { - expect(comp).toBeDefined(); - })); - - it('should show list of eperson members of current active group', () => { - const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); - expect(epersonIdsFound.length).toEqual(1); - epersonMembers.map((eperson: EPerson) => { - expect(epersonIdsFound.find((foundEl) => { - return (foundEl.nativeElement.textContent.trim() === eperson.uuid); - })).toBeTruthy(); - }); - }); - - describe('search', () => { - describe('when searching without query', () => { - let epersonsFound; - beforeEach(fakeAsync(() => { - component.search({ scope: 'metadata', query: '' }); - tick(); - fixture.detectChanges(); - epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr')); - })); - - it('should display all epersons', () => { - expect(epersonsFound.length).toEqual(2); - }); - - describe('if eperson is already a eperson', () => { - it('should have delete button, else it should have add button', () => { - activeGroup.epersons.map((eperson: EPerson) => { - epersonsFound.map((foundEPersonRowElement) => { - if (foundEPersonRowElement.debugElement !== undefined) { - const epersonId = foundEPersonRowElement.debugElement.query(By.css('td:first-child')); - const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus')); - const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt')); - if (epersonId.nativeElement.textContent === eperson.id) { - expect(addButton).toBeUndefined(); - expect(deleteButton).toBeDefined(); - } else { - expect(deleteButton).toBeUndefined(); - expect(addButton).toBeDefined(); - } - } - }); - }); - }); - }); - - describe('if first add button is pressed', () => { - beforeEach(fakeAsync(() => { - const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-plus')); - addButton.nativeElement.click(); - tick(); - fixture.detectChanges(); - })); - it('all groups in search member of selected group', () => { - epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr')); - expect(epersonsFound.length).toEqual(2); - epersonsFound.map((foundEPersonRowElement) => { - if (foundEPersonRowElement.debugElement !== undefined) { - const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus')); - const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt')); - expect(addButton).toBeUndefined(); - expect(deleteButton).toBeDefined(); - } - }); - }); - }); - - describe('if first delete button is pressed', () => { - beforeEach(fakeAsync(() => { - const addButton = fixture.debugElement.query(By.css('#epersonsSearch tbody .fa-trash-alt')); - addButton.nativeElement.click(); - tick(); - fixture.detectChanges(); - })); - it('first eperson in search delete button, because now member', () => { - epersonsFound = fixture.debugElement.queryAll(By.css('#epersonsSearch tbody tr')); - epersonsFound.map((foundEPersonRowElement) => { - if (foundEPersonRowElement.debugElement !== undefined) { - const addButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-plus')); - const deleteButton = foundEPersonRowElement.debugElement.query(By.css('td:last-child .fa-trash-alt')); - expect(deleteButton).toBeUndefined(); - expect(addButton).toBeDefined(); - } - }); - }); - }); - }); - }); - -}); diff --git a/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.ts b/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.ts deleted file mode 100644 index 9eafe10daa..0000000000 --- a/src/app/access-control/group-registry/group-form/eperson-list/eperson-list.component.ts +++ /dev/null @@ -1,358 +0,0 @@ -import { Component, OnDestroy, OnInit, Input } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; -import { Router } from '@angular/router'; -import { TranslateService } from '@ngx-translate/core'; -import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; -import { GroupDataService } from '../../../../core/eperson/group-data.service'; -import { NotificationsService } from '../../../../shared/notifications/notifications.service'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; -import { Group } from '../../../../core/eperson/models/group.model'; -import { - getAllCompletedRemoteData, - getFirstSucceededRemoteData, - getRemoteDataPayload, - getFirstCompletedRemoteData -} from '../../../../core/shared/operators'; -import { - BehaviorSubject, - Subscription, - combineLatest as observableCombineLatest, - Observable, - ObservedValueOf, - of as observableOf -} from 'rxjs'; -import { PaginatedList, buildPaginatedList } from '../../../../core/data/paginated-list.model'; -import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model'; -import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; -import { switchMap, map, take, mergeMap } from 'rxjs/operators'; -import { RemoteData } from '../../../../core/data/remote-data'; -import { EPerson } from '../../../../core/eperson/models/eperson.model'; - -/** - * Keys to keep track of specific subscriptions - */ -enum SubKey { - ActiveGroup, - MembersDTO, - SearchResultsDTO, -} - -export interface EPersonActionConfig { - css?: string; - disabled: boolean; - icon: string; -} - -export interface EPersonListActionConfig { - add: EPersonActionConfig; - remove: EPersonActionConfig; -} - -@Component({ - selector: 'ds-eperson-list', - templateUrl: './eperson-list.component.html' -}) -export class EPersonListComponent implements OnInit, OnDestroy { - - @Input() - messagePrefix: string; - - @Input() - actionConfig: EPersonListActionConfig = { - add: { - css: 'btn-outline-primary', - disabled: false, - icon: 'fas fa-plus fa-fw', - }, - remove: { - css: 'btn-outline-danger', - disabled: false, - icon: 'fas fa-trash-alt fa-fw' - }, - }; - - /** - * EPeople being displayed in search result, initially all members, after search result of search - */ - ePeopleSearchDtos: BehaviorSubject> = new BehaviorSubject>(undefined); - /** - * List of EPeople members of currently active group being edited - */ - ePeopleMembersOfGroupDtos: BehaviorSubject> = new BehaviorSubject>(undefined); - - /** - * Pagination config used to display the list of EPeople that are result of EPeople search - */ - configSearch: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'sml', - pageSize: 5, - currentPage: 1 - }); - /** - * Pagination config used to display the list of EPerson Membes of active group being edited - */ - config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { - id: 'ml', - pageSize: 5, - currentPage: 1 - }); - - /** - * Map of active subscriptions - */ - subs: Map = new Map(); - - // The search form - searchForm; - - // Current search in edit group - epeople search form - currentSearchQuery: string; - currentSearchScope: string; - - // Whether or not user has done a EPeople search yet - searchDone: boolean; - - // current active group being edited - groupBeingEdited: Group; - - constructor( - protected groupDataService: GroupDataService, - public ePersonDataService: EPersonDataService, - protected translateService: TranslateService, - protected notificationsService: NotificationsService, - protected formBuilder: FormBuilder, - protected paginationService: PaginationService, - private router: Router - ) { - this.currentSearchQuery = ''; - this.currentSearchScope = 'metadata'; - } - - ngOnInit(): void { - this.searchForm = this.formBuilder.group(({ - scope: 'metadata', - query: '', - })); - this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => { - if (activeGroup != null) { - this.groupBeingEdited = activeGroup; - this.retrieveMembers(this.config.currentPage); - } - })); - } - - /** - * Retrieve the EPersons that are members of the group - * - * @param page the number of the page to retrieve - * @private - */ - retrieveMembers(page: number): void { - this.unsubFrom(SubKey.MembersDTO); - this.subs.set(SubKey.MembersDTO, - this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( - switchMap((currentPagination) => { - return this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, { - currentPage: currentPagination.currentPage, - elementsPerPage: currentPagination.pageSize - } - ); - }), - getAllCompletedRemoteData(), - map((rd: RemoteData) => { - if (rd.hasFailed) { - this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage })); - } else { - return rd; - } - }), - switchMap((epersonListRD: RemoteData>) => { - const dtos$ = observableCombineLatest(...epersonListRD.payload.page.map((member: EPerson) => { - const dto$: Observable = observableCombineLatest( - this.isMemberOfGroup(member), (isMember: ObservedValueOf>) => { - const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel(); - epersonDtoModel.eperson = member; - epersonDtoModel.memberOfGroup = isMember; - return epersonDtoModel; - }); - return dto$; - })); - return dtos$.pipe(map((dtos: EpersonDtoModel[]) => { - return buildPaginatedList(epersonListRD.payload.pageInfo, dtos); - })); - })) - .subscribe((paginatedListOfDTOs: PaginatedList) => { - this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs); - })); - } - - /** - * Whether the given ePerson is a member of the group currently being edited - * @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited - */ - isMemberOfGroup(possibleMember: EPerson): Observable { - return this.groupDataService.getActiveGroup().pipe(take(1), - mergeMap((group: Group) => { - if (group != null) { - return this.ePersonDataService.findAllByHref(group._links.epersons.href, { - currentPage: 1, - elementsPerPage: 9999 - }, false) - .pipe( - getFirstSucceededRemoteData(), - getRemoteDataPayload(), - map((listEPeopleInGroup: PaginatedList) => listEPeopleInGroup.page.filter((ePersonInList: EPerson) => ePersonInList.id === possibleMember.id)), - map((epeople: EPerson[]) => epeople.length > 0)); - } else { - return observableOf(false); - } - })); - } - - /** - * Unsubscribe from a subscription if it's still subscribed, and remove it from the map of - * active subscriptions - * - * @param key The key of the subscription to unsubscribe from - * @private - */ - protected unsubFrom(key: SubKey) { - if (this.subs.has(key)) { - this.subs.get(key).unsubscribe(); - this.subs.delete(key); - } - } - - /** - * Deletes a given EPerson from the members list of the group currently being edited - * @param ePerson EPerson we want to delete as member from group that is currently being edited - */ - deleteMemberFromGroup(ePerson: EpersonDtoModel) { - this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => { - if (activeGroup != null) { - const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson.eperson); - this.showNotifications('deleteMember', response, ePerson.eperson.name, activeGroup); - this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); - } else { - this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup')); - } - }); - } - - /** - * Adds a given EPerson to the members list of the group currently being edited - * @param ePerson EPerson we want to add as member to group that is currently being edited - */ - addMemberToGroup(ePerson: EpersonDtoModel) { - ePerson.memberOfGroup = true; - this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => { - if (activeGroup != null) { - const response = this.groupDataService.addMemberToGroup(activeGroup, ePerson.eperson); - this.showNotifications('addMember', response, ePerson.eperson.name, activeGroup); - } else { - this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup')); - } - }); - } - - /** - * Search in the EPeople by name, email or metadata - * @param data Contains scope and query param - */ - search(data: any) { - this.unsubFrom(SubKey.SearchResultsDTO); - this.subs.set(SubKey.SearchResultsDTO, - this.paginationService.getCurrentPagination(this.configSearch.id, this.configSearch).pipe( - switchMap((paginationOptions) => { - - const query: string = data.query; - const scope: string = data.scope; - if (query != null && this.currentSearchQuery !== query && this.groupBeingEdited) { - this.router.navigate([], { - queryParamsHandling: 'merge' - }); - this.currentSearchQuery = query; - this.paginationService.resetPage(this.configSearch.id); - } - if (scope != null && this.currentSearchScope !== scope && this.groupBeingEdited) { - this.router.navigate([], { - queryParamsHandling: 'merge' - }); - this.currentSearchScope = scope; - this.paginationService.resetPage(this.configSearch.id); - } - this.searchDone = true; - - return this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, { - currentPage: paginationOptions.currentPage, - elementsPerPage: paginationOptions.pageSize - }); - }), - getAllCompletedRemoteData(), - map((rd: RemoteData) => { - if (rd.hasFailed) { - this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage })); - } else { - return rd; - } - }), - switchMap((epersonListRD: RemoteData>) => { - const dtos$ = observableCombineLatest(...epersonListRD.payload.page.map((member: EPerson) => { - const dto$: Observable = observableCombineLatest( - this.isMemberOfGroup(member), (isMember: ObservedValueOf>) => { - const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel(); - epersonDtoModel.eperson = member; - epersonDtoModel.memberOfGroup = isMember; - return epersonDtoModel; - }); - return dto$; - })); - return dtos$.pipe(map((dtos: EpersonDtoModel[]) => { - return buildPaginatedList(epersonListRD.payload.pageInfo, dtos); - })); - })) - .subscribe((paginatedListOfDTOs: PaginatedList) => { - this.ePeopleSearchDtos.next(paginatedListOfDTOs); - })); - } - - /** - * unsub all subscriptions - */ - ngOnDestroy(): void { - for (const key of this.subs.keys()) { - this.unsubFrom(key); - } - this.paginationService.clearPagination(this.config.id); - this.paginationService.clearPagination(this.configSearch.id); - } - - /** - * Shows a notification based on the success/failure of the request - * @param messageSuffix Suffix for message - * @param response RestResponse observable containing success/failure request - * @param nameObject Object request was about - * @param activeGroup Group currently being edited - */ - showNotifications(messageSuffix: string, response: Observable>, nameObject: string, activeGroup: Group) { - response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData) => { - if (rd.hasSucceeded) { - this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject })); - this.ePersonDataService.clearLinkRequests(activeGroup._links.epersons.href); - } else { - this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject })); - } - }); - } - - /** - * Reset all input-fields to be empty and search all search - */ - clearFormAndResetResult() { - this.searchForm.patchValue({ - query: '', - }); - this.search({ query: '' }); - } - -} diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.html b/src/app/access-control/group-registry/group-form/members-list/members-list.component.html index e5932edf05..e4a507ae19 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.html +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.html @@ -55,18 +55,20 @@
- -
@@ -113,10 +115,19 @@
- +
diff --git a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts index 8bc540641e..bdeb49decd 100644 --- a/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts +++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts @@ -1,30 +1,32 @@ -import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit, Input } from '@angular/core'; import { FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { - Observable, - of as observableOf, - Subscription, - BehaviorSubject, - combineLatest as observableCombineLatest, - ObservedValueOf, -} from 'rxjs'; -import { map, mergeMap, switchMap, take } from 'rxjs/operators'; -import {buildPaginatedList, PaginatedList} from '../../../../core/data/paginated-list.model'; -import { RemoteData } from '../../../../core/data/remote-data'; import { EPersonDataService } from '../../../../core/eperson/eperson-data.service'; import { GroupDataService } from '../../../../core/eperson/group-data.service'; -import { EPerson } from '../../../../core/eperson/models/eperson.model'; +import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; import { Group } from '../../../../core/eperson/models/group.model'; import { + getAllCompletedRemoteData, getFirstSucceededRemoteData, - getFirstCompletedRemoteData, getAllCompletedRemoteData, getRemoteDataPayload + getRemoteDataPayload, + getFirstCompletedRemoteData } from '../../../../core/shared/operators'; -import { NotificationsService } from '../../../../shared/notifications/notifications.service'; +import { + BehaviorSubject, + Subscription, + combineLatest as observableCombineLatest, + Observable, + ObservedValueOf, + of as observableOf +} from 'rxjs'; +import { PaginatedList, buildPaginatedList } from '../../../../core/data/paginated-list.model'; +import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model'; import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; -import {EpersonDtoModel} from '../../../../core/eperson/models/eperson-dto.model'; -import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { switchMap, map, take, mergeMap } from 'rxjs/operators'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { EPerson } from '../../../../core/eperson/models/eperson.model'; /** * Keys to keep track of specific subscriptions @@ -35,6 +37,17 @@ enum SubKey { SearchResultsDTO, } +export interface EPersonActionConfig { + css?: string; + disabled: boolean; + icon: string; +} + +export interface EPersonListActionConfig { + add: EPersonActionConfig; + remove: EPersonActionConfig; +} + @Component({ selector: 'ds-members-list', templateUrl: './members-list.component.html' @@ -47,6 +60,20 @@ export class MembersListComponent implements OnInit, OnDestroy { @Input() messagePrefix: string; + @Input() + actionConfig: EPersonListActionConfig = { + add: { + css: 'btn-outline-primary', + disabled: false, + icon: 'fas fa-plus fa-fw', + }, + remove: { + css: 'btn-outline-danger', + disabled: false, + icon: 'fas fa-trash-alt fa-fw' + }, + }; + /** * EPeople being displayed in search result, initially all members, after search result of search */ @@ -91,23 +118,20 @@ export class MembersListComponent implements OnInit, OnDestroy { // current active group being edited groupBeingEdited: Group; - paginationSub: Subscription; - - constructor( protected groupDataService: GroupDataService, public ePersonDataService: EPersonDataService, - private translateService: TranslateService, - private notificationsService: NotificationsService, + protected translateService: TranslateService, + protected notificationsService: NotificationsService, protected formBuilder: FormBuilder, - private paginationService: PaginationService, + protected paginationService: PaginationService, private router: Router ) { this.currentSearchQuery = ''; this.currentSearchScope = 'metadata'; } - ngOnInit() { + ngOnInit(): void { this.searchForm = this.formBuilder.group(({ scope: 'metadata', query: '', @@ -126,7 +150,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * @param page the number of the page to retrieve * @private */ - protected retrieveMembers(page: number) { + retrieveMembers(page: number): void { this.unsubFrom(SubKey.MembersDTO); this.subs.set(SubKey.MembersDTO, this.paginationService.getCurrentPagination(this.config.id, this.config).pipe( @@ -137,36 +161,36 @@ export class MembersListComponent implements OnInit, OnDestroy { } ); }), - getAllCompletedRemoteData(), - map((rd: RemoteData) => { - if (rd.hasFailed) { - this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', {cause: rd.errorMessage})); - } else { - return rd; - } - }), - switchMap((epersonListRD: RemoteData>) => { - const dtos$ = observableCombineLatest(...epersonListRD.payload.page.map((member: EPerson) => { - const dto$: Observable = observableCombineLatest( - this.isMemberOfGroup(member), (isMember: ObservedValueOf>) => { - const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel(); - epersonDtoModel.eperson = member; - epersonDtoModel.memberOfGroup = isMember; - return epersonDtoModel; - }); - return dto$; + getAllCompletedRemoteData(), + map((rd: RemoteData) => { + if (rd.hasFailed) { + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage })); + } else { + return rd; + } + }), + switchMap((epersonListRD: RemoteData>) => { + const dtos$ = observableCombineLatest(...epersonListRD.payload.page.map((member: EPerson) => { + const dto$: Observable = observableCombineLatest( + this.isMemberOfGroup(member), (isMember: ObservedValueOf>) => { + const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel(); + epersonDtoModel.eperson = member; + epersonDtoModel.memberOfGroup = isMember; + return epersonDtoModel; + }); + return dto$; + })); + return dtos$.pipe(map((dtos: EpersonDtoModel[]) => { + return buildPaginatedList(epersonListRD.payload.pageInfo, dtos); + })); + })) + .subscribe((paginatedListOfDTOs: PaginatedList) => { + this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs); })); - return dtos$.pipe(map((dtos: EpersonDtoModel[]) => { - return buildPaginatedList(epersonListRD.payload.pageInfo, dtos); - })); - })) - .subscribe((paginatedListOfDTOs: PaginatedList) => { - this.ePeopleMembersOfGroupDtos.next(paginatedListOfDTOs); - })); } /** - * Whether or not the given ePerson is a member of the group currently being edited + * Whether the given ePerson is a member of the group currently being edited * @param possibleMember EPerson that is a possible member (being tested) of the group currently being edited */ isMemberOfGroup(possibleMember: EPerson): Observable { @@ -195,7 +219,7 @@ export class MembersListComponent implements OnInit, OnDestroy { * @param key The key of the subscription to unsubscribe from * @private */ - private unsubFrom(key: SubKey) { + protected unsubFrom(key: SubKey) { if (this.subs.has(key)) { this.subs.get(key).unsubscribe(); this.subs.delete(key); @@ -270,7 +294,7 @@ export class MembersListComponent implements OnInit, OnDestroy { getAllCompletedRemoteData(), map((rd: RemoteData) => { if (rd.hasFailed) { - this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', {cause: rd.errorMessage})); + this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure', { cause: rd.errorMessage })); } else { return rd; } @@ -333,4 +357,5 @@ export class MembersListComponent implements OnInit, OnDestroy { }); this.search({ query: '' }); } + } diff --git a/src/app/item-page/edit-item-page/modify-item-overview/modify-item-overview.component.html b/src/app/item-page/edit-item-page/modify-item-overview/modify-item-overview.component.html index ce6e01df3d..dbef279d8c 100644 --- a/src/app/item-page/edit-item-page/modify-item-overview/modify-item-overview.component.html +++ b/src/app/item-page/edit-item-page/modify-item-overview/modify-item-overview.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.html index 3009cc0771..d4ac620811 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.html +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.html @@ -7,9 +7,12 @@ [groupId]="groupId" [ngClass]="groupId ? 'reviewersListWithGroup' : ''" [multipleReviewers]="multipleReviewers" - (selectedReviewersUpdated)="selectedReviewers = $event" + (selectedReviewersUpdated)="selectedReviewers = $event; displayError = false" messagePrefix="advanced-workflow-action-select-reviewer.groups.form.reviewers-list" > + + {{ 'advanced-workflow-action.select-reviewer.no-reviewer-selected.error' | translate }} + diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index d7a885f067..f766c97f39 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -9,7 +9,7 @@ import { } from '../../../core/tasks/models/select-reviewer-action-advanced-info.model'; import { EPersonListActionConfig -} from '../../../access-control/group-registry/group-form/eperson-list/eperson-list.component'; +} from '../../../access-control/group-registry/group-form/members-list/members-list.component'; import { Subscription } from 'rxjs'; import { EPerson } from '../../../core/eperson/models/eperson.model'; @@ -38,6 +38,8 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf subs: Subscription[] = []; + displayError = false; + ngOnDestroy(): void { this.subs.forEach((subscription: Subscription) => subscription.unsubscribe()); } @@ -84,6 +86,15 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf return ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER; } + performAction(): void { + if (this.selectedReviewers.length > 0) { + super.performAction(); + } else { + this.displayError = true; + } + console.log(this.displayError); + } + createBody(): any { return { [WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER]: true, diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts index 14159450ff..0cd485e638 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts @@ -8,15 +8,14 @@ import { NotificationsService } from '../../../../shared/notifications/notificat import { PaginationService } from '../../../../core/pagination/pagination.service'; import { Group } from '../../../../core/eperson/models/group.model'; import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; -import { - EPersonListComponent, - EPersonListActionConfig -} from '../../../../access-control/group-registry/group-form/eperson-list/eperson-list.component'; import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model'; import { EPerson } from '../../../../core/eperson/models/eperson.model'; import { Observable, of as observableOf } from 'rxjs'; import { hasValue } from '../../../../shared/empty.util'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; +import { + MembersListComponent, EPersonListActionConfig +} from '../../../../access-control/group-registry/group-form/members-list/members-list.component'; /** * Keys to keep track of specific subscriptions @@ -30,9 +29,9 @@ enum SubKey { @Component({ selector: 'ds-reviewers-list', // templateUrl: './reviewers-list.component.html', - templateUrl: '../../../../access-control/group-registry/group-form/eperson-list/eperson-list.component.html', + templateUrl: '../../../../access-control/group-registry/group-form/members-list/members-list.component.html', }) -export class ReviewersListComponent extends EPersonListComponent implements OnInit, OnChanges, OnDestroy { +export class ReviewersListComponent extends MembersListComponent implements OnInit, OnChanges, OnDestroy { @Input() groupId: string | null; diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 57f93ee906..aa2f59e7ee 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -599,6 +599,8 @@ "advanced-workflow-action-select-reviewer.groups.form.reviewers-list.no-items": "No EPeople found in that search", + "advanced-workflow-action.select-reviewer.no-reviewer-selected.error": "No reviewer selected.", + "auth.errors.invalid-user": "Invalid email address or password.", From bd2135bcd29d3a459a5f2d25295db7fcb62b0dc5 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 11 Jan 2023 20:45:04 +0100 Subject: [PATCH 050/173] 98376: scoreReview workflow - slight refactor REST changes --- ...vanced-workflow-action-rating.component.ts | 8 ++++---- src/assets/i18n/en.json5 | 20 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts index c669cfcfe9..eb06e49243 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts @@ -9,8 +9,8 @@ import { RatingReviewerActionAdvancedInfo } from '../../../core/tasks/models/rating-reviewer-action-advanced-info.model'; -export const WORKFLOW_ADVANCED_TASK_OPTION_RATING = 'rating'; -export const ADVANCED_WORKFLOW_ACTION_RATING = 'ratingreviewaction'; +export const WORKFLOW_ADVANCED_TASK_OPTION_RATING = 'submit_score'; +export const ADVANCED_WORKFLOW_ACTION_RATING = 'scorereviewaction'; @rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_RATING) @Component({ @@ -49,8 +49,8 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio score: this.ratingForm.get('rating').value, }; if (this.ratingForm.get('review').value !== '') { - const description: string = this.ratingForm.get('review').value; - Object.assign(body, { description }); + const review: string = this.ratingForm.get('review').value; + Object.assign(body, { review: review }); } return body; } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index aa2f59e7ee..6b5b317c6e 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4074,9 +4074,9 @@ "submission.workflow.generic.submit_select_reviewer-help": "", - "submission.workflow.generic.rating": "Rate", + "submission.workflow.generic.submit_score": "Rate", - "submission.workflow.generic.rating-help": "", + "submission.workflow.generic.submit_score-help": "", "submission.workflow.tasks.claimed.approve": "Approve", @@ -4254,21 +4254,21 @@ "workflow-item.selectrevieweraction.button.confirm": "Confirm", - "workflow-item.ratingreviewaction.notification.success.title": "Rating review", + "workflow-item.scorereviewaction.notification.success.title": "Rating review", - "workflow-item.ratingreviewaction.notification.success.content": "The rating for this item workflow item has been successfully submitted", + "workflow-item.scorereviewaction.notification.success.content": "The rating for this item workflow item has been successfully submitted", - "workflow-item.ratingreviewaction.notification.error.title": "Something went wrong", + "workflow-item.scorereviewaction.notification.error.title": "Something went wrong", - "workflow-item.ratingreviewaction.notification.error.content": "Couldn't rate this item", + "workflow-item.scorereviewaction.notification.error.content": "Couldn't rate this item", - "workflow-item.ratingreviewaction.title": "Rate this item", + "workflow-item.scorereviewaction.title": "Rate this item", - "workflow-item.ratingreviewaction.header": "Rate this item", + "workflow-item.scorereviewaction.header": "Rate this item", - "workflow-item.ratingreviewaction.button.cancel": "Cancel", + "workflow-item.scorereviewaction.button.cancel": "Cancel", - "workflow-item.ratingreviewaction.button.confirm": "Confirm", + "workflow-item.scorereviewaction.button.confirm": "Confirm", "idle-modal.header": "Session will expire soon", From 4d2522a69aa4758c50cd0d0e397b4eab0f32370d Mon Sep 17 00:00:00 2001 From: Sufiyan Shaikh Date: Thu, 12 Jan 2023 19:00:44 +0530 Subject: [PATCH 051/173] [CST-7755] Build Failded Fixed --- ...search-result-list-element.component.spec.ts | 17 ++++++++++++++--- ...search-result-list-element.component.spec.ts | 17 ++++++++++++++--- ...search-result-list-element.component.spec.ts | 16 +++++++++++++--- ...search-result-list-element.component.spec.ts | 16 +++++++++++++--- ...search-result-list-element.component.spec.ts | 16 +++++++++++++--- ...search-result-list-element.component.spec.ts | 17 ++++++++++++++--- 6 files changed, 81 insertions(+), 18 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts index 9f8963b60b..3a0e73e46c 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts @@ -13,7 +13,7 @@ import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; -import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { PageInfo } from '../../../../../core/shared/page-info.model'; import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; import { GroupMock } from '../../../../../shared/testing/group-mock'; @@ -24,6 +24,8 @@ import { EPersonDataService } from '../../../../../core/eperson/eperson-data.ser import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; +import { EPerson } from '../../../../../core/eperson/models/eperson.model'; +import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let journalIssueListElementComponent: JournalIssueSearchResultListElementComponent; let fixture: ComponentFixture; @@ -37,6 +39,15 @@ const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { } }); +const user = Object.assign(new EPerson(), { + id: 'userId', + groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), + _links: { self: { href: 'test.com/uuid/1234567654321' } } +}); +const epersonService = jasmine.createSpyObj('epersonService', { + findById: createSuccessfulRemoteDataObject$(user), +}); + const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), { @@ -150,7 +161,7 @@ describe('JournalIssueSearchResultListElementComponent', () => { { provide: TranslateService, useValue: {}}, { provide: ResourcePolicyDataService, useValue: {}}, { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: {}}, + { provide: EPersonDataService, useValue: epersonService}, { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } @@ -248,7 +259,7 @@ describe('JournalIssueSearchResultListElementComponent', () => { {provide: TranslateService, useValue: {}}, {provide: ResourcePolicyDataService, useValue: {}}, {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: {}}, + {provide: EPersonDataService, useValue: epersonService}, {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts index 03c0151693..67665403d1 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts @@ -13,7 +13,7 @@ import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; -import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { PageInfo } from '../../../../../core/shared/page-info.model'; import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; import { GroupMock } from '../../../../../shared/testing/group-mock'; @@ -24,6 +24,8 @@ import { EPersonDataService } from '../../../../../core/eperson/eperson-data.ser import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service'; import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; +import { EPerson } from '../../../../../core/eperson/models/eperson.model'; +import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let journalVolumeListElementComponent: JournalVolumeSearchResultListElementComponent; let fixture: ComponentFixture; @@ -36,6 +38,15 @@ const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { return of(EPersonMock); } }); + +const user = Object.assign(new EPerson(), { + id: 'userId', + groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), + _links: { self: { href: 'test.com/uuid/1234567654321' } } +}); +const epersonService = jasmine.createSpyObj('epersonService', { + findById: createSuccessfulRemoteDataObject$(user), +}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), { @@ -148,7 +159,7 @@ describe('JournalVolumeSearchResultListElementComponent', () => { { provide: TranslateService, useValue: {}}, { provide: ResourcePolicyDataService, useValue: {}}, { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: {}}, + { provide: EPersonDataService, useValue: epersonService}, { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } @@ -245,7 +256,7 @@ describe('JournalVolumeSearchResultListElementComponent', () => { {provide: TranslateService, useValue: {}}, {provide: ResourcePolicyDataService, useValue: {}}, {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: {}}, + {provide: EPersonDataService, useValue: epersonService}, {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts index bc1a560613..63e8abda28 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts @@ -13,7 +13,7 @@ import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; -import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { PageInfo } from '../../../../../core/shared/page-info.model'; import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; import { GroupMock } from '../../../../../shared/testing/group-mock'; @@ -24,6 +24,8 @@ import { AuthorizationDataService } from '../../../../../core/data/feature-autho import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; +import { EPerson } from '../../../../../core/eperson/models/eperson.model'; +import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let journalListElementComponent: JournalSearchResultListElementComponent; let fixture: ComponentFixture; @@ -36,6 +38,14 @@ const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { return of(EPersonMock); } }); +const user = Object.assign(new EPerson(), { + id: 'userId', + groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), + _links: { self: { href: 'test.com/uuid/1234567654321' } } +}); +const epersonService = jasmine.createSpyObj('epersonService', { + findById: createSuccessfulRemoteDataObject$(user), +}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), @@ -145,7 +155,7 @@ describe('JournalSearchResultListElementComponent', () => { { provide: TranslateService, useValue: {}}, { provide: ResourcePolicyDataService, useValue: {}}, { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: {}}, + { provide: EPersonDataService, useValue: epersonService}, { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } @@ -218,7 +228,7 @@ describe('JournalSearchResultListElementComponent', () => { {provide: TranslateService, useValue: {}}, {provide: ResourcePolicyDataService, useValue: {}}, {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: {}}, + {provide: EPersonDataService, useValue: epersonService}, {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts index 8be5f7496b..102393ed8f 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts @@ -14,7 +14,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../../shared/mocks/translate-loader.mock'; import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; -import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { PageInfo } from '../../../../../core/shared/page-info.model'; import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; import { GroupMock } from '../../../../../shared/testing/group-mock'; @@ -25,6 +25,8 @@ import { EPersonDataService } from '../../../../../core/eperson/eperson-data.ser import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; +import { EPerson } from '../../../../../core/eperson/models/eperson.model'; +import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let orgUnitListElementComponent: OrgUnitSearchResultListElementComponent; let fixture: ComponentFixture; @@ -37,6 +39,14 @@ const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { return of(EPersonMock); } }); +const user = Object.assign(new EPerson(), { + id: 'userId', + groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), + _links: { self: { href: 'test.com/uuid/1234567654321' } } +}); +const epersonService = jasmine.createSpyObj('epersonService', { + findById: createSuccessfulRemoteDataObject$(user), +}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), @@ -150,7 +160,7 @@ describe('OrgUnitSearchResultListElementComponent', () => { { provide: NotificationsService, useValue: {}}, { provide: ResourcePolicyDataService, useValue: {}}, { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: {}}, + { provide: EPersonDataService, useValue: epersonService}, { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } @@ -229,7 +239,7 @@ describe('OrgUnitSearchResultListElementComponent', () => { {provide: NotificationsService, useValue: {}}, {provide: ResourcePolicyDataService, useValue: {}}, {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: {}}, + {provide: EPersonDataService, useValue: epersonService}, {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts index e8cb217cdf..25b3a37c83 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts @@ -14,7 +14,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../../shared/mocks/translate-loader.mock'; import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; -import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { hot } from 'jasmine-marbles'; import { PageInfo } from '../../../../../core/shared/page-info.model'; import { GroupMock } from '../../../../../shared/testing/group-mock'; @@ -25,6 +25,8 @@ import { ResourcePolicyDataService } from '../../../../../core/resource-policy/r import { AuthService } from '../../../../../core/auth/auth.service'; import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service'; import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; +import { EPerson } from '../../../../../core/eperson/models/eperson.model'; +import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let personListElementComponent: PersonSearchResultListElementComponent; let fixture: ComponentFixture; @@ -40,6 +42,14 @@ const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { return of(EPersonMock); } }); +const user = Object.assign(new EPerson(), { + id: 'userId', + groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), + _links: { self: { href: 'test.com/uuid/1234567654321' } } +}); +const epersonService = jasmine.createSpyObj('epersonService', { + findById: createSuccessfulRemoteDataObject$(user), +}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), @@ -149,7 +159,7 @@ describe('PersonSearchResultListElementComponent', () => { { provide: NotificationsService, useValue: {} }, { provide: ResourcePolicyDataService, useValue: {}}, { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: {}}, + { provide: EPersonDataService, useValue: epersonService}, { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } @@ -228,7 +238,7 @@ describe('PersonSearchResultListElementComponent', () => { {provide: NotificationsService, useValue: {}}, {provide: ResourcePolicyDataService, useValue: {}}, {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: {}}, + {provide: EPersonDataService, useValue: epersonService}, {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts index 6af4f9c43b..60487af8c8 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts @@ -13,7 +13,7 @@ import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; -import { createSuccessfulRemoteDataObject } from '../../../../../shared/remote-data.utils'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; import { PageInfo } from '../../../../../core/shared/page-info.model'; import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; import { GroupMock } from '../../../../../shared/testing/group-mock'; @@ -24,6 +24,8 @@ import { EPersonDataService } from '../../../../../core/eperson/eperson-data.ser import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; +import { EPerson } from '../../../../../core/eperson/models/eperson.model'; +import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let projectListElementComponent: ProjectSearchResultListElementComponent; let fixture: ComponentFixture; @@ -37,6 +39,15 @@ const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { } }); +const user = Object.assign(new EPerson(), { + id: 'userId', + groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), + _links: { self: { href: 'test.com/uuid/1234567654321' } } +}); +const epersonService = jasmine.createSpyObj('epersonService', { + findById: createSuccessfulRemoteDataObject$(user), +}); + const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), { @@ -144,7 +155,7 @@ describe('ProjectSearchResultListElementComponent', () => { { provide: TranslateService, useValue: {}}, { provide: ResourcePolicyDataService, useValue: {}}, { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: {}}, + { provide: EPersonDataService, useValue: epersonService}, { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } @@ -217,7 +228,7 @@ describe('ProjectSearchResultListElementComponent', () => { {provide: TranslateService, useValue: {}}, {provide: ResourcePolicyDataService, useValue: {}}, {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: {}}, + {provide: EPersonDataService, useValue: epersonService}, {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } From e2087ad9525dc92f76b715e25472a052d75c90ec Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 13 Jan 2023 14:35:40 +0100 Subject: [PATCH 052/173] Fix merge issues --- src/app/browse-by/browse-by.module.ts | 4 +- .../collection-page/collection-page.module.ts | 2 + .../community-page/community-page.module.ts | 2 + .../journal-entities.module.ts | 4 +- .../research-entities.module.ts | 4 +- src/app/item-page/item-page.module.ts | 3 +- src/app/item-page/item-shared.module.ts | 7 +-- .../dso-edit-menu.component.spec.ts | 19 +++---- src/app/shared/dso-page/dso-page.module.ts | 55 +++++++++++++++++++ .../dso-versioning-modal.service.ts | 9 +-- src/app/shared/menu/menu.module.ts | 2 +- src/app/shared/shared.module.ts | 23 ++------ src/themes/custom/eager-theme.module.ts | 2 + src/themes/custom/lazy-theme.module.ts | 2 + src/themes/dspace/eager-theme.module.ts | 5 +- 15 files changed, 96 insertions(+), 47 deletions(-) diff --git a/src/app/browse-by/browse-by.module.ts b/src/app/browse-by/browse-by.module.ts index 14e21f8b4c..07165b1a28 100644 --- a/src/app/browse-by/browse-by.module.ts +++ b/src/app/browse-by/browse-by.module.ts @@ -10,6 +10,7 @@ import { ComcolModule } from '../shared/comcol/comcol.module'; import { ThemedBrowseByMetadataPageComponent } from './browse-by-metadata-page/themed-browse-by-metadata-page.component'; import { ThemedBrowseByDatePageComponent } from './browse-by-date-page/themed-browse-by-date-page.component'; import { ThemedBrowseByTitlePageComponent } from './browse-by-title-page/themed-browse-by-title-page.component'; +import { DsoPageModule } from '../shared/dso-page/dso-page.module'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -27,7 +28,8 @@ const ENTRY_COMPONENTS = [ imports: [ CommonModule, ComcolModule, - SharedModule + SharedModule, + DsoPageModule ], declarations: [ BrowseBySwitcherComponent, diff --git a/src/app/collection-page/collection-page.module.ts b/src/app/collection-page/collection-page.module.ts index ff49b983ff..e032694e0e 100644 --- a/src/app/collection-page/collection-page.module.ts +++ b/src/app/collection-page/collection-page.module.ts @@ -16,6 +16,7 @@ import { StatisticsModule } from '../statistics/statistics.module'; import { CollectionFormModule } from './collection-form/collection-form.module'; import { ThemedCollectionPageComponent } from './themed-collection-page.component'; import { ComcolModule } from '../shared/comcol/comcol.module'; +import { DsoPageModule } from '../shared/dso-page/dso-page.module'; @NgModule({ imports: [ @@ -26,6 +27,7 @@ import { ComcolModule } from '../shared/comcol/comcol.module'; EditItemPageModule, CollectionFormModule, ComcolModule, + DsoPageModule, ], declarations: [ CollectionPageComponent, diff --git a/src/app/community-page/community-page.module.ts b/src/app/community-page/community-page.module.ts index 1dd9e82499..45ffb2a786 100644 --- a/src/app/community-page/community-page.module.ts +++ b/src/app/community-page/community-page.module.ts @@ -19,6 +19,7 @@ import { import { ThemedCollectionPageSubCollectionListComponent } from './sub-collection-list/themed-community-page-sub-collection-list.component'; +import { DsoPageModule } from '../shared/dso-page/dso-page.module'; const DECLARATIONS = [CommunityPageComponent, ThemedCommunityPageComponent, @@ -37,6 +38,7 @@ const DECLARATIONS = [CommunityPageComponent, StatisticsModule.forRoot(), CommunityFormModule, ComcolModule, + DsoPageModule, ], declarations: [ ...DECLARATIONS diff --git a/src/app/entity-groups/journal-entities/journal-entities.module.ts b/src/app/entity-groups/journal-entities/journal-entities.module.ts index 3bf861e10d..9c214468d5 100644 --- a/src/app/entity-groups/journal-entities/journal-entities.module.ts +++ b/src/app/entity-groups/journal-entities/journal-entities.module.ts @@ -20,6 +20,7 @@ import { JournalVolumeSidebarSearchListElementComponent } from './item-list-elem import { JournalIssueSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal-issue/journal-issue-sidebar-search-list-element.component'; import { JournalSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/journal/journal-sidebar-search-list-element.component'; import { ItemSharedModule } from '../../item-page/item-shared.module'; +import { DsoPageModule } from '../../shared/dso-page/dso-page.module'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -47,7 +48,8 @@ const ENTRY_COMPONENTS = [ imports: [ CommonModule, ItemSharedModule, - SharedModule + SharedModule, + DsoPageModule ], declarations: [ ...ENTRY_COMPONENTS diff --git a/src/app/entity-groups/research-entities/research-entities.module.ts b/src/app/entity-groups/research-entities/research-entities.module.ts index 721a22be08..d115f61784 100644 --- a/src/app/entity-groups/research-entities/research-entities.module.ts +++ b/src/app/entity-groups/research-entities/research-entities.module.ts @@ -29,6 +29,7 @@ import { OrgUnitSidebarSearchListElementComponent } from './item-list-elements/s import { PersonSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/person/person-sidebar-search-list-element.component'; import { ProjectSidebarSearchListElementComponent } from './item-list-elements/sidebar-search-list-elements/project/project-sidebar-search-list-element.component'; import { ItemSharedModule } from '../../item-page/item-shared.module'; +import { DsoPageModule } from '../../shared/dso-page/dso-page.module'; const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator @@ -69,7 +70,8 @@ const COMPONENTS = [ CommonModule, ItemSharedModule, SharedModule, - NgbTooltipModule + NgbTooltipModule, + DsoPageModule ], declarations: [ ...COMPONENTS, diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index 5842e9e17e..bbe43ec590 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -51,6 +51,7 @@ import { ItemVersionsModule } from './versions/item-versions.module'; import { BitstreamRequestACopyPageComponent } from './bitstreams/request-a-copy/bitstream-request-a-copy-page.component'; import { FileSectionComponent } from './simple/field-components/file-section/file-section.component'; import { ItemSharedModule } from './item-shared.module'; +import { DsoPageModule } from '../shared/dso-page/dso-page.module'; const ENTRY_COMPONENTS = [ @@ -89,7 +90,6 @@ const DECLARATIONS = [ OrcidSyncSettingsComponent, OrcidQueueComponent, ItemAlertsComponent, - VersionedItemComponent, BitstreamRequestACopyPageComponent, ]; @@ -107,6 +107,7 @@ const DECLARATIONS = [ NgxGalleryModule, NgbAccordionModule, UploadModule, + DsoPageModule, ], declarations: [ ...DECLARATIONS, diff --git a/src/app/item-page/item-shared.module.ts b/src/app/item-page/item-shared.module.ts index c558b11692..0249e3cf22 100644 --- a/src/app/item-page/item-shared.module.ts +++ b/src/app/item-page/item-shared.module.ts @@ -10,16 +10,14 @@ import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/ import { ItemVersionsDeleteModalComponent } from './versions/item-versions-delete-modal/item-versions-delete-modal.component'; import { ItemVersionsSummaryModalComponent } from './versions/item-versions-summary-modal/item-versions-summary-modal.component'; import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component'; -import { DsoPageVersionButtonComponent } from '../shared/dso-page/dso-page-version-button/dso-page-version-button.component'; -import { PersonPageClaimButtonComponent } from '../shared/dso-page/person-page-claim-button/person-page-claim-button.component'; import { GenericItemPageFieldComponent } from './simple/field-components/specific-field/generic/generic-item-page-field.component'; import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component'; import { RelatedItemsComponent } from './simple/related-items/related-items-component'; -import { DsoPageOrcidButtonComponent } from '../shared/dso-page/dso-page-orcid-button/dso-page-orcid-button.component'; const ENTRY_COMPONENTS = [ ItemVersionsDeleteModalComponent, ItemVersionsSummaryModalComponent, + ]; const COMPONENTS = [ @@ -27,12 +25,9 @@ const COMPONENTS = [ RelatedEntitiesSearchComponent, TabbedRelatedEntitiesSearchComponent, MetadataValuesComponent, - DsoPageVersionButtonComponent, - PersonPageClaimButtonComponent, GenericItemPageFieldComponent, MetadataRepresentationListComponent, RelatedItemsComponent, - DsoPageOrcidButtonComponent ]; @NgModule({ diff --git a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu.component.spec.ts b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu.component.spec.ts index 1502407081..5616e8ea10 100644 --- a/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu.component.spec.ts +++ b/src/app/shared/dso-page/dso-edit-menu/dso-edit-menu.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; -import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; +import { Injector, NO_ERRORS_SCHEMA } from '@angular/core'; import { of as observableOf } from 'rxjs'; import { RouterTestingModule } from '@angular/router/testing'; import { ActivatedRoute } from '@angular/router'; @@ -10,13 +10,13 @@ import { AuthorizationDataService } from '../../../core/data/feature-authorizati import { AuthService } from '../../../core/auth/auth.service'; import { AuthServiceStub } from '../../testing/auth-service.stub'; import { MenuService } from '../../menu/menu.service'; -import { - ExpandableNavbarSectionComponent -} from '../../../navbar/expandable-navbar-section/expandable-navbar-section.component'; import { MenuItemModel } from '../../menu/menu-item/models/menu-item.model'; import { ThemeService } from '../../theme-support/theme.service'; import { getMockThemeService } from '../../mocks/theme-service.mock'; + +import { DsoPageModule } from '../dso-page.module'; + describe('DsoEditMenuComponent', () => { let comp: DsoEditMenuComponent; let fixture: ComponentFixture; @@ -46,7 +46,7 @@ describe('DsoEditMenuComponent', () => { }); spyOn(menuService, 'getMenuTopSections').and.returnValue(observableOf([section])); TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), RouterTestingModule], + imports: [TranslateModule.forRoot(), RouterTestingModule, DsoPageModule], declarations: [DsoEditMenuComponent], providers: [ Injector, @@ -54,15 +54,10 @@ describe('DsoEditMenuComponent', () => { {provide: AuthService, useClass: AuthServiceStub}, {provide: ActivatedRoute, useValue: routeStub}, {provide: AuthorizationDataService, useValue: authorizationService}, - { provide: ThemeService, useValue: getMockThemeService() }, + {provide: ThemeService, useValue: getMockThemeService()}, ], schemas: [NO_ERRORS_SCHEMA] - }).overrideComponent(ExpandableNavbarSectionComponent, { - set: { - changeDetection: ChangeDetectionStrategy.Default, - } - }) - .compileComponents(); + }).compileComponents(); })); beforeEach(() => { diff --git a/src/app/shared/dso-page/dso-page.module.ts b/src/app/shared/dso-page/dso-page.module.ts index e69de29bb2..6820e8eb53 100644 --- a/src/app/shared/dso-page/dso-page.module.ts +++ b/src/app/shared/dso-page/dso-page.module.ts @@ -0,0 +1,55 @@ +import { NgModule } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { RouterModule } from '@angular/router'; +import { CommonModule } from '@angular/common'; +import { DsoEditMenuComponent } from '../dso-page/dso-edit-menu/dso-edit-menu.component'; +import { + DsoEditMenuSectionComponent +} from '../dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component'; +import { + DsoEditMenuExpandableSectionComponent +} from '../dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component'; +import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'; + +const COMPONENTS = [ + DsoEditMenuComponent, + DsoEditMenuSectionComponent, + DsoEditMenuExpandableSectionComponent, +]; + +const ENTRY_COMPONENTS = [ +]; + +const MODULES = [ + TranslateModule, + RouterModule, + CommonModule, + NgbTooltipModule, +]; +const PROVIDERS = [ + +]; + +@NgModule({ + imports: [ + ...MODULES + ], + declarations: [ + ...COMPONENTS, + ...ENTRY_COMPONENTS, + ], + providers: [ + ...PROVIDERS, + ...ENTRY_COMPONENTS, + ], + exports: [ + ...COMPONENTS + ] +}) + +/** + * This module handles all components, providers and modules that are needed for the menu + */ +export class DsoPageModule { + +} diff --git a/src/app/shared/dso-page/dso-versioning-modal-service/dso-versioning-modal.service.ts b/src/app/shared/dso-page/dso-versioning-modal-service/dso-versioning-modal.service.ts index 708b0431e7..46792294dd 100644 --- a/src/app/shared/dso-page/dso-versioning-modal-service/dso-versioning-modal.service.ts +++ b/src/app/shared/dso-page/dso-versioning-modal-service/dso-versioning-modal.service.ts @@ -1,6 +1,3 @@ -import { - ItemVersionsSummaryModalComponent -} from '../../item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component'; import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators'; import { RemoteData } from '../../../core/data/remote-data'; import { Version } from '../../../core/shared/version.model'; @@ -10,12 +7,15 @@ import { WorkspaceItem } from '../../../core/submission/models/workspaceitem.mod import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { VersionDataService } from '../../../core/data/version-data.service'; import { VersionHistoryDataService } from '../../../core/data/version-history-data.service'; -import { ItemVersionsSharedService } from '../../item/item-versions/item-versions-shared.service'; import { Router } from '@angular/router'; import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service'; import { ItemDataService } from '../../../core/data/item-data.service'; import { Injectable } from '@angular/core'; import { Observable, of } from 'rxjs'; +import { ItemVersionsSharedService } from '../../../item-page/versions/item-versions-shared.service'; +import { + ItemVersionsSummaryModalComponent +} from '../../../item-page/versions/item-versions-summary-modal/item-versions-summary-modal.component'; /** * Service to take care of all the functionality related to the version creation modal @@ -25,6 +25,7 @@ import { Observable, of } from 'rxjs'; }) export class DsoVersioningModalService { + constructor( protected modalService: NgbModal, protected versionService: VersionDataService, diff --git a/src/app/shared/menu/menu.module.ts b/src/app/shared/menu/menu.module.ts index c007af517d..28bdab9987 100644 --- a/src/app/shared/menu/menu.module.ts +++ b/src/app/shared/menu/menu.module.ts @@ -24,7 +24,7 @@ const ENTRY_COMPONENTS = [ const MODULES = [ TranslateModule, RouterModule, - CommonModule + CommonModule, ]; const PROVIDERS = [ diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 095af56c56..445ee82c83 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -101,19 +101,19 @@ import { CreateCommunityParentSelectorComponent } from './dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component'; import { - ThemedCreateCommunityParentSelectorComponent + ThemedCreateCommunityParentSelectorComponent } from './dso-selector/modal-wrappers/create-community-parent-selector/themed-create-community-parent-selector.component'; import { CreateItemParentSelectorComponent } from './dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component'; import { - ThemedCreateItemParentSelectorComponent + ThemedCreateItemParentSelectorComponent } from './dso-selector/modal-wrappers/create-item-parent-selector/themed-create-item-parent-selector.component'; import { CreateCollectionParentSelectorComponent } from './dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component'; import { - ThemedCreateCollectionParentSelectorComponent + ThemedCreateCollectionParentSelectorComponent } from './dso-selector/modal-wrappers/create-collection-parent-selector/themed-create-collection-parent-selector.component'; import { CommunitySearchResultListElementComponent @@ -125,19 +125,19 @@ import { EditItemSelectorComponent } from './dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; import { - ThemedEditItemSelectorComponent + ThemedEditItemSelectorComponent } from './dso-selector/modal-wrappers/edit-item-selector/themed-edit-item-selector.component'; import { EditCommunitySelectorComponent } from './dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component'; import { - ThemedEditCommunitySelectorComponent + ThemedEditCommunitySelectorComponent } from './dso-selector/modal-wrappers/edit-community-selector/themed-edit-community-selector.component'; import { EditCollectionSelectorComponent } from './dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component'; import { - ThemedEditCollectionSelectorComponent + ThemedEditCollectionSelectorComponent } from './dso-selector/modal-wrappers/edit-collection-selector/themed-edit-collection-selector.component'; import { RoleDirective } from './roles/role.directive'; import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component'; @@ -238,13 +238,6 @@ import { ItemPageTitleFieldComponent } from '../item-page/simple/field-components/specific-field/title/item-page-title-field.component'; import { MarkdownPipe } from './utils/markdown.pipe'; -import { - DsoEditMenuSectionComponent -} from './dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component'; -import { DsoEditMenuComponent } from './dso-page/dso-edit-menu/dso-edit-menu.component'; -import { - DsoEditMenuExpandableSectionComponent -} from './dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component'; import { GoogleRecaptchaModule } from '../core/google-recaptcha/google-recaptcha.module'; import { MenuModule } from './menu/menu.module'; import { @@ -351,7 +344,6 @@ const COMPONENTS = [ ItemPageTitleFieldComponent, ThemedSearchNavbarComponent, ListableNotificationObjectComponent, - DsoPageEditButtonComponent, MetadataFieldWrapperComponent, ]; @@ -409,9 +401,6 @@ const ENTRY_COMPONENTS = [ CommunitySidebarSearchListElementComponent, ScopeSelectorModalComponent, ListableNotificationObjectComponent, - ExternalLinkMenuItemComponent, - DsoEditMenuSectionComponent, - DsoEditMenuExpandableSectionComponent, ]; const PROVIDERS = [ diff --git a/src/themes/custom/eager-theme.module.ts b/src/themes/custom/eager-theme.module.ts index 50b56252d3..ed0f1a9285 100644 --- a/src/themes/custom/eager-theme.module.ts +++ b/src/themes/custom/eager-theme.module.ts @@ -42,6 +42,7 @@ import { import { CommunityListElementComponent } from './app/shared/object-list/community-list-element/community-list-element.component'; import { CollectionListElementComponent } from './app/shared/object-list/collection-list-element/collection-list-element.component'; import { CollectionDropdownComponent } from './app/shared/collection-dropdown/collection-dropdown.component'; +import { DsoPageModule } from '../../app/shared/dso-page/dso-page.module'; /** @@ -84,6 +85,7 @@ const DECLARATIONS = [ NavbarModule, ItemPageModule, ItemSharedModule, + DsoPageModule, ], declarations: DECLARATIONS, providers: [ diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index 871764bc66..701c1e656f 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -119,6 +119,7 @@ import { } from './app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component'; import { ItemVersionsModule } from '../../app/item-page/versions/item-versions.module'; import { ItemSharedModule } from '../../app/item-page/item-shared.module'; +import { DsoPageModule } from '../../app/shared/dso-page/dso-page.module'; const DECLARATIONS = [ FileSectionComponent, @@ -206,6 +207,7 @@ const DECLARATIONS = [ InfoModule, JournalEntitiesModule, MenuModule, + DsoPageModule, MyDspaceSearchModule, NavbarModule, NgbModule, diff --git a/src/themes/dspace/eager-theme.module.ts b/src/themes/dspace/eager-theme.module.ts index 584c83a617..015283a0d6 100644 --- a/src/themes/dspace/eager-theme.module.ts +++ b/src/themes/dspace/eager-theme.module.ts @@ -12,8 +12,7 @@ import { NavbarModule } from '../../app/navbar/navbar.module'; * Add components that use a custom decorator to ENTRY_COMPONENTS as well as DECLARATIONS. * This will ensure that decorator gets picked up when the app loads */ -const ENTRY_COMPONENTS = [ -]; +const ENTRY_COMPONENTS = []; const DECLARATIONS = [ ...ENTRY_COMPONENTS, @@ -32,7 +31,7 @@ const DECLARATIONS = [ ], declarations: DECLARATIONS, providers: [ - ...ENTRY_COMPONENTS.map((component) => ({ provide: component })) + ...ENTRY_COMPONENTS.map((component) => ({provide: component})) ], }) /** From 28351f2ac7c8df83cecab87fdd2a834574beded5 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 16 Jan 2023 09:55:56 +0100 Subject: [PATCH 053/173] [CST-7757] Fix merge with main --- src/app/shared/shared.module.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 5162270b14..9a6c855208 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -244,6 +244,9 @@ import { import { ThemedCollectionDropdownComponent } from './collection-dropdown/themed-collection-dropdown.component'; import { MetadataFieldWrapperComponent } from './metadata-field-wrapper/metadata-field-wrapper.component'; import { LogInExternalProviderComponent } from './log-in/methods/log-in-external-provider/log-in-external-provider.component'; +import { + DsoPageSubscriptionButtonComponent +} from './dso-page/dso-page-subscription-button/dso-page-subscription-button.component'; const MODULES = [ CommonModule, @@ -344,6 +347,7 @@ const COMPONENTS = [ ThemedSearchNavbarComponent, ListableNotificationObjectComponent, DsoPageEditButtonComponent, + DsoPageSubscriptionButtonComponent, MetadataFieldWrapperComponent, ]; From 6523b029134db91a449d17085dcb58b79e335f21 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 16 Jan 2023 13:07:19 +0100 Subject: [PATCH 054/173] DS-8408: Edit item is ordered by relevance --- .../edit-item-selector.component.html | 11 +++++++++++ .../edit-item-selector.component.ts | 2 +- .../edit-item-selector.component.html | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html new file mode 100644 index 0000000000..85d8797e66 --- /dev/null +++ b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html @@ -0,0 +1,11 @@ +
+ + +
diff --git a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts index 4822849e4c..c1ae583908 100644 --- a/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.ts @@ -14,7 +14,7 @@ import { Item } from '../../../../core/shared/item.model'; @Component({ selector: 'ds-edit-item-selector', - templateUrl: '../dso-selector-modal-wrapper.component.html', + templateUrl: 'edit-item-selector.component.html', }) export class EditItemSelectorComponent extends DSOSelectorModalWrapperComponent implements OnInit { objectType = DSpaceObjectType.ITEM; diff --git a/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html b/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html index 54044f5d79..85d8797e66 100644 --- a/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html +++ b/src/themes/custom/app/shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component.html @@ -6,6 +6,6 @@ From fe61bb7b6b42ddab2b22f1341491e4158cd5e0c3 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 17 Jan 2023 19:00:00 +0100 Subject: [PATCH 055/173] 98535: Cleaned up code & added documentation --- src/app/core/core.module.ts | 12 ++++---- .../models/advanced-workflow-info.model.ts | 11 +++++++ .../advanced-workflow-info.resource-type.ts | 17 +++++++++++ .../rating-advanced-workflow-info.model.ts | 28 +++++++++++++++++ ...ing-reviewer-action-advanced-info.model.ts | 27 ----------------- .../reviewer-action-advanced-info.model.ts | 16 ---------- ...ewer-action-advanced-info.resource-type.ts | 25 ---------------- ...ect-reviewer-action-advanced-info.model.ts | 18 ----------- ...t-reviewer-advanced-workflow-info.model.ts | 19 ++++++++++++ .../models/workflow-action-object.model.ts | 4 +-- ...claimed-task-actions-abstract.component.ts | 11 +++++-- ...ed-claimed-task-action-rating.component.ts | 9 ++++-- ...d-task-action-select-reviewer.component.ts | 9 ++++-- ...advanced-workflow-action-page.component.ts | 4 +++ ...vanced-workflow-action-rating.component.ts | 20 ++++++++----- ...rkflow-action-select-reviewer.component.ts | 20 +++++++++---- .../reviewers-list.component.ts | 30 +++++++++++++++++-- ...advanced-workflow-action.component.spec.ts | 3 +- .../advanced-workflow-action.component.ts | 8 ++++- ...ced-workflow-actions-loader.component.html | 2 +- ...anced-workflow-actions-loader.component.ts | 11 ++++--- ...=> advanced-workflow-actions.directive.ts} | 4 +-- .../workflowitems-edit-page.module.ts | 6 ++-- 23 files changed, 184 insertions(+), 130 deletions(-) create mode 100644 src/app/core/tasks/models/advanced-workflow-info.model.ts create mode 100644 src/app/core/tasks/models/advanced-workflow-info.resource-type.ts create mode 100644 src/app/core/tasks/models/rating-advanced-workflow-info.model.ts delete mode 100644 src/app/core/tasks/models/rating-reviewer-action-advanced-info.model.ts delete mode 100644 src/app/core/tasks/models/reviewer-action-advanced-info.model.ts delete mode 100644 src/app/core/tasks/models/reviewer-action-advanced-info.resource-type.ts delete mode 100644 src/app/core/tasks/models/select-reviewer-action-advanced-info.model.ts create mode 100644 src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts rename src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/{advanced-claimed-task-actions.directive.ts => advanced-workflow-actions.directive.ts} (73%) diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index df71f740da..254f6d2478 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -162,9 +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 { RatingReviewerActionAdvancedInfo } from './tasks/models/rating-reviewer-action-advanced-info.model'; -import { ReviewerActionAdvancedInfo } from './tasks/models/reviewer-action-advanced-info.model'; -import { SelectReviewerActionAdvancedInfo } from './tasks/models/select-reviewer-action-advanced-info.model'; +import { RatingAdvancedWorkflowInfo } from './tasks/models/rating-advanced-workflow-info.model'; +import { AdvancedWorkflowInfo } from './tasks/models/advanced-workflow-info.model'; +import { SelectReviewerAdvancedWorkflowInfo } from './tasks/models/select-reviewer-advanced-workflow-info.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -335,9 +335,9 @@ export const models = Version, VersionHistory, WorkflowAction, - ReviewerActionAdvancedInfo, - RatingReviewerActionAdvancedInfo, - SelectReviewerActionAdvancedInfo, + AdvancedWorkflowInfo, + RatingAdvancedWorkflowInfo, + SelectReviewerAdvancedWorkflowInfo, TemplateItem, Feature, Authorization, diff --git a/src/app/core/tasks/models/advanced-workflow-info.model.ts b/src/app/core/tasks/models/advanced-workflow-info.model.ts new file mode 100644 index 0000000000..87991a375c --- /dev/null +++ b/src/app/core/tasks/models/advanced-workflow-info.model.ts @@ -0,0 +1,11 @@ +import { autoserialize } from 'cerialize'; + +/** + * An abstract model class for a {@link AdvancedWorkflowInfo} + */ +export abstract class AdvancedWorkflowInfo { + + @autoserialize + id: string; + +} diff --git a/src/app/core/tasks/models/advanced-workflow-info.resource-type.ts b/src/app/core/tasks/models/advanced-workflow-info.resource-type.ts new file mode 100644 index 0000000000..4e7793f875 --- /dev/null +++ b/src/app/core/tasks/models/advanced-workflow-info.resource-type.ts @@ -0,0 +1,17 @@ +import { ResourceType } from '../../shared/resource-type'; + +/** + * The resource type for {@link RatingAdvancedWorkflowInfo} + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const RATING_ADVANCED_WORKFLOW_INFO = new ResourceType('ratingrevieweraction'); + +/** + * The resource type for {@link SelectReviewerAdvancedWorkflowInfo} + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const SELECT_REVIEWER_ADVANCED_WORKFLOW_INFO = new ResourceType('selectrevieweraction'); diff --git a/src/app/core/tasks/models/rating-advanced-workflow-info.model.ts b/src/app/core/tasks/models/rating-advanced-workflow-info.model.ts new file mode 100644 index 0000000000..b7861d4fe4 --- /dev/null +++ b/src/app/core/tasks/models/rating-advanced-workflow-info.model.ts @@ -0,0 +1,28 @@ +import { typedObject } from '../../cache/builders/build-decorators'; +import { inheritSerialization, autoserialize } from 'cerialize'; +import { RATING_ADVANCED_WORKFLOW_INFO } from './advanced-workflow-info.resource-type'; +import { AdvancedWorkflowInfo } from './advanced-workflow-info.model'; +import { ResourceType } from '../../shared/resource-type'; + +/** + * A model class for a {@link RatingAdvancedWorkflowInfo} + */ +@typedObject +@inheritSerialization(AdvancedWorkflowInfo) +export class RatingAdvancedWorkflowInfo extends AdvancedWorkflowInfo { + + static type: ResourceType = RATING_ADVANCED_WORKFLOW_INFO; + + /** + * Whether the description is required. + */ + @autoserialize + descriptionRequired: boolean; + + /** + * The maximum value. + */ + @autoserialize + maxValue: number; + +} diff --git a/src/app/core/tasks/models/rating-reviewer-action-advanced-info.model.ts b/src/app/core/tasks/models/rating-reviewer-action-advanced-info.model.ts deleted file mode 100644 index 2759d3edf2..0000000000 --- a/src/app/core/tasks/models/rating-reviewer-action-advanced-info.model.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { typedObject } from '../../cache/builders/build-decorators'; -import { inheritSerialization, autoserialize } from 'cerialize'; -import { RATING_REVIEWER_ACTION_ADVANCED_INFO } from './reviewer-action-advanced-info.resource-type'; -import { ReviewerActionAdvancedInfo } from './reviewer-action-advanced-info.model'; - -/** - * A model class for a {@link RatingReviewerActionAdvancedInfo} - */ -@typedObject -@inheritSerialization(ReviewerActionAdvancedInfo) -export class RatingReviewerActionAdvancedInfo extends ReviewerActionAdvancedInfo { - - static type = RATING_REVIEWER_ACTION_ADVANCED_INFO; - - /** - * Whether the description is required. - */ - @autoserialize - descriptionRequired: boolean; - - /** - * The maximum value. - */ - @autoserialize - maxValue: number; - -} diff --git a/src/app/core/tasks/models/reviewer-action-advanced-info.model.ts b/src/app/core/tasks/models/reviewer-action-advanced-info.model.ts deleted file mode 100644 index dc423eec51..0000000000 --- a/src/app/core/tasks/models/reviewer-action-advanced-info.model.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { typedObject } from '../../cache/builders/build-decorators'; -import { autoserialize } from 'cerialize'; -import { REVIEWER_ACTION_ADVANCED_INFO } from './reviewer-action-advanced-info.resource-type'; - -/** - * A model class for a {@link ReviewerActionAdvancedInfo} - */ -@typedObject -export class ReviewerActionAdvancedInfo { - - static type = REVIEWER_ACTION_ADVANCED_INFO; - - @autoserialize - id: string; - -} diff --git a/src/app/core/tasks/models/reviewer-action-advanced-info.resource-type.ts b/src/app/core/tasks/models/reviewer-action-advanced-info.resource-type.ts deleted file mode 100644 index 876f37495e..0000000000 --- a/src/app/core/tasks/models/reviewer-action-advanced-info.resource-type.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ResourceType } from '../../shared/resource-type'; - -/** - * The resource type for {@link ReviewerActionAdvancedInfo} - * - * Needs to be in a separate file to prevent circular - * dependencies in webpack. - */ -export const REVIEWER_ACTION_ADVANCED_INFO = new ResourceType('revieweraction'); - -/** - * The resource type for {@link RatingReviewerActionAdvancedInfo} - * - * Needs to be in a separate file to prevent circular - * dependencies in webpack. - */ -export const RATING_REVIEWER_ACTION_ADVANCED_INFO = new ResourceType('ratingrevieweraction'); - -/** - * The resource type for {@link SelectReviewerActionAdvancedInfo} - * - * Needs to be in a separate file to prevent circular - * dependencies in webpack. - */ -export const SELECT_REVIEWER_ACTION_ADVANCED_INFO = new ResourceType('selectrevieweraction'); diff --git a/src/app/core/tasks/models/select-reviewer-action-advanced-info.model.ts b/src/app/core/tasks/models/select-reviewer-action-advanced-info.model.ts deleted file mode 100644 index cd8812f6af..0000000000 --- a/src/app/core/tasks/models/select-reviewer-action-advanced-info.model.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { typedObject } from '../../cache/builders/build-decorators'; -import { inheritSerialization, autoserialize } from 'cerialize'; -import { SELECT_REVIEWER_ACTION_ADVANCED_INFO } from './reviewer-action-advanced-info.resource-type'; -import { ReviewerActionAdvancedInfo } from './reviewer-action-advanced-info.model'; - -/** - * A model class for a {@link SelectReviewerActionAdvancedInfo} - */ -@typedObject -@inheritSerialization(ReviewerActionAdvancedInfo) -export class SelectReviewerActionAdvancedInfo extends ReviewerActionAdvancedInfo { - - static type = SELECT_REVIEWER_ACTION_ADVANCED_INFO; - - @autoserialize - group: string; - -} diff --git a/src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts b/src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts new file mode 100644 index 0000000000..b87770596e --- /dev/null +++ b/src/app/core/tasks/models/select-reviewer-advanced-workflow-info.model.ts @@ -0,0 +1,19 @@ +import { typedObject } from '../../cache/builders/build-decorators'; +import { inheritSerialization, autoserialize } from 'cerialize'; +import { SELECT_REVIEWER_ADVANCED_WORKFLOW_INFO } from './advanced-workflow-info.resource-type'; +import { AdvancedWorkflowInfo } from './advanced-workflow-info.model'; +import { ResourceType } from '../../shared/resource-type'; + +/** + * A model class for a {@link SelectReviewerAdvancedWorkflowInfo} + */ +@typedObject +@inheritSerialization(AdvancedWorkflowInfo) +export class SelectReviewerAdvancedWorkflowInfo extends AdvancedWorkflowInfo { + + static type: ResourceType = SELECT_REVIEWER_ADVANCED_WORKFLOW_INFO; + + @autoserialize + group: string; + +} diff --git a/src/app/core/tasks/models/workflow-action-object.model.ts b/src/app/core/tasks/models/workflow-action-object.model.ts index 6fa103bbea..0896e6b8f8 100644 --- a/src/app/core/tasks/models/workflow-action-object.model.ts +++ b/src/app/core/tasks/models/workflow-action-object.model.ts @@ -2,7 +2,7 @@ import { inheritSerialization, autoserialize } from 'cerialize'; import { typedObject } from '../../cache/builders/build-decorators'; import { DSpaceObject } from '../../shared/dspace-object.model'; import { WORKFLOW_ACTION } from './workflow-action-object.resource-type'; -import { ReviewerActionAdvancedInfo } from './reviewer-action-advanced-info.model'; +import { AdvancedWorkflowInfo } from './advanced-workflow-info.model'; /** * A model class for a WorkflowAction @@ -40,6 +40,6 @@ export class WorkflowAction extends DSpaceObject { * The advanced info required by the advanced options */ @autoserialize - advancedInfo: ReviewerActionAdvancedInfo[]; + advancedInfo: AdvancedWorkflowInfo[]; } diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts index 3774b60f1a..d3f2ce4b6e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts @@ -7,9 +7,8 @@ import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/wo /** * Abstract component for rendering an advanced claimed task's action * To create a child-component for a new option: - * - Set the "option" of the component + * - Set the "option" and "workflowType" of the component * - Add a @rendersWorkflowTaskOption annotation to your component providing the same enum value - * - Optionally overwrite createBody if the request body requires more than just the option */ @Component({ selector: 'ds-advanced-claimed-task-action-abstract', @@ -17,7 +16,10 @@ import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/wo }) export abstract class AdvancedClaimedTaskActionsAbstractComponent extends ClaimedTaskActionsAbstractComponent implements OnInit { - workflowType: string; + /** + * The {@link WorkflowAction} id of the advanced workflow that needs to be opened. + */ + abstract workflowType: string; /** * Route to the workflow's task page @@ -40,6 +42,9 @@ export abstract class AdvancedClaimedTaskActionsAbstractComponent extends Claime })); } + /** + * Navigates to the advanced workflow page based on the {@link workflow}. + */ openAdvancedClaimedTaskTab(): void { void this.router.navigate([this.workflowTaskPageRoute], { queryParams: { diff --git a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts index 2c2983c25c..8699dc702e 100644 --- a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts @@ -9,11 +9,14 @@ import { } from '../abstract/advanced-claimed-task-actions-abstract.component'; import { ADVANCED_WORKFLOW_ACTION_RATING, - WORKFLOW_ADVANCED_TASK_OPTION_RATING, + ADVANCED_WORKFLOW_TASK_OPTION_RATING, } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component'; import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-decorator'; -@rendersWorkflowTaskOption(WORKFLOW_ADVANCED_TASK_OPTION_RATING) +/** + * Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionRatingComponent} + */ +@rendersWorkflowTaskOption(ADVANCED_WORKFLOW_TASK_OPTION_RATING) @Component({ selector: 'ds-advanced-claimed-task-action-rating-reviewer', templateUrl: './advanced-claimed-task-action-rating.component.html', @@ -24,7 +27,7 @@ export class AdvancedClaimedTaskActionRatingComponent extends AdvancedClaimedTas /** * This component represents the advanced select option */ - option = WORKFLOW_ADVANCED_TASK_OPTION_RATING; + option = ADVANCED_WORKFLOW_TASK_OPTION_RATING; workflowType = ADVANCED_WORKFLOW_ACTION_RATING; diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts index c7a2a2e545..7473c737d9 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts @@ -10,10 +10,13 @@ import { SearchService } from '../../../../core/shared/search/search.service'; import { RequestService } from '../../../../core/data/request.service'; import { ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, - WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER + ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; -@rendersWorkflowTaskOption(WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER) +/** + * Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionSelectReviewerComponent} + */ +@rendersWorkflowTaskOption(ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER) @Component({ selector: 'ds-advanced-claimed-task-action-select-reviewer', templateUrl: './advanced-claimed-task-action-select-reviewer.component.html', @@ -24,7 +27,7 @@ export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedCl /** * This component represents the advanced select option */ - option = WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER; + option = ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER; workflowType = ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER; diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.ts index e6c7da7d59..91dce19a5e 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.ts @@ -1,6 +1,10 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +/** + * The Advanced Workflow page containing the correct {@link AdvancedWorkflowActionComponent} + * based on the route parameters. + */ @Component({ selector: 'ds-advanced-workflow-action-page', templateUrl: './advanced-workflow-action-page.component.html', diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts index eb06e49243..f98e83f8be 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.ts @@ -6,12 +6,15 @@ import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/adv import { FormGroup, FormControl } from '@angular/forms'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { - RatingReviewerActionAdvancedInfo -} from '../../../core/tasks/models/rating-reviewer-action-advanced-info.model'; + RatingAdvancedWorkflowInfo +} from '../../../core/tasks/models/rating-advanced-workflow-info.model'; -export const WORKFLOW_ADVANCED_TASK_OPTION_RATING = 'submit_score'; +export const ADVANCED_WORKFLOW_TASK_OPTION_RATING = 'submit_score'; export const ADVANCED_WORKFLOW_ACTION_RATING = 'scorereviewaction'; +/** + * The page on which reviewers can rate submitted items. + */ @rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_RATING) @Component({ selector: 'ds-advanced-workflow-action-rating-reviewer', @@ -23,7 +26,7 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio ratingForm: FormGroup; - ngOnInit() { + ngOnInit(): void { super.ngOnInit(); this.ratingForm = new FormGroup({ review: new FormControl(''), @@ -43,9 +46,12 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio } } + /** + * Returns the task option, the score and the review if one was provided + */ createBody(): any { const body = { - [WORKFLOW_ADVANCED_TASK_OPTION_RATING]: true, + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, score: this.ratingForm.get('rating').value, }; if (this.ratingForm.get('review').value !== '') { @@ -59,8 +65,8 @@ export class AdvancedWorkflowActionRatingComponent extends AdvancedWorkflowActio return ADVANCED_WORKFLOW_ACTION_RATING; } - getAdvancedInfo(workflowAction: WorkflowAction | null): RatingReviewerActionAdvancedInfo | null { - return workflowAction ? (workflowAction.advancedInfo[0] as RatingReviewerActionAdvancedInfo) : null; + getAdvancedInfo(workflowAction: WorkflowAction | null): RatingAdvancedWorkflowInfo | null { + return workflowAction ? (workflowAction.advancedInfo[0] as RatingAdvancedWorkflowInfo) : null; } } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index f766c97f39..3d07acdebe 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -5,17 +5,20 @@ import { import { AdvancedWorkflowActionComponent } from '../advanced-workflow-action/advanced-workflow-action.component'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { - SelectReviewerActionAdvancedInfo -} from '../../../core/tasks/models/select-reviewer-action-advanced-info.model'; + SelectReviewerAdvancedWorkflowInfo +} from '../../../core/tasks/models/select-reviewer-advanced-workflow-info.model'; import { EPersonListActionConfig } from '../../../access-control/group-registry/group-form/members-list/members-list.component'; import { Subscription } from 'rxjs'; import { EPerson } from '../../../core/eperson/models/eperson.model'; -export const WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer'; +export const ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer'; export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction'; +/** + * The page on which Review Managers can assign Reviewers to review an item. + */ @rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER) @Component({ selector: 'ds-advanced-workflow-action-select-reviewer', @@ -75,7 +78,7 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf } this.subs.push(this.workflowAction$.subscribe((workflowAction: WorkflowAction) => { if (workflowAction) { - this.groupId = (workflowAction.advancedInfo as SelectReviewerActionAdvancedInfo[])[0].group; + this.groupId = (workflowAction.advancedInfo as SelectReviewerAdvancedWorkflowInfo[])[0].group; } else { this.groupId = null; } @@ -86,18 +89,23 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf return ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER; } + /** + * Only performs the action when some reviewers have been selected. + */ performAction(): void { if (this.selectedReviewers.length > 0) { super.performAction(); } else { this.displayError = true; } - console.log(this.displayError); } + /** + * Returns the task option and the selected {@link EPerson} id(s) + */ createBody(): any { return { - [WORKFLOW_ADVANCED_TASK_OPTION_SELECT_REVIEWER]: true, + [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true, eperson: this.selectedReviewers.map((ePerson: EPerson) => ePerson.id), }; } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts index 0cd485e638..7112a30543 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.ts @@ -14,7 +14,8 @@ import { Observable, of as observableOf } from 'rxjs'; import { hasValue } from '../../../../shared/empty.util'; import { PaginatedList } from '../../../../core/data/paginated-list.model'; import { - MembersListComponent, EPersonListActionConfig + MembersListComponent, + EPersonListActionConfig, } from '../../../../access-control/group-registry/group-form/members-list/members-list.component'; /** @@ -26,6 +27,9 @@ enum SubKey { SearchResultsDTO, } +/** + * A custom {@link MembersListComponent} for the advanced SelectReviewer workflow. + */ @Component({ selector: 'ds-reviewers-list', // templateUrl: './reviewers-list.component.html', @@ -83,6 +87,12 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn } } + /** + * Sets the list of currently selected members, when no group is defined the list of {@link selectedReviewers} + * will be set. + * + * @param page The number of the page to retrieve + */ retrieveMembers(page: number): void { this.config.currentPage = page; if (this.groupId === null) { @@ -95,19 +105,35 @@ export class ReviewersListComponent extends MembersListComponent implements OnIn } } + /** + * Checks whether the given {@link possibleMember} is part of the {@link selectedReviewers}. + * + * @param possibleMember The {@link EPerson} that needs to be checked + */ isMemberOfGroup(possibleMember: EPerson): Observable { return observableOf(hasValue(this.selectedReviewers.find((reviewer: EpersonDtoModel) => reviewer.eperson.id === possibleMember.id))); } + /** + * Removes the {@link ePerson} from the {@link selectedReviewers} + * + * @param ePerson The {@link EpersonDtoModel} containg the {@link EPerson} to remove + */ deleteMemberFromGroup(ePerson: EpersonDtoModel) { ePerson.memberOfGroup = false; const index = this.selectedReviewers.indexOf(ePerson); if (index !== -1) { this.selectedReviewers.splice(index, 1); } - this.selectedReviewersUpdated.emit(this.selectedReviewers.map((epersonDtoModel: EpersonDtoModel) => epersonDtoModel.eperson)); + this.selectedReviewersUpdated.emit(this.selectedReviewers.map((ePersonDtoModel: EpersonDtoModel) => ePersonDtoModel.eperson)); } + /** + * Adds the {@link ePerson} to the {@link selectedReviewers} (or replaces it when {@link multipleReviewers} is + * `false`). Afterwards it will emit the list. + * + * @param ePerson The {@link EPerson} to add to the list + */ addMemberToGroup(ePerson: EpersonDtoModel) { ePerson.memberOfGroup = true; if (!this.multipleReviewers) { diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts index b836a8704a..13799fee4c 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts @@ -108,7 +108,8 @@ describe('AdvancedWorkflowActionComponent', () => { }); @Component({ - selector: 'ds-test-cmp', + // tslint:disable-next-line:component-selector + selector: '', template: '' }) class TestComponent extends AdvancedWorkflowActionComponent { diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts index 256a4d8915..982e5f8eac 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts @@ -13,6 +13,12 @@ import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.se import { map } from 'rxjs/operators'; import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; +/** + * Abstract component for rendering an advanced claimed task's workflow page + * To create a child-component for a new option: + * - Set the "getType()" of the component + * - Implement the createBody, should always contain at least the ADVANCED_WORKFLOW_TASK_OPTION + */ @Component({ selector: 'ds-advanced-workflow-action', template: '', @@ -62,7 +68,7 @@ export abstract class AdvancedWorkflowActionComponent extends WorkflowItemAction /** * Submits the task with the given {@link createBody}. * - * @param id + * @param id The task id */ sendRequest(id: string): Observable { return this.claimedTaskDataService.submitTask(id, this.createBody()).pipe( diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html index 2374ed7913..0904d0fcde 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html @@ -1 +1 @@ - + diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts index ac6d2f171d..32f14c015d 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts @@ -3,10 +3,13 @@ import { hasValue } from '../../../shared/empty.util'; import { getAdvancedComponentByWorkflowTaskOption } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; -import { AdvancedClaimedTaskActionsDirective } from './advanced-claimed-task-actions.directive'; +import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive'; import { Router } from '@angular/router'; import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; +/** + * Component for loading a {@link AdvancedWorkflowActionComponent} depending on the "{@link type}" input + */ @Component({ selector: 'ds-advanced-workflow-actions-loader', templateUrl: './advanced-workflow-actions-loader.component.html', @@ -23,7 +26,7 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { /** * Directive to determine where the dynamic child component is located */ - @ViewChild(AdvancedClaimedTaskActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedClaimedTaskActionsDirective; + @ViewChild(AdvancedWorkflowActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedWorkflowActionsDirective; constructor( private componentFactoryResolver: ComponentFactoryResolver, @@ -47,8 +50,8 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { } } - getComponentByWorkflowTaskOption(option: string) { - return getAdvancedComponentByWorkflowTaskOption(option); + getComponentByWorkflowTaskOption(type: string): any { + return getAdvancedComponentByWorkflowTaskOption(type); } } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts similarity index 73% rename from src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive.ts rename to src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts index b2ed49b502..e569f6cc6f 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts @@ -1,12 +1,12 @@ import { Directive, ViewContainerRef } from '@angular/core'; @Directive({ - selector: '[dsAdvancedClaimedTaskActions]', + selector: '[dsAdvancedWorkflowActions]', }) /** * Directive used as a hook to know where to inject the dynamic Advanced Claimed Task Actions component */ -export class AdvancedClaimedTaskActionsDirective { +export class AdvancedWorkflowActionsDirective { constructor( public viewContainerRef: ViewContainerRef, diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts index dc60bd7d2b..aaf75fbb9f 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts @@ -24,8 +24,8 @@ import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component'; import { - AdvancedClaimedTaskActionsDirective -} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-claimed-task-actions.directive'; + AdvancedWorkflowActionsDirective +} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive'; import { AccessControlModule } from '../access-control/access-control.module'; import { ReviewersListComponent @@ -54,7 +54,7 @@ import { RatingModule } from 'ngx-bootstrap/rating'; AdvancedWorkflowActionRatingComponent, AdvancedWorkflowActionSelectReviewerComponent, AdvancedWorkflowActionPageComponent, - AdvancedClaimedTaskActionsDirective, + AdvancedWorkflowActionsDirective, ReviewersListComponent, ] }) From 572f5ac4c577057d6df03de894e5d1489b43514a Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 18 Jan 2023 13:45:04 +0100 Subject: [PATCH 056/173] 98535: Added missing tests to the new advanced workflow components --- ...ced-workflow-action-page.component.spec.ts | 6 +- ...d-workflow-action-rating.component.spec.ts | 116 +++++++++++++++++- ...w-action-select-reviewer.component.spec.ts | 67 +++++++++- .../reviewers-list.component.spec.ts | 80 +++++++++--- ...advanced-workflow-action.component.spec.ts | 6 +- ...-workflow-actions-loader.component.spec.ts | 53 +++++++- 6 files changed, 288 insertions(+), 40 deletions(-) diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts index f2511f0490..cbb85b6ad8 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action-page.component'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; describe('AdvancedWorkflowActionPageComponent', () => { @@ -20,9 +20,9 @@ describe('AdvancedWorkflowActionPageComponent', () => { provide: ActivatedRoute, useValue: { snapshot: { - queryParams: convertToParamMap({ + queryParams: { workflow: 'testaction', - }), + }, }, }, }, diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts index a75d4e30a2..4abbe1a0c1 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AdvancedWorkflowActionRatingComponent } from './advanced-workflow-action-rating.component'; -import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'; +import { + AdvancedWorkflowActionRatingComponent, + ADVANCED_WORKFLOW_TASK_OPTION_RATING +} from './advanced-workflow-action-rating.component'; +import { ActivatedRoute, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -17,10 +20,19 @@ import { TranslateModule } from '@ngx-translate/core'; import { VarDirective } from '../../../shared/utils/var.directive'; import { RatingModule } from 'ngx-bootstrap/rating'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; +import { createSuccessfulRemoteDataObject$, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { Item } from '../../../core/shared/item.model'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; +import { RatingAdvancedWorkflowInfo } from '../../../core/tasks/models/rating-advanced-workflow-info.model'; +const claimedTaskId = '2'; const workflowId = '1'; describe('AdvancedWorkflowActionRatingComponent', () => { + const workflowItem: WorkflowItem = new WorkflowItem(); + workflowItem.item = createSuccessfulRemoteDataObject$(new Item()); let component: AdvancedWorkflowActionRatingComponent; let fixture: ComponentFixture; @@ -52,11 +64,13 @@ describe('AdvancedWorkflowActionRatingComponent', () => { useValue: { data: observableOf({ id: workflowId, + wfi: createSuccessfulRemoteDataObject(workflowItem), }), snapshot: { - queryParams: convertToParamMap({ + queryParams: { + claimedTask: claimedTaskId, workflow: 'testaction', - }), + }, }, }, }, @@ -67,6 +81,7 @@ describe('AdvancedWorkflowActionRatingComponent', () => { { provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService }, ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); @@ -80,7 +95,96 @@ describe('AdvancedWorkflowActionRatingComponent', () => { fixture.debugElement.nativeElement.remove(); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('performAction', () => { + let ratingAdvancedWorkflowInfo: RatingAdvancedWorkflowInfo; + beforeEach(() => { + ratingAdvancedWorkflowInfo = new RatingAdvancedWorkflowInfo(); + ratingAdvancedWorkflowInfo.maxValue = 5; + spyOn(component, 'getAdvancedInfo').and.returnValue(ratingAdvancedWorkflowInfo); + spyOn(component, 'previousPage'); + // The form validators are set in the HTML code so the getAdvancedInfo needs to return a value + fixture.detectChanges(); + }); + + describe('with required review', () => { + beforeEach(() => { + ratingAdvancedWorkflowInfo.descriptionRequired = true; + fixture.detectChanges(); + }); + + it('should call the claimedTaskDataService with the rating and the required description when it has been rated and return to the mydspace page', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: 'Good job!', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, + review: 'Good job!', + score: 4, + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + + it('should not call the claimedTaskDataService when the required description is empty', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: '', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).not.toHaveBeenCalled(); + expect(notificationService.success).not.toHaveBeenCalled(); + expect(component.previousPage).not.toHaveBeenCalled(); + }); + }); + + describe('with an optional review', () => { + beforeEach(() => { + ratingAdvancedWorkflowInfo.descriptionRequired = false; + fixture.detectChanges(); + }); + + it('should call the claimedTaskDataService with the optional review when provided and return to the mydspace page', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: 'Good job!', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, + review: 'Good job!', + score: 4, + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + + it('should call the claimedTaskDataService when the optional description is empty and return to the mydspace page', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: '', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, + score: 4, + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + }); }); }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts index a3ede08b1d..855bde79a7 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AdvancedWorkflowActionSelectReviewerComponent } from './advanced-workflow-action-select-reviewer.component'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { + AdvancedWorkflowActionSelectReviewerComponent, + ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER +} from './advanced-workflow-action-select-reviewer.component'; +import { ActivatedRoute } from '@angular/router'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub'; import { RouterTestingModule } from '@angular/router/testing'; @@ -14,10 +17,19 @@ import { TranslateModule } from '@ngx-translate/core'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub'; import { of as observableOf } from 'rxjs'; +import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; +import { createSuccessfulRemoteDataObject$, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { Item } from '../../../core/shared/item.model'; +import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock'; +import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +const claimedTaskId = '2'; const workflowId = '1'; describe('AdvancedWorkflowActionSelectReviewerComponent', () => { + const workflowItem: WorkflowItem = new WorkflowItem(); + workflowItem.item = createSuccessfulRemoteDataObject$(new Item()); let component: AdvancedWorkflowActionSelectReviewerComponent; let fixture: ComponentFixture; @@ -46,11 +58,13 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { useValue: { data: observableOf({ id: workflowId, + wfi: createSuccessfulRemoteDataObject(workflowItem), }), snapshot: { - queryParams: convertToParamMap({ + queryParams: { + claimedTask: claimedTaskId, workflow: 'testaction', - }), + }, }, }, }, @@ -60,6 +74,7 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { { provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService }, ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); @@ -67,9 +82,49 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { fixture = TestBed.createComponent(AdvancedWorkflowActionSelectReviewerComponent); component = fixture.componentInstance; fixture.detectChanges(); + spyOn(component, 'previousPage'); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + describe('performAction', () => { + it('should call the claimedTaskDataService with the list of selected ePersons', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.selectedReviewers = [EPersonMock, EPersonMock2]; + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true, + eperson: [EPersonMock.id, EPersonMock2.id], + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + + it('should not call the claimedTaskDataService with the list of selected ePersons when it\'s empty', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.selectedReviewers = []; + + component.performAction(); + + expect(claimedTaskDataService.submitTask).not.toHaveBeenCalled(); + }); + + it('should not call the return to mydspace page when the request failed', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(false))); + component.selectedReviewers = [EPersonMock, EPersonMock2]; + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true, + eperson: [EPersonMock.id, EPersonMock2.id], + }); + expect(notificationService.error).toHaveBeenCalled(); + expect(component.previousPage).not.toHaveBeenCalled(); + }); }); }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts index bf27b1e79f..24513cd22f 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts @@ -1,6 +1,6 @@ import { CommonModule } from '@angular/common'; -import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; -import { ComponentFixture, fakeAsync, flush, inject, TestBed, waitForAsync } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA, SimpleChange, DebugElement } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, waitForAsync } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule, By } from '@angular/platform-browser'; import { Router } from '@angular/router'; @@ -31,6 +31,7 @@ import { NotificationsServiceStub } from '../../../../shared/testing/notificatio import { RouterMock } from '../../../../shared/mocks/router.mock'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; +import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model'; describe('ReviewersListComponent', () => { let component: ReviewersListComponent; @@ -45,6 +46,8 @@ describe('ReviewersListComponent', () => { let epersonMembers; let subgroupMembers; let paginationService; + let ePersonDtoModel1: EpersonDtoModel; + let ePersonDtoModel2: EpersonDtoModel; beforeEach(waitForAsync(() => { activeGroup = GroupMock; @@ -56,7 +59,7 @@ describe('ReviewersListComponent', () => { activeGroup: activeGroup, epersonMembers: epersonMembers, subgroupMembers: subgroupMembers, - findAllByHref(href: string): Observable>> { + findAllByHref(_href: string): Observable>> { return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupsDataServiceStub.getEPersonMembers())); }, searchByScope(scope: string, query: string): Observable>> { @@ -119,7 +122,6 @@ describe('ReviewersListComponent', () => { findById(id: string) { for (const group of allGroups) { if (group.id === id) { - console.log('found', group); return createSuccessfulRemoteDataObject$(group); } } @@ -167,9 +169,12 @@ describe('ReviewersListComponent', () => { fixture.debugElement.nativeElement.remove(); })); - it('should create ReviewersListComponent', inject([ReviewersListComponent], (comp: ReviewersListComponent) => { - expect(comp).toBeDefined(); - })); + beforeEach(() => { + ePersonDtoModel1 = new EpersonDtoModel(); + ePersonDtoModel1.eperson = EPersonMock; + ePersonDtoModel2 = new EpersonDtoModel(); + ePersonDtoModel2.eperson = EPersonMock2; + }); describe('when no group is selected', () => { beforeEach(() => { @@ -179,18 +184,18 @@ describe('ReviewersListComponent', () => { fixture.detectChanges(); }); - it('should show no epersons because no group is selected', () => { - const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); - expect(epersonIdsFound.length).toEqual(0); - epersonMembers.map((eperson: EPerson) => { - expect(epersonIdsFound.find((foundEl) => { - return (foundEl.nativeElement.textContent.trim() === eperson.uuid); + it('should show no ePersons because no group is selected', () => { + const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); + expect(ePersonIdsFound.length).toEqual(0); + epersonMembers.map((ePerson: EPerson) => { + expect(ePersonIdsFound.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === ePerson.uuid); })).not.toBeTruthy(); }); }); }); - describe('when group is selected', () => { + describe('when a group is selected', () => { beforeEach(() => { component.ngOnChanges({ groupId: new SimpleChange(undefined, GroupMock.id, true) @@ -198,15 +203,50 @@ describe('ReviewersListComponent', () => { fixture.detectChanges(); }); - it('should show all eperson members of group', () => { - const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); - expect(epersonIdsFound.length).toEqual(1); - epersonMembers.map((eperson: EPerson) => { - expect(epersonIdsFound.find((foundEl) => { - return (foundEl.nativeElement.textContent.trim() === eperson.uuid); + it('should show all ePerson members of group', () => { + const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); + expect(ePersonIdsFound.length).toEqual(1); + epersonMembers.map((ePerson: EPerson) => { + expect(ePersonIdsFound.find((foundEl: DebugElement) => { + return (foundEl.nativeElement.textContent.trim() === ePerson.uuid); })).toBeTruthy(); }); }); }); + + it('should replace the value when a new member is added when multipleReviewers is false', () => { + spyOn(component.selectedReviewersUpdated, 'emit'); + component.multipleReviewers = false; + component.selectedReviewers = [ePersonDtoModel1]; + + component.addMemberToGroup(ePersonDtoModel2); + + expect(component.selectedReviewers).toEqual([ePersonDtoModel2]); + expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel2.eperson]); + }); + + it('should add the value when a new member is added when multipleReviewers is true', () => { + spyOn(component.selectedReviewersUpdated, 'emit'); + component.multipleReviewers = true; + component.selectedReviewers = [ePersonDtoModel1]; + + component.addMemberToGroup(ePersonDtoModel2); + + expect(component.selectedReviewers).toEqual([ePersonDtoModel1, ePersonDtoModel2]); + expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel1.eperson, ePersonDtoModel2.eperson]); + }); + + it('should delete the member when present', () => { + spyOn(component.selectedReviewersUpdated, 'emit'); + ePersonDtoModel1.memberOfGroup = true; + component.selectedReviewers = [ePersonDtoModel1]; + + component.deleteMemberFromGroup(ePersonDtoModel1); + + expect(component.selectedReviewers).toEqual([]); + expect(ePersonDtoModel1.memberOfGroup).toBeFalse(); + expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([]); + }); + }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts index 13799fee4c..f0c6882617 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts @@ -5,7 +5,7 @@ import { MockComponent } from 'ng-mocks'; import { DSOSelectorComponent } from '../../../shared/dso-selector/dso-selector/dso-selector.component'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { RouterTestingModule } from '@angular/router/testing'; @@ -54,9 +54,9 @@ describe('AdvancedWorkflowActionComponent', () => { id: workflowId, }), snapshot: { - queryParams: convertToParamMap({ + queryParams: { workflow: 'testaction', - }), + }, }, }, }, diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts index 321a6b954d..660e228c70 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts @@ -2,6 +2,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedWorkflowActionsLoaderComponent } from './advanced-workflow-actions-loader.component'; import { Router } from '@angular/router'; import { RouterStub } from '../../../shared/testing/router.stub'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive'; +import { + rendersAdvancedWorkflowTaskOption +} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; +import { By } from '@angular/platform-browser'; +import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; + +const ADVANCED_WORKFLOW_ACTION_TEST = 'testaction'; describe('AdvancedWorkflowActionsLoaderComponent', () => { let component: AdvancedWorkflowActionsLoaderComponent; @@ -14,21 +23,61 @@ describe('AdvancedWorkflowActionsLoaderComponent', () => { await TestBed.configureTestingModule({ declarations: [ + AdvancedWorkflowActionsDirective, AdvancedWorkflowActionsLoaderComponent, ], providers: [ { provide: Router, useValue: router }, ], + }).overrideComponent(AdvancedWorkflowActionsLoaderComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + entryComponents: [AdvancedWorkflowActionTestComponent], + }, }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(AdvancedWorkflowActionsLoaderComponent); component = fixture.componentInstance; + component.type = ADVANCED_WORKFLOW_ACTION_TEST; fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + describe('When the component is rendered', () => { + it('should display the AdvancedWorkflowActionTestComponent when the type has been defined in a rendersAdvancedWorkflowTaskOption', () => { + spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(AdvancedWorkflowActionTestComponent); + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(ADVANCED_WORKFLOW_ACTION_TEST); + expect(fixture.debugElement.query(By.css('#AdvancedWorkflowActionsLoaderComponent'))).not.toBeNull(); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should redirect to page not found when the type has not been defined in a rendersAdvancedWorkflowTaskOption', () => { + spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(undefined); + component.type = 'nonexistingaction'; + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith('nonexistingaction'); + expect(router.navigate).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]); + }); }); }); + +@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_TEST) +@Component({ + // tslint:disable-next-line:component-selector + selector: '', + template: '', +}) +class AdvancedWorkflowActionTestComponent { +} From 940c6281a2b5a284791792561ae3f095004ea901 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 18 Jan 2023 13:45:04 +0100 Subject: [PATCH 057/173] 98535: Added missing tests to the new advanced workflow components --- ...ced-workflow-action-page.component.spec.ts | 6 +- ...d-workflow-action-rating.component.spec.ts | 116 +++++++++++++++++- ...w-action-select-reviewer.component.spec.ts | 67 +++++++++- ...rkflow-action-select-reviewer.component.ts | 20 +++ .../reviewers-list.component.spec.ts | 80 +++++++++--- ...advanced-workflow-action.component.spec.ts | 6 +- ...-workflow-actions-loader.component.spec.ts | 53 +++++++- 7 files changed, 308 insertions(+), 40 deletions(-) diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts index f2511f0490..cbb85b6ad8 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action-page.component'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; describe('AdvancedWorkflowActionPageComponent', () => { @@ -20,9 +20,9 @@ describe('AdvancedWorkflowActionPageComponent', () => { provide: ActivatedRoute, useValue: { snapshot: { - queryParams: convertToParamMap({ + queryParams: { workflow: 'testaction', - }), + }, }, }, }, diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts index a75d4e30a2..4abbe1a0c1 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AdvancedWorkflowActionRatingComponent } from './advanced-workflow-action-rating.component'; -import { ActivatedRoute, convertToParamMap, Router } from '@angular/router'; +import { + AdvancedWorkflowActionRatingComponent, + ADVANCED_WORKFLOW_TASK_OPTION_RATING +} from './advanced-workflow-action-rating.component'; +import { ActivatedRoute, Router } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -17,10 +20,19 @@ import { TranslateModule } from '@ngx-translate/core'; import { VarDirective } from '../../../shared/utils/var.directive'; import { RatingModule } from 'ngx-bootstrap/rating'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; +import { createSuccessfulRemoteDataObject$, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { Item } from '../../../core/shared/item.model'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; +import { RatingAdvancedWorkflowInfo } from '../../../core/tasks/models/rating-advanced-workflow-info.model'; +const claimedTaskId = '2'; const workflowId = '1'; describe('AdvancedWorkflowActionRatingComponent', () => { + const workflowItem: WorkflowItem = new WorkflowItem(); + workflowItem.item = createSuccessfulRemoteDataObject$(new Item()); let component: AdvancedWorkflowActionRatingComponent; let fixture: ComponentFixture; @@ -52,11 +64,13 @@ describe('AdvancedWorkflowActionRatingComponent', () => { useValue: { data: observableOf({ id: workflowId, + wfi: createSuccessfulRemoteDataObject(workflowItem), }), snapshot: { - queryParams: convertToParamMap({ + queryParams: { + claimedTask: claimedTaskId, workflow: 'testaction', - }), + }, }, }, }, @@ -67,6 +81,7 @@ describe('AdvancedWorkflowActionRatingComponent', () => { { provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService }, ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); @@ -80,7 +95,96 @@ describe('AdvancedWorkflowActionRatingComponent', () => { fixture.debugElement.nativeElement.remove(); }); - it('should create', () => { - expect(component).toBeTruthy(); + describe('performAction', () => { + let ratingAdvancedWorkflowInfo: RatingAdvancedWorkflowInfo; + beforeEach(() => { + ratingAdvancedWorkflowInfo = new RatingAdvancedWorkflowInfo(); + ratingAdvancedWorkflowInfo.maxValue = 5; + spyOn(component, 'getAdvancedInfo').and.returnValue(ratingAdvancedWorkflowInfo); + spyOn(component, 'previousPage'); + // The form validators are set in the HTML code so the getAdvancedInfo needs to return a value + fixture.detectChanges(); + }); + + describe('with required review', () => { + beforeEach(() => { + ratingAdvancedWorkflowInfo.descriptionRequired = true; + fixture.detectChanges(); + }); + + it('should call the claimedTaskDataService with the rating and the required description when it has been rated and return to the mydspace page', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: 'Good job!', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, + review: 'Good job!', + score: 4, + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + + it('should not call the claimedTaskDataService when the required description is empty', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: '', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).not.toHaveBeenCalled(); + expect(notificationService.success).not.toHaveBeenCalled(); + expect(component.previousPage).not.toHaveBeenCalled(); + }); + }); + + describe('with an optional review', () => { + beforeEach(() => { + ratingAdvancedWorkflowInfo.descriptionRequired = false; + fixture.detectChanges(); + }); + + it('should call the claimedTaskDataService with the optional review when provided and return to the mydspace page', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: 'Good job!', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, + review: 'Good job!', + score: 4, + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + + it('should call the claimedTaskDataService when the optional description is empty and return to the mydspace page', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.ratingForm.setValue({ + review: '', + rating: 4, + }); + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_RATING]: true, + score: 4, + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + }); }); }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts index a3ede08b1d..855bde79a7 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts @@ -1,6 +1,9 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AdvancedWorkflowActionSelectReviewerComponent } from './advanced-workflow-action-select-reviewer.component'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { + AdvancedWorkflowActionSelectReviewerComponent, + ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER +} from './advanced-workflow-action-select-reviewer.component'; +import { ActivatedRoute } from '@angular/router'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub'; import { RouterTestingModule } from '@angular/router/testing'; @@ -14,10 +17,19 @@ import { TranslateModule } from '@ngx-translate/core'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub'; import { of as observableOf } from 'rxjs'; +import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; +import { createSuccessfulRemoteDataObject$, createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils'; +import { Item } from '../../../core/shared/item.model'; +import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock'; +import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +const claimedTaskId = '2'; const workflowId = '1'; describe('AdvancedWorkflowActionSelectReviewerComponent', () => { + const workflowItem: WorkflowItem = new WorkflowItem(); + workflowItem.item = createSuccessfulRemoteDataObject$(new Item()); let component: AdvancedWorkflowActionSelectReviewerComponent; let fixture: ComponentFixture; @@ -46,11 +58,13 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { useValue: { data: observableOf({ id: workflowId, + wfi: createSuccessfulRemoteDataObject(workflowItem), }), snapshot: { - queryParams: convertToParamMap({ + queryParams: { + claimedTask: claimedTaskId, workflow: 'testaction', - }), + }, }, }, }, @@ -60,6 +74,7 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { { provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService }, ], + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); }); @@ -67,9 +82,49 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { fixture = TestBed.createComponent(AdvancedWorkflowActionSelectReviewerComponent); component = fixture.componentInstance; fixture.detectChanges(); + spyOn(component, 'previousPage'); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + describe('performAction', () => { + it('should call the claimedTaskDataService with the list of selected ePersons', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.selectedReviewers = [EPersonMock, EPersonMock2]; + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true, + eperson: [EPersonMock.id, EPersonMock2.id], + }); + expect(notificationService.success).toHaveBeenCalled(); + expect(component.previousPage).toHaveBeenCalled(); + }); + + it('should not call the claimedTaskDataService with the list of selected ePersons when it\'s empty', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); + component.selectedReviewers = []; + + component.performAction(); + + expect(claimedTaskDataService.submitTask).not.toHaveBeenCalled(); + }); + + it('should not call the return to mydspace page when the request failed', () => { + spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(false))); + component.selectedReviewers = [EPersonMock, EPersonMock2]; + + component.performAction(); + + expect(claimedTaskDataService.submitTask).toHaveBeenCalledWith(claimedTaskId, { + [ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER]: true, + eperson: [EPersonMock.id, EPersonMock2.id], + }); + expect(notificationService.error).toHaveBeenCalled(); + expect(component.previousPage).not.toHaveBeenCalled(); + }); }); }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index 3d07acdebe..68e67ee95e 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -12,6 +12,13 @@ import { } from '../../../access-control/group-registry/group-form/members-list/members-list.component'; import { Subscription } from 'rxjs'; import { EPerson } from '../../../core/eperson/models/eperson.model'; +import { ActivatedRoute, Router } from '@angular/router'; +import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; +import { RouteService } from '../../../core/services/route.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; +import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; export const ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer'; export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction'; @@ -43,6 +50,19 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf displayError = false; + constructor( + protected route: ActivatedRoute, + protected workflowItemService: WorkflowItemDataService, + protected router: Router, + protected routeService: RouteService, + protected notificationsService: NotificationsService, + protected translationService: TranslateService, + protected workflowActionService: WorkflowActionDataService, + protected claimedTaskDataService: ClaimedTaskDataService, + ) { + super(route, workflowItemService, router, routeService, notificationsService, translationService, workflowActionService, claimedTaskDataService); + } + ngOnDestroy(): void { this.subs.forEach((subscription: Subscription) => subscription.unsubscribe()); } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts index bf27b1e79f..24513cd22f 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/reviewers-list/reviewers-list.component.spec.ts @@ -1,6 +1,6 @@ import { CommonModule } from '@angular/common'; -import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; -import { ComponentFixture, fakeAsync, flush, inject, TestBed, waitForAsync } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA, SimpleChange, DebugElement } from '@angular/core'; +import { ComponentFixture, fakeAsync, flush, TestBed, waitForAsync } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule, By } from '@angular/platform-browser'; import { Router } from '@angular/router'; @@ -31,6 +31,7 @@ import { NotificationsServiceStub } from '../../../../shared/testing/notificatio import { RouterMock } from '../../../../shared/mocks/router.mock'; import { PaginationService } from '../../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; +import { EpersonDtoModel } from '../../../../core/eperson/models/eperson-dto.model'; describe('ReviewersListComponent', () => { let component: ReviewersListComponent; @@ -45,6 +46,8 @@ describe('ReviewersListComponent', () => { let epersonMembers; let subgroupMembers; let paginationService; + let ePersonDtoModel1: EpersonDtoModel; + let ePersonDtoModel2: EpersonDtoModel; beforeEach(waitForAsync(() => { activeGroup = GroupMock; @@ -56,7 +59,7 @@ describe('ReviewersListComponent', () => { activeGroup: activeGroup, epersonMembers: epersonMembers, subgroupMembers: subgroupMembers, - findAllByHref(href: string): Observable>> { + findAllByHref(_href: string): Observable>> { return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), groupsDataServiceStub.getEPersonMembers())); }, searchByScope(scope: string, query: string): Observable>> { @@ -119,7 +122,6 @@ describe('ReviewersListComponent', () => { findById(id: string) { for (const group of allGroups) { if (group.id === id) { - console.log('found', group); return createSuccessfulRemoteDataObject$(group); } } @@ -167,9 +169,12 @@ describe('ReviewersListComponent', () => { fixture.debugElement.nativeElement.remove(); })); - it('should create ReviewersListComponent', inject([ReviewersListComponent], (comp: ReviewersListComponent) => { - expect(comp).toBeDefined(); - })); + beforeEach(() => { + ePersonDtoModel1 = new EpersonDtoModel(); + ePersonDtoModel1.eperson = EPersonMock; + ePersonDtoModel2 = new EpersonDtoModel(); + ePersonDtoModel2.eperson = EPersonMock2; + }); describe('when no group is selected', () => { beforeEach(() => { @@ -179,18 +184,18 @@ describe('ReviewersListComponent', () => { fixture.detectChanges(); }); - it('should show no epersons because no group is selected', () => { - const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); - expect(epersonIdsFound.length).toEqual(0); - epersonMembers.map((eperson: EPerson) => { - expect(epersonIdsFound.find((foundEl) => { - return (foundEl.nativeElement.textContent.trim() === eperson.uuid); + it('should show no ePersons because no group is selected', () => { + const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); + expect(ePersonIdsFound.length).toEqual(0); + epersonMembers.map((ePerson: EPerson) => { + expect(ePersonIdsFound.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === ePerson.uuid); })).not.toBeTruthy(); }); }); }); - describe('when group is selected', () => { + describe('when a group is selected', () => { beforeEach(() => { component.ngOnChanges({ groupId: new SimpleChange(undefined, GroupMock.id, true) @@ -198,15 +203,50 @@ describe('ReviewersListComponent', () => { fixture.detectChanges(); }); - it('should show all eperson members of group', () => { - const epersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); - expect(epersonIdsFound.length).toEqual(1); - epersonMembers.map((eperson: EPerson) => { - expect(epersonIdsFound.find((foundEl) => { - return (foundEl.nativeElement.textContent.trim() === eperson.uuid); + it('should show all ePerson members of group', () => { + const ePersonIdsFound = fixture.debugElement.queryAll(By.css('#ePeopleMembersOfGroup tr td:first-child')); + expect(ePersonIdsFound.length).toEqual(1); + epersonMembers.map((ePerson: EPerson) => { + expect(ePersonIdsFound.find((foundEl: DebugElement) => { + return (foundEl.nativeElement.textContent.trim() === ePerson.uuid); })).toBeTruthy(); }); }); }); + + it('should replace the value when a new member is added when multipleReviewers is false', () => { + spyOn(component.selectedReviewersUpdated, 'emit'); + component.multipleReviewers = false; + component.selectedReviewers = [ePersonDtoModel1]; + + component.addMemberToGroup(ePersonDtoModel2); + + expect(component.selectedReviewers).toEqual([ePersonDtoModel2]); + expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel2.eperson]); + }); + + it('should add the value when a new member is added when multipleReviewers is true', () => { + spyOn(component.selectedReviewersUpdated, 'emit'); + component.multipleReviewers = true; + component.selectedReviewers = [ePersonDtoModel1]; + + component.addMemberToGroup(ePersonDtoModel2); + + expect(component.selectedReviewers).toEqual([ePersonDtoModel1, ePersonDtoModel2]); + expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([ePersonDtoModel1.eperson, ePersonDtoModel2.eperson]); + }); + + it('should delete the member when present', () => { + spyOn(component.selectedReviewersUpdated, 'emit'); + ePersonDtoModel1.memberOfGroup = true; + component.selectedReviewers = [ePersonDtoModel1]; + + component.deleteMemberFromGroup(ePersonDtoModel1); + + expect(component.selectedReviewers).toEqual([]); + expect(ePersonDtoModel1.memberOfGroup).toBeFalse(); + expect(component.selectedReviewersUpdated.emit).toHaveBeenCalledWith([]); + }); + }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts index 13799fee4c..f0c6882617 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts @@ -5,7 +5,7 @@ import { MockComponent } from 'ng-mocks'; import { DSOSelectorComponent } from '../../../shared/dso-selector/dso-selector/dso-selector.component'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ClaimedTaskDataServiceStub } from '../../../shared/testing/claimed-task-data-service.stub'; -import { ActivatedRoute, convertToParamMap } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { of as observableOf } from 'rxjs'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { RouterTestingModule } from '@angular/router/testing'; @@ -54,9 +54,9 @@ describe('AdvancedWorkflowActionComponent', () => { id: workflowId, }), snapshot: { - queryParams: convertToParamMap({ + queryParams: { workflow: 'testaction', - }), + }, }, }, }, diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts index 321a6b954d..660e228c70 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts @@ -2,6 +2,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedWorkflowActionsLoaderComponent } from './advanced-workflow-actions-loader.component'; import { Router } from '@angular/router'; import { RouterStub } from '../../../shared/testing/router.stub'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive'; +import { + rendersAdvancedWorkflowTaskOption +} from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; +import { By } from '@angular/platform-browser'; +import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; + +const ADVANCED_WORKFLOW_ACTION_TEST = 'testaction'; describe('AdvancedWorkflowActionsLoaderComponent', () => { let component: AdvancedWorkflowActionsLoaderComponent; @@ -14,21 +23,61 @@ describe('AdvancedWorkflowActionsLoaderComponent', () => { await TestBed.configureTestingModule({ declarations: [ + AdvancedWorkflowActionsDirective, AdvancedWorkflowActionsLoaderComponent, ], providers: [ { provide: Router, useValue: router }, ], + }).overrideComponent(AdvancedWorkflowActionsLoaderComponent, { + set: { + changeDetection: ChangeDetectionStrategy.Default, + entryComponents: [AdvancedWorkflowActionTestComponent], + }, }).compileComponents(); }); beforeEach(() => { fixture = TestBed.createComponent(AdvancedWorkflowActionsLoaderComponent); component = fixture.componentInstance; + component.type = ADVANCED_WORKFLOW_ACTION_TEST; fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + afterEach(() => { + fixture.debugElement.nativeElement.remove(); + }); + + describe('When the component is rendered', () => { + it('should display the AdvancedWorkflowActionTestComponent when the type has been defined in a rendersAdvancedWorkflowTaskOption', () => { + spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(AdvancedWorkflowActionTestComponent); + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(ADVANCED_WORKFLOW_ACTION_TEST); + expect(fixture.debugElement.query(By.css('#AdvancedWorkflowActionsLoaderComponent'))).not.toBeNull(); + expect(router.navigate).not.toHaveBeenCalled(); + }); + + it('should redirect to page not found when the type has not been defined in a rendersAdvancedWorkflowTaskOption', () => { + spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(undefined); + component.type = 'nonexistingaction'; + + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith('nonexistingaction'); + expect(router.navigate).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]); + }); }); }); + +@rendersAdvancedWorkflowTaskOption(ADVANCED_WORKFLOW_ACTION_TEST) +@Component({ + // tslint:disable-next-line:component-selector + selector: '', + template: '', +}) +class AdvancedWorkflowActionTestComponent { +} From 3e8cb4f3d94f4e9a6cde2a224754a76081deb270 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 23 Jan 2023 16:48:47 +0100 Subject: [PATCH 058/173] 97425: Implement feedback --- .../system-wide-alert-form.component.html | 91 +++++++++++++------ .../system-wide-alert-form.component.scss | 1 + .../system-wide-alert-form.component.spec.ts | 61 +++++++++++++ .../system-wide-alert-form.component.ts | 69 +++++++++++++- src/assets/i18n/en.json5 | 10 +- 5 files changed, 198 insertions(+), 34 deletions(-) diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html index 2821a14bf8..062b06c294 100644 --- a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html @@ -17,50 +17,89 @@ [className]="(formMessage.invalid) && (formMessage.dirty || formMessage.touched) ? 'form-control is-invalid' :'form-control'" formControlName="formMessage"> -
+
{{ 'system-wide-alert.form.error.message' | translate }} -
+
-
- +
+ + {{ 'system-wide-alert.form.label.countdownTo.enable' | translate }}
-
-
-
- - +
+
+
+
+ + +
-
-
-
- +
+
+ +
-
-
-
- +
+
+ +
-
+
{{'system-wide-alert.form.label.countdownTo.hint' | translate}}
+ +
+
+
+ +
+
+
+ + + {{'system-wide-alert-banner.countdown.prefix' | translate }} + + + {{'system-wide-alert-banner.countdown.days' | translate: { + days: previewDays + } }} + + + {{'system-wide-alert-banner.countdown.hours' | translate: { + hours: previewHours + } }} + + + {{'system-wide-alert-banner.countdown.minutes' | translate: { + minutes: previewMinutes + } }} + + + {{formMessage.value}} +
+
+
-
+
From 715d3ae014a74f510be41a7eaedae1b9e986382f Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 24 Jan 2023 11:24:13 +0100 Subject: [PATCH 060/173] Fix issues with module changes --- .../alert-form/system-wide-alert-form.component.spec.ts | 4 ++-- src/app/system-wide-alert/system-wide-alert.module.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts index 608170a094..4fc79c1caa 100644 --- a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts @@ -12,8 +12,8 @@ import { RouterStub } from '../../shared/testing/router.stub'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; -import { SharedModule } from '../../shared/shared.module'; import { UiSwitchModule } from 'ngx-ui-switch'; +import { SystemWideAlertModule } from '../system-wide-alert.module'; describe('SystemWideAlertFormComponent', () => { let comp: SystemWideAlertFormComponent; @@ -52,7 +52,7 @@ describe('SystemWideAlertFormComponent', () => { router = new RouterStub(); TestBed.configureTestingModule({ - imports: [FormsModule, SharedModule, UiSwitchModule, TranslateModule.forRoot()], + imports: [FormsModule, SystemWideAlertModule, UiSwitchModule, TranslateModule.forRoot()], declarations: [SystemWideAlertFormComponent], providers: [ {provide: SystemWideAlertDataService, useValue: systemWideAlertDataService}, diff --git a/src/app/system-wide-alert/system-wide-alert.module.ts b/src/app/system-wide-alert/system-wide-alert.module.ts index ce2a87f982..ca200fa4f1 100644 --- a/src/app/system-wide-alert/system-wide-alert.module.ts +++ b/src/app/system-wide-alert/system-wide-alert.module.ts @@ -6,6 +6,7 @@ import { SharedModule } from '../shared/shared.module'; import { SystemWideAlertDataService } from '../core/data/system-wide-alert-data.service'; import { SystemWideAlertRoutingModule } from './system-wide-alert-routing.module'; import { UiSwitchModule } from 'ngx-ui-switch'; +import { NgbDatepickerModule, NgbTimepickerModule } from '@ng-bootstrap/ng-bootstrap'; @NgModule({ imports: [ @@ -13,6 +14,8 @@ import { UiSwitchModule } from 'ngx-ui-switch'; SharedModule, UiSwitchModule, SystemWideAlertRoutingModule, + NgbTimepickerModule, + NgbDatepickerModule, ], exports: [ SystemWideAlertBannerComponent From b4273fa734983b9b8c82748fefd62a5fd5793dfe Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 26 Jan 2023 15:41:45 +0100 Subject: [PATCH 061/173] 97425: Implement feedback --- .../system-wide-alert-banner.component.ts | 11 ++++-- .../system-wide-alert-form.component.html | 3 +- .../system-wide-alert-form.component.spec.ts | 36 ++++++++++++++++++- .../system-wide-alert-form.component.ts | 33 ++++++++++++++--- 4 files changed, 74 insertions(+), 9 deletions(-) diff --git a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts index 57a9604f90..a19d2a7e41 100644 --- a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts +++ b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { SystemWideAlertDataService } from '../../core/data/system-wide-alert-data.service'; import { getAllSucceededRemoteDataPayload } from '../../core/shared/operators'; import { filter, map, switchMap } from 'rxjs/operators'; @@ -7,6 +7,7 @@ import { SystemWideAlert } from '../system-wide-alert.model'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { BehaviorSubject, EMPTY, interval, Subscription } from 'rxjs'; import { zonedTimeToUtc } from 'date-fns-tz'; +import { isPlatformBrowser } from '@angular/common'; /** * Component responsible for rendering a banner and the countdown for an active system-wide alert @@ -44,6 +45,7 @@ export class SystemWideAlertBannerComponent implements OnInit, OnDestroy { subscriptions: Subscription[] = []; constructor( + @Inject(PLATFORM_ID) protected platformId: Object, protected systemWideAlertDataService: SystemWideAlertDataService ) { } @@ -65,7 +67,12 @@ export class SystemWideAlertBannerComponent implements OnInit, OnDestroy { const timeDifference = date.getTime() - new Date().getTime(); if (timeDifference > 0) { this.allocateTimeUnits(timeDifference); - return interval(1000); + if (isPlatformBrowser(this.platformId)) { + return interval(1000); + } else { + return EMPTY; + } + } } // Reset the countDown times to 0 and return EMPTY to prevent unnecessary countdown calculations diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html index d6eeaf5046..169081e277 100644 --- a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.html @@ -7,7 +7,7 @@ + (change)="setActive($event)">
@@ -44,6 +44,7 @@ placeholder="yyyy-mm-dd" name="dp" [(ngModel)]="date" + [minDate]="minDate" ngbDatepicker #d="ngbDatepicker" (ngModelChange)="updatePreviewTime()" diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts index 4fc79c1caa..505990b445 100644 --- a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.spec.ts @@ -149,8 +149,19 @@ describe('SystemWideAlertFormComponent', () => { }); }); + describe('setActive', () => { + it('should set whether the alert is active and save the current alert', () => { + spyOn(comp, 'save'); + spyOn(comp.formActive, 'patchValue'); + comp.setActive(true); + + expect(comp.formActive.patchValue).toHaveBeenCalledWith(true); + expect(comp.save).toHaveBeenCalledWith(false); + }); + }); + describe('save', () => { - it('should update the exising alert with the form values and show a success notification on success', () => { + it('should update the exising alert with the form values and show a success notification on success and navigate back', () => { spyOn(comp, 'back'); comp.currentAlert = systemWideAlert; @@ -173,6 +184,29 @@ describe('SystemWideAlertFormComponent', () => { expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('systemwidealerts'); expect(comp.back).toHaveBeenCalled(); }); + it('should update the exising alert with the form values and show a success notification on success and not navigate back when false is provided to the save method', () => { + spyOn(comp, 'back'); + comp.currentAlert = systemWideAlert; + + comp.formMessage.patchValue('New message'); + comp.formActive.patchValue(true); + comp.time = {hour: 4, minute: 26}; + comp.date = {year: 2023, month: 1, day: 25}; + + const expectedAlert = new SystemWideAlert(); + expectedAlert.alertId = systemWideAlert.alertId; + expectedAlert.message = 'New message'; + expectedAlert.active = true; + const countDownTo = new Date(2023, 0, 25, 4, 26); + expectedAlert.countdownTo = utcToZonedTime(countDownTo, 'UTC').toUTCString(); + + comp.save(false); + + expect(systemWideAlertDataService.put).toHaveBeenCalledWith(expectedAlert); + expect(notificationsService.success).toHaveBeenCalled(); + expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('systemwidealerts'); + expect(comp.back).not.toHaveBeenCalled(); + }); it('should update the exising alert with the form values but add an empty countdown date when disabled and show a success notification on success', () => { spyOn(comp, 'back'); comp.currentAlert = systemWideAlert; diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts index 6e0d2030fd..db517ef8cd 100644 --- a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.ts @@ -46,6 +46,11 @@ export class SystemWideAlertFormComponent implements OnInit { */ date: NgbDateStruct; + /** + * The minimum date for the countdown timer + */ + minDate: NgbDateStruct; + /** * Object to store the countdown time part */ @@ -90,6 +95,10 @@ export class SystemWideAlertFormComponent implements OnInit { ); this.createForm(); + const currentDate = new Date(); + this.minDate = {year: currentDate.getFullYear(), month: currentDate.getMonth() + 1, day: currentDate.getDate()}; + + this.systemWideAlert$.subscribe((alert) => { this.currentAlert = alert; this.initFormValues(alert); @@ -125,6 +134,16 @@ export class SystemWideAlertFormComponent implements OnInit { } + /** + * Set whether the system-wide alert is active + * Will also save the info in the current system-wide alert + * @param active + */ + setActive(active: boolean) { + this.formActive.patchValue(active); + this.save(false); + } + /** * Set whether the countdown timer is enabled or disabled. This will also update the counter in the preview * @param enabled - Whether the countdown timer is enabled or disabled. @@ -180,8 +199,10 @@ export class SystemWideAlertFormComponent implements OnInit { * Save the system-wide alert present in the form * When no alert is present yet on the server, a new one will be created * When one already exists, the existing one will be updated + * + * @param navigateToHomePage - Whether the user should be navigated back on successful save or not */ - save() { + save(navigateToHomePage = true) { const alert = new SystemWideAlert(); alert.message = this.formMessage.value; alert.active = this.formActive.value; @@ -193,20 +214,22 @@ export class SystemWideAlertFormComponent implements OnInit { } if (hasValue(this.currentAlert)) { const updatedAlert = Object.assign(new SystemWideAlert(), this.currentAlert, alert); - this.handleResponse(this.systemWideAlertDataService.put(updatedAlert), 'system-wide-alert.form.update'); + this.handleResponse(this.systemWideAlertDataService.put(updatedAlert), 'system-wide-alert.form.update', navigateToHomePage); } else { - this.handleResponse(this.systemWideAlertDataService.create(alert), 'system-wide-alert.form.create'); + this.handleResponse(this.systemWideAlertDataService.create(alert), 'system-wide-alert.form.create', navigateToHomePage); } } - private handleResponse(response$: Observable>, messagePrefix) { + private handleResponse(response$: Observable>, messagePrefix, navigateToHomePage: boolean) { response$.pipe( getFirstCompletedRemoteData() ).subscribe((response: RemoteData) => { if (response.hasSucceeded) { this.notificationsService.success(this.translateService.get(`${messagePrefix}.success`)); this.requestService.setStaleByHrefSubstring('systemwidealerts'); - this.back(); + if (navigateToHomePage) { + this.back(); + } } else { this.notificationsService.error(this.translateService.get(`${messagePrefix}.error`, response.errorMessage)); } From 8eab0f743f3769c97e9aebaefe78f5df94cefa90 Mon Sep 17 00:00:00 2001 From: cris Date: Sat, 28 Jan 2023 17:48:39 +0000 Subject: [PATCH 062/173] The collection is displayed in the workflow task --- ...-search-result-list-element.component.html | 1 + .../themed-item-list-preview.component.html | 16 +++++++ .../themed-item-list-preview.component.ts | 47 +++++++++++++++++-- ...-search-result-list-element.component.html | 1 + 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.html diff --git a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html index 5e98b00926..4584b12550 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component.html @@ -2,6 +2,7 @@
diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.html b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.html new file mode 100644 index 0000000000..a5e4138a5f --- /dev/null +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.html @@ -0,0 +1,16 @@ +
+
+
+
+ + {{"collection.listelement.badge" | translate}}: + +   + + {{collection.metadata["dc.title"][0]["value"]}} + +
+
+
+ +
\ No newline at end of file diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts index 3fe825d236..4d81ff2fbb 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts @@ -1,9 +1,13 @@ -import { Component, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, ComponentFactoryResolver, Input } from '@angular/core'; import { ThemedComponent } from '../../../theme-support/themed.component'; import { ItemListPreviewComponent } from './item-list-preview.component'; import { Item } from '../../../../core/shared/item.model'; import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import { SearchResult } from '../../../search/models/search-result.model'; +import { WorkflowItem } from 'src/app/core/submission/models/workflowitem.model'; +import { ThemeService } from 'src/app/shared/theme-support/theme.service'; +import { CollectionDataService } from 'src/app/core/data/collection-data.service'; +import { environment } from '../../../../../../src/environments/environment'; /** * Themed wrapper for ItemListPreviewComponent @@ -11,7 +15,7 @@ import { SearchResult } from '../../../search/models/search-result.model'; @Component({ selector: 'ds-themed-item-list-preview', styleUrls: [], - templateUrl: '../../../theme-support/themed.component.html', + templateUrl: 'themed-item-list-preview.component.html' }) export class ThemedItemListPreviewComponent extends ThemedComponent { protected inAndOutputNames: (keyof ItemListPreviewComponent & keyof this)[] = ['item', 'object', 'status', 'showSubmitter']; @@ -24,6 +28,43 @@ export class ThemedItemListPreviewComponent extends ThemedComponent { + this.collection = collection?.payload; + console.log("collection", this.collection) + }); + } protected getComponentName(): string { return 'ItemListPreviewComponent'; @@ -36,4 +77,4 @@ export class ThemedItemListPreviewComponent extends ThemedComponent { return import('./item-list-preview.component'); } -} +} \ No newline at end of file diff --git a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html index 4f0d6f774a..d863371446 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component.html @@ -2,6 +2,7 @@
From 328b16c1e4d88c3ede9e6f1488f9ea9c47aa3f34 Mon Sep 17 00:00:00 2001 From: cris Date: Sat, 28 Jan 2023 17:58:51 +0000 Subject: [PATCH 063/173] formatting issues fixed --- .../themed-item-list-preview.component.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts index 4d81ff2fbb..54371a8ac8 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts @@ -39,13 +39,13 @@ export class ThemedItemListPreviewComponent extends ThemedComponent { this.collection = collection?.payload; - console.log("collection", this.collection) }); } @@ -77,4 +76,4 @@ export class ThemedItemListPreviewComponent extends ThemedComponent { return import('./item-list-preview.component'); } -} \ No newline at end of file +} From b09de30272215b1e37d2dfefc10f805d53327bfd Mon Sep 17 00:00:00 2001 From: Mykhaylo Date: Tue, 31 Jan 2023 10:20:19 +0100 Subject: [PATCH 064/173] [CST-7757] renamed param --- src/app/shared/subscriptions/subscriptions-data.service.ts | 6 +++--- src/app/shared/testing/subscriptions-data.mock.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/shared/subscriptions/subscriptions-data.service.ts b/src/app/shared/subscriptions/subscriptions-data.service.ts index 9576d98536..cf1e90ef96 100644 --- a/src/app/shared/subscriptions/subscriptions-data.service.ts +++ b/src/app/shared/subscriptions/subscriptions-data.service.ts @@ -74,7 +74,7 @@ export class SubscriptionsDataService extends IdentifiableDataService `${endpointUrl}?dspace_object_id=${uuid}&eperson_id=${ePerson}`), + map((endpointUrl: string) => `${endpointUrl}?resource=${uuid}&eperson_id=${ePerson}`), map((endpointURL: string) => new CreateRequest(this.requestService.generateRequestId(), endpointURL, JSON.stringify(subscription))), sendRequest(this.requestService), switchMap((restRequest: RestRequest) => this.rdbService.buildFromRequestUUID(restRequest.uuid)), @@ -114,7 +114,7 @@ export class SubscriptionsDataService extends IdentifiableDataService `${endpointUrl}/${subscription.id}?dspace_object_id=${uuid}&eperson_id=${ePerson}`), + map((endpointUrl: string) => `${endpointUrl}/${subscription.id}?resource=${uuid}&eperson_id=${ePerson}`), map((endpointURL: string) => new PutRequest(this.requestService.generateRequestId(), endpointURL, JSON.stringify(subscription))), sendRequest(this.requestService), switchMap((restRequest: RestRequest) => this.rdbService.buildFromRequestUUID(restRequest.uuid)), diff --git a/src/app/shared/testing/subscriptions-data.mock.ts b/src/app/shared/testing/subscriptions-data.mock.ts index 8a4afc6d52..07108ad516 100644 --- a/src/app/shared/testing/subscriptions-data.mock.ts +++ b/src/app/shared/testing/subscriptions-data.mock.ts @@ -87,7 +87,7 @@ export const findByEPersonAndDsoResEmpty = { }, '_links': { 'self': { - 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/search/findByEPersonAndDso?dspace_object_id=092b59e8-8159-4e70-98b5-93ec60bd3431&eperson_id=335647b6-8a52-4ecb-a8c1-7ebabb199bda' + 'href': 'https://dspacecris7.4science.cloud/server/api/core/subscriptions/search/findByEPersonAndDso?resource=092b59e8-8159-4e70-98b5-93ec60bd3431&eperson_id=335647b6-8a52-4ecb-a8c1-7ebabb199bda' }, 'page': [ { From 38063d617e465db958efa6b96a3c35d826b4bc57 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Tue, 31 Jan 2023 13:06:03 +0100 Subject: [PATCH 065/173] 97425: Implement feedback --- .../data/system-wide-alert-data.service.ts | 23 ++++++++++++++++++- ...system-wide-alert-banner.component.spec.ts | 5 +++- .../system-wide-alert-banner.component.ts | 17 ++++++++++---- src/assets/i18n/en.json5 | 2 ++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/app/core/data/system-wide-alert-data.service.ts b/src/app/core/data/system-wide-alert-data.service.ts index 029ab76693..51a88b907a 100644 --- a/src/app/core/data/system-wide-alert-data.service.ts +++ b/src/app/core/data/system-wide-alert-data.service.ts @@ -17,16 +17,18 @@ import { SYSTEMWIDEALERT } from '../../system-wide-alert/system-wide-alert.resou import { SystemWideAlert } from '../../system-wide-alert/system-wide-alert.model'; import { PutData, PutDataImpl } from './base/put-data'; import { RequestParam } from '../cache/models/request-param.model'; +import { SearchData, SearchDataImpl } from './base/search-data'; /** * Dataservice representing a system-wide alert */ @Injectable() @dataService(SYSTEMWIDEALERT) -export class SystemWideAlertDataService extends IdentifiableDataService implements FindAllData, CreateData, PutData { +export class SystemWideAlertDataService extends IdentifiableDataService implements FindAllData, CreateData, PutData, SearchData { private findAllData: FindAllDataImpl; private createData: CreateDataImpl; private putData: PutDataImpl; + private searchData: SearchData; constructor( protected requestService: RequestService, @@ -40,6 +42,7 @@ export class SystemWideAlertDataService extends IdentifiableDataService>} + * Return an observable that emits response from the server + */ + searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + } diff --git a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.spec.ts b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.spec.ts index d27e5379e9..f767d0f196 100644 --- a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.spec.ts +++ b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.spec.ts @@ -9,6 +9,8 @@ import { TestScheduler } from 'rxjs/testing'; import { getTestScheduler } from 'jasmine-marbles'; import { By } from '@angular/platform-browser'; import { TranslateModule } from '@ngx-translate/core'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; describe('SystemWideAlertBannerComponent', () => { @@ -35,7 +37,7 @@ describe('SystemWideAlertBannerComponent', () => { }); systemWideAlertDataService = jasmine.createSpyObj('systemWideAlertDataService', { - findAll: createSuccessfulRemoteDataObject$(createPaginatedList([systemWideAlert])), + searchBy: createSuccessfulRemoteDataObject$(createPaginatedList([systemWideAlert])), }); TestBed.configureTestingModule({ @@ -43,6 +45,7 @@ describe('SystemWideAlertBannerComponent', () => { declarations: [SystemWideAlertBannerComponent], providers: [ {provide: SystemWideAlertDataService, useValue: systemWideAlertDataService}, + {provide: NotificationsService, useValue: new NotificationsServiceStub()}, ] }).compileComponents(); })); diff --git a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts index a19d2a7e41..320e42db85 100644 --- a/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts +++ b/src/app/system-wide-alert/alert-banner/system-wide-alert-banner.component.ts @@ -1,6 +1,6 @@ import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core'; import { SystemWideAlertDataService } from '../../core/data/system-wide-alert-data.service'; -import { getAllSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { getAllCompletedRemoteData } from '../../core/shared/operators'; import { filter, map, switchMap } from 'rxjs/operators'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { SystemWideAlert } from '../system-wide-alert.model'; @@ -8,6 +8,7 @@ import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { BehaviorSubject, EMPTY, interval, Subscription } from 'rxjs'; import { zonedTimeToUtc } from 'date-fns-tz'; import { isPlatformBrowser } from '@angular/common'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; /** * Component responsible for rendering a banner and the countdown for an active system-wide alert @@ -46,13 +47,21 @@ export class SystemWideAlertBannerComponent implements OnInit, OnDestroy { constructor( @Inject(PLATFORM_ID) protected platformId: Object, - protected systemWideAlertDataService: SystemWideAlertDataService + protected systemWideAlertDataService: SystemWideAlertDataService, + protected notificationsService: NotificationsService, ) { } ngOnInit() { - this.subscriptions.push(this.systemWideAlertDataService.findAll().pipe( - getAllSucceededRemoteDataPayload(), + this.subscriptions.push(this.systemWideAlertDataService.searchBy('active').pipe( + getAllCompletedRemoteData(), + map((rd) => { + if (rd.hasSucceeded) { + return rd.payload; + } else { + this.notificationsService.error('system-wide-alert-banner.retrieval.error'); + } + }), map((payload: PaginatedList) => payload.page), filter((page) => isNotEmpty(page)), map((page) => page[0]) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 26844bb1d6..056fc593d8 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4844,6 +4844,8 @@ "listable-notification-object.default-message": "This object couldn't be retrieved", + "system-wide-alert-banner.retrieval.error": "Something went wrong retrieving the system-wide alert banner", + "system-wide-alert-banner.countdown.prefix": "In", "system-wide-alert-banner.countdown.days": "{{days}} day(s),", From 4fb914b51cd085ce6487b251a46b0feba8e5cd2b Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 1 Feb 2023 14:52:52 +0100 Subject: [PATCH 066/173] 98855: Themed FileDownloadLinkComponent --- .../full-file-section.component.html | 8 ++-- .../file-section/file-section.component.html | 4 +- .../file-section.component.spec.ts | 4 +- .../detail/process-detail.component.html | 4 +- .../themed-file-download-link.component.ts | 38 +++++++++++++++++++ src/app/shared/shared.module.ts | 3 ++ .../file/section-upload-file.component.html | 4 +- 7 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 src/app/shared/file-download-link/themed-file-download-link.component.ts diff --git a/src/app/item-page/full/field-components/file-section/full-file-section.component.html b/src/app/item-page/full/field-components/file-section/full-file-section.component.html index 33acd6650b..f474c666f2 100644 --- a/src/app/item-page/full/field-components/file-section/full-file-section.component.html +++ b/src/app/item-page/full/field-components/file-section/full-file-section.component.html @@ -33,9 +33,9 @@
- + {{"item.page.filesection.download" | translate}} - +
@@ -74,9 +74,9 @@
- + {{"item.page.filesection.download" | translate}} - +
diff --git a/src/app/item-page/simple/field-components/file-section/file-section.component.html b/src/app/item-page/simple/field-components/file-section/file-section.component.html index 9d61b0a0e0..8e9fb63eda 100644 --- a/src/app/item-page/simple/field-components/file-section/file-section.component.html +++ b/src/app/item-page/simple/field-components/file-section/file-section.component.html @@ -1,11 +1,11 @@
- + {{file?.name}} ({{(file?.sizeBytes) | dsFileSize }}) - +
{{'item.page.bitstreams.view-more' | translate}} diff --git a/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts b/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts index 2d185aef9c..adad0cc350 100644 --- a/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts +++ b/src/app/item-page/simple/field-components/file-section/file-section.component.spec.ts @@ -109,7 +109,7 @@ describe('FileSectionComponent', () => { it('one bitstream should be on the page', () => { const viewMore = fixture.debugElement.query(By.css('.bitstream-view-more')); viewMore.triggerEventHandler('click', null); - const fileDownloadLink = fixture.debugElement.queryAll(By.css('ds-file-download-link')); + const fileDownloadLink = fixture.debugElement.queryAll(By.css('ds-themed-file-download-link')); expect(fileDownloadLink.length).toEqual(1); }); @@ -122,7 +122,7 @@ describe('FileSectionComponent', () => { }); it('should contain another bitstream', () => { - const fileDownloadLink = fixture.debugElement.queryAll(By.css('ds-file-download-link')); + const fileDownloadLink = fixture.debugElement.queryAll(By.css('ds-themed-file-download-link')); expect(fileDownloadLink.length).toEqual(2); }); }); diff --git a/src/app/process-page/detail/process-detail.component.html b/src/app/process-page/detail/process-detail.component.html index ae3418eafa..29cbfc113f 100644 --- a/src/app/process-page/detail/process-detail.component.html +++ b/src/app/process-page/detail/process-detail.component.html @@ -17,10 +17,10 @@
- + {{getFileName(file)}} ({{(file?.sizeBytes) | dsFileSize }}) - +
diff --git a/src/app/shared/file-download-link/themed-file-download-link.component.ts b/src/app/shared/file-download-link/themed-file-download-link.component.ts new file mode 100644 index 0000000000..4e619b8f28 --- /dev/null +++ b/src/app/shared/file-download-link/themed-file-download-link.component.ts @@ -0,0 +1,38 @@ +import { ThemedComponent } from '../theme-support/themed.component'; +import { Component, Input } from '@angular/core'; +import { FileDownloadLinkComponent } from './file-download-link.component'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { Item } from '../../core/shared/item.model'; + +@Component({ + selector: 'ds-themed-file-download-link', + styleUrls: [], + templateUrl: '../theme-support/themed.component.html', +}) +export class ThemedFileDownloadLinkComponent extends ThemedComponent { + + @Input() bitstream: Bitstream; + + @Input() item: Item; + + @Input() cssClasses: string; + + @Input() isBlank: boolean; + + @Input() enableRequestACopy: boolean; + + protected inAndOutputNames: (keyof FileDownloadLinkComponent & keyof this)[] = ['bitstream', 'item', 'cssClasses', 'isBlank', 'enableRequestACopy']; + + protected getComponentName(): string { + return 'FileDownloadLinkComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/shared/file-download-link/file-download-link.component`); + } + + protected importUnthemedComponent(): Promise { + return import('./file-download-link.component'); + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 45e9764151..b16814e8ce 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -256,6 +256,7 @@ import { 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'; +import { ThemedFileDownloadLinkComponent } from './file-download-link/themed-file-download-link.component'; import { CollectionDropdownComponent } from './collection-dropdown/collection-dropdown.component'; import { EntityDropdownComponent } from './entity-dropdown/entity-dropdown.component'; import { VocabularyTreeviewComponent } from './vocabulary-treeview/vocabulary-treeview.component'; @@ -479,6 +480,7 @@ const COMPONENTS = [ ModifyItemOverviewComponent, ImpersonateNavbarComponent, FileDownloadLinkComponent, + ThemedFileDownloadLinkComponent, BitstreamDownloadPageComponent, BitstreamRequestACopyPageComponent, CollectionDropdownComponent, @@ -561,6 +563,7 @@ const ENTRY_COMPONENTS = [ ClaimedTaskActionsEditMetadataComponent, CollectionDropdownComponent, FileDownloadLinkComponent, + ThemedFileDownloadLinkComponent, BitstreamDownloadPageComponent, BitstreamRequestACopyPageComponent, CurationFormComponent, diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.html b/src/app/submission/sections/upload/file/section-upload-file.component.html index 1bfc52529b..9bf4eb1bcb 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.html +++ b/src/app/submission/sections/upload/file/section-upload-file.component.html @@ -10,9 +10,9 @@
- + - + - - -
diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts index 65ddd76bc4..8f4d109ba1 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts @@ -44,12 +44,6 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent>; - /** - * The option used to render the "return to pool" component - * Every claimed task contains this option - */ - public returnToPoolOption = WORKFLOW_TASK_OPTION_RETURN_TO_POOL; - /** * Initialize instance variables * From 25dc0ab239558df957964fa513ab8a3b4c7d9d89 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 2 Feb 2023 17:31:27 +0100 Subject: [PATCH 074/173] [CST-7217] Use short number pipe and fix badge alignment --- .../search-facet-option.component.html | 7 +++---- .../search-facet-option.component.scss | 2 +- .../search-facet-option.component.spec.ts | 3 ++- .../search-facet-range-option.component.html | 4 ++-- .../search-facet-range-option.component.spec.ts | 8 ++++++-- .../search-facet-selected-option.component.scss | 2 +- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html index 967f0ff0d8..4e03767b9f 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html @@ -4,11 +4,10 @@ [queryParams]="addQueryParams" queryParamsHandling="merge"> - - {{filterValue.count}} - + diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.scss b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.scss index 65922287ee..2f6b236b1c 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.scss +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.scss @@ -9,7 +9,7 @@ a { } .break-facet { - word-break: break-all; + word-break: break-word; } .filter-checkbox { diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts index d92f6444ac..53acb1d911 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.spec.ts @@ -18,6 +18,7 @@ import { SearchFacetOptionComponent } from './search-facet-option.component'; import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; import { PaginationService } from '../../../../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../../../testing/pagination-service.stub'; +import { ShortNumberPipe } from '../../../../../utils/short-number.pipe'; describe('SearchFacetOptionComponent', () => { let comp: SearchFacetOptionComponent; @@ -89,7 +90,7 @@ describe('SearchFacetOptionComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], - declarations: [SearchFacetOptionComponent], + declarations: [SearchFacetOptionComponent, ShortNumberPipe], providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, { provide: Router, useValue: new RouterStub() }, diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html index 798038503f..0e6090b0fa 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html @@ -3,6 +3,6 @@ [queryParams]="changeQueryParams" queryParamsHandling="merge"> {{filterValue.label}} - {{filterValue.count}} - + {{filterValue.count | dsShortNumber}} + diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts index 7eb0cb8a54..b0acf43c32 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.spec.ts @@ -15,10 +15,14 @@ import { SearchConfigurationService } from '../../../../../../core/shared/search import { SearchFilterService } from '../../../../../../core/shared/search/search-filter.service'; import { By } from '@angular/platform-browser'; import { SearchFacetRangeOptionComponent } from './search-facet-range-option.component'; -import { RANGE_FILTER_MAX_SUFFIX, RANGE_FILTER_MIN_SUFFIX } from '../../search-range-filter/search-range-filter.component'; +import { + RANGE_FILTER_MAX_SUFFIX, + RANGE_FILTER_MIN_SUFFIX +} from '../../search-range-filter/search-range-filter.component'; import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; import { PaginationService } from '../../../../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../../../../testing/pagination-service.stub'; +import { ShortNumberPipe } from '../../../../../utils/short-number.pipe'; describe('SearchFacetRangeOptionComponent', () => { let comp: SearchFacetRangeOptionComponent; @@ -60,7 +64,7 @@ describe('SearchFacetRangeOptionComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule], - declarations: [SearchFacetRangeOptionComponent], + declarations: [SearchFacetRangeOptionComponent, ShortNumberPipe], providers: [ { provide: SearchService, useValue: new SearchServiceStub(searchLink) }, { provide: Router, useValue: new RouterStub() }, diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.scss b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.scss index 65922287ee..2f6b236b1c 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.scss +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component.scss @@ -9,7 +9,7 @@ a { } .break-facet { - word-break: break-all; + word-break: break-word; } .filter-checkbox { From bb7eef263196e6cb5d3c578d6687b39b3871bd53 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Thu, 2 Feb 2023 17:58:55 +0100 Subject: [PATCH 075/173] Fix typo --- .../alert-form/system-wide-alert-form.component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.scss b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.scss index 6e881e4807..cdcfd8b0cd 100644 --- a/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.scss +++ b/src/app/system-wide-alert/alert-form/system-wide-alert-form.component.scss @@ -1,4 +1,4 @@ .timepicker-margin { - // Negative margin to offset the time picker arrowsand ensure the date and time are correctly aligned + // Negative margin to offset the time picker arrows and ensure the date and time are correctly aligned margin-top: -38px; } From e92c2209e91d7c38fa991fff94bdcafacc5336a9 Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Thu, 2 Feb 2023 23:39:50 +0000 Subject: [PATCH 076/173] Update recent-item-list.component.ts Update PaginatedSearchOptions to only retrieve items --- .../home-page/recent-item-list/recent-item-list.component.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/home-page/recent-item-list/recent-item-list.component.ts b/src/app/home-page/recent-item-list/recent-item-list.component.ts index f0e5803b71..9a8535d970 100644 --- a/src/app/home-page/recent-item-list/recent-item-list.component.ts +++ b/src/app/home-page/recent-item-list/recent-item-list.component.ts @@ -17,6 +17,7 @@ import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-con import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { isPlatformBrowser } from '@angular/common'; import { setPlaceHolderAttributes } from '../../shared/utils/object-list-utils'; +import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model'; @Component({ selector: 'ds-recent-item-list', @@ -67,6 +68,7 @@ export class RecentItemListComponent implements OnInit { this.itemRD$ = this.searchService.search( new PaginatedSearchOptions({ pagination: this.paginationConfig, + dsoTypes: [DSpaceObjectType.ITEM], sort: this.sortConfig, }), undefined, From ac7b5f841e20df0ac7d60e171745b937bf20426c Mon Sep 17 00:00:00 2001 From: cris Date: Sat, 4 Feb 2023 21:28:49 +0000 Subject: [PATCH 077/173] collection is displayed below the submitter --- .../my-dspace-page/my-dspace-search.module.ts | 2 + .../item-collection.component.html | 7 ++ .../item-collection.component.scss | 0 .../item-collection.component.ts | 65 +++++++++++++++++++ .../item-submitter.component.html | 2 +- .../item-list-preview.component.html | 36 +++++----- .../item-list-preview.component.ts | 6 ++ .../themed-item-list-preview.component.html | 16 ----- .../themed-item-list-preview.component.ts | 29 +-------- 9 files changed, 102 insertions(+), 61 deletions(-) create mode 100644 src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.html create mode 100644 src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.scss create mode 100644 src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.ts delete mode 100644 src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.html diff --git a/src/app/my-dspace-page/my-dspace-search.module.ts b/src/app/my-dspace-page/my-dspace-search.module.ts index 1ce39991b3..6e450e8df1 100644 --- a/src/app/my-dspace-page/my-dspace-search.module.ts +++ b/src/app/my-dspace-page/my-dspace-search.module.ts @@ -18,6 +18,7 @@ import { ClaimedApprovedSearchResultListElementComponent } from '../shared/objec import { ClaimedDeclinedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-declined-search-result/claimed-declined-search-result-list-element.component'; import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module'; import { ItemSubmitterComponent } from '../shared/object-collection/shared/mydspace-item-submitter/item-submitter.component'; +import { ItemCollectionComponent } from '../shared/object-collection/shared/mydspace-item-collection/item-collection.component'; import { ItemDetailPreviewComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component'; import { ItemDetailPreviewFieldComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview-field/item-detail-preview-field.component'; import { ItemListPreviewComponent } from '../shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component'; @@ -44,6 +45,7 @@ const ENTRY_COMPONENTS = [ const DECLARATIONS = [ ...ENTRY_COMPONENTS, ItemSubmitterComponent, + ItemCollectionComponent, ItemDetailPreviewComponent, ItemDetailPreviewFieldComponent, ItemListPreviewComponent, diff --git a/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.html b/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.html new file mode 100644 index 0000000000..e17ba92a05 --- /dev/null +++ b/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.html @@ -0,0 +1,7 @@ +
+ {{'collection.listelement.badge' | translate}}: + + {{(collection$ | async)?.name}} + + +
\ No newline at end of file diff --git a/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.scss b/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.ts b/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.ts new file mode 100644 index 0000000000..8f857540d4 --- /dev/null +++ b/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.ts @@ -0,0 +1,65 @@ +import { Component, Input, OnInit } from '@angular/core'; + +import { EMPTY, Observable } from 'rxjs'; +import { map, mergeMap } from 'rxjs/operators'; + +import { RemoteData } from '../../../../core/data/remote-data'; +import { isNotEmpty } from '../../../empty.util'; +import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { Collection } from '../../../../core/shared/collection.model'; +import { getFirstCompletedRemoteData } from '../../../../core/shared/operators'; +import { LinkService } from '../../../../core/cache/builders/link.service'; +import { followLink } from '../../../utils/follow-link-config.model'; + +/** + * This component represents a badge with collection information. + */ +@Component({ + selector: 'ds-item-collection', + styleUrls: ['./item-collection.component.scss'], + templateUrl: './item-collection.component.html' +}) +export class ItemCollectionComponent implements OnInit { + + /** + * The target object + */ + @Input() object: any; + + /** + * The collection object + */ + collection$: Observable; + + public constructor(protected linkService: LinkService) { + + } + + /** + * Initialize collection object + */ + ngOnInit() { + + this.linkService.resolveLinks(this.object, followLink('workflowitem', {}, + followLink('collection',{}) + )); + this.collection$ = (this.object.workflowitem as Observable>).pipe( + getFirstCompletedRemoteData(), + mergeMap((rd: RemoteData) => { + if (rd.hasSucceeded && isNotEmpty(rd.payload)) { + return (rd.payload.collection as Observable>).pipe( + getFirstCompletedRemoteData(), + map((rds: RemoteData) => { + if (rds.hasSucceeded && isNotEmpty(rds.payload)) { + return rds.payload; + } else { + return null; + } + }) + ); + } else { + return EMPTY; + } + })); + } +} diff --git a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.html b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.html index 0f7ae433fa..db38f98b04 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.html +++ b/src/app/shared/object-collection/shared/mydspace-item-submitter/item-submitter.component.html @@ -1,3 +1,3 @@
- {{'submission.workflow.tasks.generic.submitter' | translate}} : {{(submitter$ | async)?.name}} + {{'submission.workflow.tasks.generic.submitter' | translate}}: {{(submitter$ | async)?.name}}
diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.html b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.html index d2db0ba209..94426136b5 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.html +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.html @@ -15,34 +15,36 @@

- - - ( - + + ( + ) - - {{'mydspace.results.no-authors' | translate}} - - - ; + {{'mydspace.results.no-authors' + | translate}} + + + ; + + + - - - -
+
-
+
\ No newline at end of file diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts index 6b40678ded..39f83bc371 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/item-list-preview.component.ts @@ -8,6 +8,7 @@ import { import { SearchResult } from '../../../search/models/search-result.model'; import { APP_CONFIG, AppConfig } from '../../../../../config/app-config.interface'; import { DSONameService } from '../../../../core/breadcrumbs/dso-name.service'; +import { WorkflowItem } from 'src/app/core/submission/models/workflowitem.model'; /** * This component show metadata for the given item object in the list view. @@ -40,6 +41,11 @@ export class ItemListPreviewComponent implements OnInit { */ @Input() showSubmitter = false; + /** + * Represents the workflow of the item + */ + @Input() workflowItem: WorkflowItem; + /** * Display thumbnails if required by configuration */ diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.html b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.html deleted file mode 100644 index a5e4138a5f..0000000000 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.html +++ /dev/null @@ -1,16 +0,0 @@ -
-
-
-
- - {{"collection.listelement.badge" | translate}}: - -   - - {{collection.metadata["dc.title"][0]["value"]}} - -
-
-
- -
\ No newline at end of file diff --git a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts index 54371a8ac8..ea5a38e3cb 100644 --- a/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts +++ b/src/app/shared/object-list/my-dspace-result-list-element/item-list-preview/themed-item-list-preview.component.ts @@ -6,8 +6,6 @@ import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspa import { SearchResult } from '../../../search/models/search-result.model'; import { WorkflowItem } from 'src/app/core/submission/models/workflowitem.model'; import { ThemeService } from 'src/app/shared/theme-support/theme.service'; -import { CollectionDataService } from 'src/app/core/data/collection-data.service'; -import { environment } from '../../../../../../src/environments/environment'; /** * Themed wrapper for ItemListPreviewComponent @@ -15,10 +13,10 @@ import { environment } from '../../../../../../src/environments/environment'; @Component({ selector: 'ds-themed-item-list-preview', styleUrls: [], - templateUrl: 'themed-item-list-preview.component.html' + templateUrl: '../../../theme-support/themed.component.html' }) export class ThemedItemListPreviewComponent extends ThemedComponent { - protected inAndOutputNames: (keyof ItemListPreviewComponent & keyof this)[] = ['item', 'object', 'status', 'showSubmitter']; + protected inAndOutputNames: (keyof ItemListPreviewComponent & keyof this)[] = ['item', 'object', 'status', 'showSubmitter', 'workflowItem']; @Input() item: Item; @@ -30,39 +28,16 @@ export class ThemedItemListPreviewComponent extends ThemedComponent { - this.collection = collection?.payload; - }); } protected getComponentName(): string { From b1011edb3d18d540e639b30e92bde8753048d900 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 3 Feb 2023 12:29:03 +0100 Subject: [PATCH 078/173] [CST-7757] Remove subscription button from item pages --- .../item-pages/journal-issue/journal-issue.component.html | 1 - .../item-pages/journal-volume/journal-volume.component.html | 1 - .../journal-entities/item-pages/journal/journal.component.html | 1 - .../item-pages/org-unit/org-unit.component.html | 1 - .../research-entities/item-pages/person/person.component.html | 1 - .../research-entities/item-pages/project/project.component.html | 1 - src/app/item-page/full/full-item-page.component.html | 1 - .../simple/item-types/publication/publication.component.html | 1 - .../simple/item-types/untyped-item/untyped-item.component.html | 1 - 9 files changed, 9 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html index e12b8a5ceb..bf73a62447 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html @@ -6,7 +6,6 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> -
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html index 1bc9ec1137..b370431a27 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html @@ -6,7 +6,6 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> -
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index b787e937d6..27ee373237 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -6,7 +6,6 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> -
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 524adae4ce..ac92e4ad6d 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -6,7 +6,6 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> -
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 998c07777f..a73236006b 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 @@ -7,7 +7,6 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> -
diff --git a/src/app/entity-groups/research-entities/item-pages/project/project.component.html b/src/app/entity-groups/research-entities/item-pages/project/project.component.html index b52f5aa8dd..3fb5e2d520 100644 --- a/src/app/entity-groups/research-entities/item-pages/project/project.component.html +++ b/src/app/entity-groups/research-entities/item-pages/project/project.component.html @@ -6,7 +6,6 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> -
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 60aa6d6f86..ee4ead2835 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 @@
-
diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html index 806bd4baaf..5c3e5e77b0 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -15,7 +15,6 @@ [tooltipMsgCreate]="'item.page.version.create'" [tooltipMsgHasDraft]="'item.page.version.hasDraft'"> -
From a4d94b0399ae28562bd6e0692770231504089e8c Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 3 Feb 2023 23:37:58 +0100 Subject: [PATCH 079/173] [CST-7757] Response parameters fixed; submit button disabled if frequency is missing --- .../subscriptions/models/subscription.model.ts | 8 ++++---- .../subscription-modal.component.html | 2 +- .../subscription-modal.component.ts | 12 ++++++++++++ .../subscriptions/subscriptions-data.service.ts | 4 ++-- .../subscriptions-page.component.html | 4 ++-- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/app/shared/subscriptions/models/subscription.model.ts b/src/app/shared/subscriptions/models/subscription.model.ts index 36e43ce996..b460a0418f 100644 --- a/src/app/shared/subscriptions/models/subscription.model.ts +++ b/src/app/shared/subscriptions/models/subscription.model.ts @@ -39,8 +39,8 @@ export class Subscription extends DSpaceObject { @deserialize _links: { self: HALLink; - ePerson: HALLink; - dSpaceObject: HALLink; + eperson: HALLink; + resource: HALLink; }; /** @@ -48,14 +48,14 @@ export class Subscription extends DSpaceObject { * Will be undefined unless the logo {@link HALLink} has been resolved. */ @link(EPERSON) - ePerson?: Observable>; + eperson?: Observable>; /** * The logo for this Community * Will be undefined unless the logo {@link HALLink} has been resolved. */ @link(DSPACE_OBJECT) - dSpaceObject?: Observable>; + resource?: Observable>; /** * The embedded ePerson & dSpaceObject for this Subscription */ diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html index f78567ee7b..156b8717f9 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html @@ -30,7 +30,7 @@ (click)="activeModal.close()"> {{'item.page.subscriptions.modal.close' | translate}} -
From 0f8d2d7b16afbe503141e5747814f17711b2e752 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Fri, 3 Feb 2023 23:45:59 +0100 Subject: [PATCH 080/173] [CST-7757] D-W-M fixed --- .../subscription-modal/subscription-modal.component.ts | 2 +- src/assets/i18n/en.json5 | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts index fa7aec9273..fc7e021b86 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts @@ -64,7 +64,7 @@ export class SubscriptionModalComponent implements OnInit { /** * Frequencies to be shown as checkboxes */ - private frequencyDefaultValues = ['D', 'M', 'W']; + private frequencyDefaultValues = ['D', 'W', 'M']; /** * True if form status has changed and at least one frequency is checked diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 49e0657877..e3fee7198c 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2313,11 +2313,11 @@ "item.page.subscriptions.modal.new-subscription-form.type.content": "Content", - "item.page.subscriptions.modal.new-subscription-form.frequency.D": "daily", + "item.page.subscriptions.modal.new-subscription-form.frequency.D": "Daily", - "item.page.subscriptions.modal.new-subscription-form.frequency.M": "monthly", + "item.page.subscriptions.modal.new-subscription-form.frequency.W": "Weekly", - "item.page.subscriptions.modal.new-subscription-form.frequency.W": "weekly", + "item.page.subscriptions.modal.new-subscription-form.frequency.M": "Monthly", "item.page.subscriptions.modal.new-subscription-form.submit": "Submit", From 0dfb37d15403acd954278e799f1a72a42b9dcfdd Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 6 Feb 2023 10:22:12 +0100 Subject: [PATCH 081/173] [CST-7757] Show object name --- .../subscription-modal/subscription-modal.component.html | 1 + src/assets/i18n/en.json5 | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html index 156b8717f9..debc6edf50 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html @@ -6,6 +6,7 @@ - - + diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index ba0200a892..27f461e7ea 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4621,6 +4621,14 @@ "subscriptions.table.action": "Action", + "subscriptions.table.edit": "Edit", + + "subscriptions.table.delete": "Delete", + + "subscriptions.table.not-available": "Not available", + + "subscriptions.table.not-available-message": "The subscribed item has been deleted, or you don't currently have the permission to view it", + "subscriptions.table.empty.message": "You do not have any subscriptions at this time. To subscribe to email updates for a Community or Collection, use the subscription button on the object's page.", From 1e20551d2a375a5b2abf602b9ba31edb263a310f Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Fri, 13 Jan 2023 17:54:39 +0100 Subject: [PATCH 128/173] Keep SSL sessions alive between xhr2 requests --- src/app/core/services/server-xhr.service.ts | 51 +++++++++++++++++++++ src/modules/app/server-app.module.ts | 6 +++ 2 files changed, 57 insertions(+) create mode 100644 src/app/core/services/server-xhr.service.ts diff --git a/src/app/core/services/server-xhr.service.ts b/src/app/core/services/server-xhr.service.ts new file mode 100644 index 0000000000..17e5162d43 --- /dev/null +++ b/src/app/core/services/server-xhr.service.ts @@ -0,0 +1,51 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ + +import { XhrFactory } from '@angular/common'; +import { Injectable } from '@angular/core'; +import { Agent as HttpAgent, AgentOptions as HttpAgentOptions } from 'http'; +import { Agent as HttpsAgent } from 'https'; +import { XMLHttpRequest } from 'xhr2'; + +/** + * Allow HTTP sessions to be kept alive. + * Without this configuration, Angular re-connects to REST multiple times per SSR cycle. + * https://nodejs.org/api/http.html#new-agentoptions + */ +const agentOptions: HttpAgentOptions = { + keepAlive: true, + keepAliveMsecs: 60 * 1000, +}; + +// Agents need to be reused between requests, otherwise keep-alive doesn't help. +const httpAgent = new HttpAgent(agentOptions); +const httpsAgent = new HttpsAgent(agentOptions); + +/** + * Contructs the XMLHttpRequest instances used for all HttpClient requests. + * Emulated by https://github.com/pwnall/node-xhr2 on the server. + * This class overrides the built-in Angular implementation to set additional configuration. + * + * Note that this must be provided in ServerAppModule; + * it doesn't work when added as a Universal engine provider. + */ +@Injectable() +export class ServerXhrService implements XhrFactory { + build(): XMLHttpRequest { + const xhr = new XMLHttpRequest(); + + // This call is specific to xhr2 and will probably break if we use another library. + // https://github.com/pwnall/node-xhr2#features + (xhr as any).nodejsSet({ + httpAgent, + httpsAgent, + }); + + return xhr; + } +} diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 01a5548948..6bb344a1cd 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -34,6 +34,8 @@ import { AuthRequestService } from '../../app/core/auth/auth-request.service'; import { ServerAuthRequestService } from '../../app/core/auth/server-auth-request.service'; import { CorrelationIdService } from '../../app/correlation-id/correlation-id.service'; import { AppConfig, APP_CONFIG_STATE } from '../../config/app-config.interface'; +import { XhrFactory } from '@angular/common'; +import { ServerXhrService } from '../../app/core/services/server-xhr.service'; import { environment } from '../../environments/environment'; @@ -121,6 +123,10 @@ export function createTranslateLoader() { provide: HardRedirectService, useClass: ServerHardRedirectService, }, + { + provide: XhrFactory, + useClass: ServerXhrService, + }, ] }) export class ServerAppModule { From 800d3a90019a51f45e7d1b6f6b899231bf7fbd86 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 9 Feb 2023 15:31:03 +0100 Subject: [PATCH 129/173] [CST-7755] added cancel button to modal and updated message returned when a supervision order already exists --- .../supervision-group-selector.component.html | 17 +++++++++++------ .../supervision-group-selector.component.ts | 6 +++--- ...item-search-result-list-element.component.ts | 2 +- src/assets/i18n/en.json5 | 4 ++++ 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.html b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.html index f6a5c339ce..c6d45c8743 100644 --- a/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.html +++ b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.html @@ -30,11 +30,16 @@ (select)="updateGroupObjectSelected($event)"> -
- + +
- \ No newline at end of file + diff --git a/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts index 1ae2d496d2..88ffb13070 100644 --- a/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts @@ -79,13 +79,13 @@ export class SupervisionGroupSelectorComponent { if (rd.state === 'Success') { this.supervisionOrderDataService.searchByItem(this.itemUUID, null, null, followLink('group')); this.notificationsService.success(this.translateService.get('supervision-group-selector.notification.create.success.title', { name: this.selectedGroup.name })); + this.close(); } else { this.notificationsService.error( - this.translateService.get('supervision-group-selector.notification.create.failure.title'), - rd.errorMessage); + this.translateService.get('supervision-group-selector.notification.create.failure.title'), + rd.statusCode == 422 ? this.translateService.get('supervision-group-selector.notification.create.already-existing') : rd.errorMessage); } }); - this.close(); } } diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts index 41afab0ab8..2fa72d2ac0 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts @@ -100,7 +100,7 @@ export class ItemSearchResultListElementComponent extends SearchResultListElemen ).pipe( getAllSucceededRemoteData(), ).subscribe((result) => { - this.supervisionOrder$ = this.supervisionOrderDataService.searchByItem(this.dso.uuid, null, null, followLink('group')).pipe( + this.supervisionOrder$ = this.supervisionOrderDataService.searchByItem(this.dso.uuid, false, true, followLink('group')).pipe( getAllSucceededRemoteListPayload(), switchMap((supervisionOrders: SupervisionOrder[]) => { const supervisionOrdersArray = supervisionOrders.map((supervisionOrder: SupervisionOrder) => { diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0251f171b7..47fbbaf518 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1443,6 +1443,8 @@ "supervision-group-selector.select.group.label": "Select a Group", + "supervision-group-selector.button.cancel": "Cancel", + "supervision-group-selector.button.save": "Save", "supervision-group-selector.select.type-of-order.error": "Please select a type of order", @@ -1453,6 +1455,8 @@ "supervision-group-selector.notification.create.failure.title": "Error", + "supervision-group-selector.notification.create.already-existing" : "A supervision order already exists on this item for selected group", + "confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}", "confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}", From 52cd98a7d3a41891b407971dba6fe529f9dc22b3 Mon Sep 17 00:00:00 2001 From: corrado lombardi Date: Thu, 9 Feb 2023 15:39:13 +0100 Subject: [PATCH 130/173] [CST-7755] lint fix --- .../supervision-group-selector.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts index 88ffb13070..da22fe0d44 100644 --- a/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component.ts @@ -83,7 +83,7 @@ export class SupervisionGroupSelectorComponent { } else { this.notificationsService.error( this.translateService.get('supervision-group-selector.notification.create.failure.title'), - rd.statusCode == 422 ? this.translateService.get('supervision-group-selector.notification.create.already-existing') : rd.errorMessage); + rd.statusCode === 422 ? this.translateService.get('supervision-group-selector.notification.create.already-existing') : rd.errorMessage); } }); } From ac06f30950ee2f9cdb05a08a53019c48abaf5a2e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 9 Feb 2023 17:19:07 +0100 Subject: [PATCH 131/173] 99053: Removed ngx-bootstrap dependency --- package.json | 1 - ...nced-workflow-action-rating.component.html | 10 +- ...d-workflow-action-rating.component.spec.ts | 4 +- .../workflowitems-edit-page.module.ts | 4 +- yarn.lock | 30393 ++++++++-------- 5 files changed, 15203 insertions(+), 15209 deletions(-) diff --git a/package.json b/package.json index 71b744fe77..278afdf6c3 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,6 @@ "ng-mocks": "11.11.2", "ng2-file-upload": "1.4.0", "ng2-nouislider": "^1.8.3", - "ngx-bootstrap": "7.1.2", "ngx-infinite-scroll": "^10.0.1", "ngx-moment": "^5.0.0", "ngx-pagination": "5.0.0", diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.html index ac0cdac1cc..aca9b4da73 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.html +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.html @@ -21,12 +21,14 @@
-
diff --git a/src/app/shared/form/chips/chips.component.spec.ts b/src/app/shared/form/chips/chips.component.spec.ts index 2b8a469bd1..050950ed4d 100644 --- a/src/app/shared/form/chips/chips.component.spec.ts +++ b/src/app/shared/form/chips/chips.component.spec.ts @@ -122,7 +122,7 @@ describe('ChipsComponent test suite', () => { })); it('should save chips item index when drag and drop start', fakeAsync(() => { - const de = chipsFixture.debugElement.query(By.css('li.nav-item')); + const de = chipsFixture.debugElement.query(By.css('div.nav-item')); de.triggerEventHandler('dragstart', null); @@ -131,7 +131,7 @@ describe('ChipsComponent test suite', () => { it('should update chips item order when drag and drop end', fakeAsync(() => { spyOn(chipsComp.chips, 'updateOrder'); - const de = chipsFixture.debugElement.query(By.css('li.nav-item')); + const de = chipsFixture.debugElement.query(By.css('div.nav-item')); de.triggerEventHandler('dragend', null); @@ -158,7 +158,7 @@ describe('ChipsComponent test suite', () => { }); it('should show icon for every field that has a configured icon', () => { - const de = chipsFixture.debugElement.query(By.css('li.nav-item')); + const de = chipsFixture.debugElement.query(By.css('div.nav-item')); const icons = de.queryAll(By.css('i.fas')); expect(icons.length).toBe(4); @@ -166,7 +166,7 @@ describe('ChipsComponent test suite', () => { }); it('should show tooltip on mouse over an icon', () => { - const de = chipsFixture.debugElement.query(By.css('li.nav-item')); + const de = chipsFixture.debugElement.query(By.css('div.nav-item')); const icons = de.queryAll(By.css('i.fas')); icons[0].triggerEventHandler('mouseover', null); From 19d0975df517ed9e49f8770ca2d828a46311ba1e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 10 Feb 2023 12:21:33 +0100 Subject: [PATCH 133/173] 99053: Fixed encoded parameters being encoded twice in WorkflowItemActionPageComponent.previousPage() --- .../workflow-item-action-page.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts b/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts index 0f6a222d85..276b2054fe 100644 --- a/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts +++ b/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts @@ -78,7 +78,7 @@ export abstract class WorkflowItemActionPageComponent implements OnInit { const params: Params = {}; if (url.split('?').length > 1) { for (const param of url.split('?')[1].split('&')) { - params[param.split('=')[0]] = param.split('=')[1]; + params[param.split('=')[0]] = decodeURIComponent(param.split('=')[1]); } } void this.router.navigate([url.split('?')[0]], { queryParams: params }); From 99f9f592a0223a523887cf48cc6d4dfbcade99b0 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 10 Feb 2023 12:50:36 +0100 Subject: [PATCH 134/173] 99053: Passed previousSearchQuery to AdvancedWorkflowActionSelectReviewerComponent --- ...claimed-task-actions-abstract.component.ts | 16 +++++++--- ...k-action-select-reviewer.component.spec.ts | 24 ++++++++++++++- ...d-task-action-select-reviewer.component.ts | 12 +++++++- src/app/shared/testing/active-router.stub.ts | 3 +- ...w-action-select-reviewer.component.spec.ts | 29 +++++++++++++++---- ...rkflow-action-select-reviewer.component.ts | 13 +++++++-- 6 files changed, 82 insertions(+), 15 deletions(-) diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts index 06d51d67a3..32d9a230eb 100644 --- a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts @@ -3,6 +3,7 @@ import { ClaimedTaskActionsAbstractComponent } from './claimed-task-actions-abst import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; +import { Params } from '@angular/router'; /** * Abstract component for rendering an advanced claimed task's action @@ -47,11 +48,18 @@ export abstract class AdvancedClaimedTaskActionsAbstractComponent extends Claime */ openAdvancedClaimedTaskTab(): void { void this.router.navigate([this.workflowTaskPageRoute], { - queryParams: { - workflow: this.workflowType, - claimedTask: this.object.id, - }, + queryParams: this.getQueryParams(), }); } + /** + * The {@link Params} that need to be given to the workflow page. + */ + getQueryParams(): Params { + return { + workflow: this.workflowType, + claimedTask: this.object.id, + }; + } + } diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts index db670e3cdc..60f47a60b8 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedClaimedTaskActionSelectReviewerComponent } from './advanced-claimed-task-action-select-reviewer.component'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { RouterStub } from '../../../testing/router.stub'; import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; import { NotificationsService } from '../../../notifications/notifications.service'; @@ -22,6 +22,7 @@ import { } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { of as observableOf } from 'rxjs'; +import { ActivatedRouteStub } from '../../../testing/active-router.stub'; const taskId = 'claimed-task-1'; const workflowId = 'workflow-1'; @@ -36,12 +37,14 @@ describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { let component: AdvancedClaimedTaskActionSelectReviewerComponent; let fixture: ComponentFixture; + let route: ActivatedRouteStub; let claimedTaskDataService: ClaimedTaskDataServiceStub; let notificationService: NotificationsServiceStub; let router: RouterStub; let searchService: SearchServiceStub; beforeEach(async () => { + route = new ActivatedRouteStub(); claimedTaskDataService = new ClaimedTaskDataServiceStub(); notificationService = new NotificationsServiceStub(); router = new RouterStub(); @@ -56,6 +59,7 @@ describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { NgbTooltip, ], providers: [ + { provide: ActivatedRoute, useValue: route }, { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, { provide: NotificationsService, useValue: notificationService }, { provide: RequestService, useValue: {} }, @@ -95,4 +99,22 @@ describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { }, }); }); + + it('should navigate to the advanced workflow page with a previousSearchQuery when clicked anq a query is defined', () => { + spyOnProperty(route, 'snapshot').and.returnValue({ + queryParams: { + query: 'Thor%20Love%20and%20Thunder', + } + }); + component.workflowTaskPageRoute = `/workflowitems/${workflowId}/advanced`; + fixture.debugElement.query(By.css('.selectReviewerAction')).nativeElement.click(); + + expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${workflowId}/advanced`], { + queryParams: { + workflow: ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, + claimedTask: taskId, + previousSearchQuery: 'Thor%20Love%20and%20Thunder', + }, + }); + }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts index 7473c737d9..2331a8bd7f 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts @@ -3,7 +3,7 @@ import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-deco import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advanced-claimed-task-actions-abstract.component'; -import { Router } from '@angular/router'; +import { Router, Params, ActivatedRoute } from '@angular/router'; import { NotificationsService } from '../../../notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { SearchService } from '../../../../core/shared/search/search.service'; @@ -12,6 +12,7 @@ import { ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; +import { hasValue } from '../../../empty.util'; /** * Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionSelectReviewerComponent} @@ -38,8 +39,17 @@ export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedCl protected translate: TranslateService, protected searchService: SearchService, protected requestService: RequestService, + protected route: ActivatedRoute, ) { super(injector, router, notificationsService, translate, searchService, requestService); } + getQueryParams(): Params { + const params: Params = super.getQueryParams(); + if (hasValue(this.route.snapshot.queryParams.query)) { + params.previousSearchQuery = this.route.snapshot.queryParams.query; + } + return params; + } + } diff --git a/src/app/shared/testing/active-router.stub.ts b/src/app/shared/testing/active-router.stub.ts index aa4bfce438..86d92a2fb8 100644 --- a/src/app/shared/testing/active-router.stub.ts +++ b/src/app/shared/testing/active-router.stub.ts @@ -54,7 +54,8 @@ export class ActivatedRouteStub { get snapshot() { return { params: this.testParams, - queryParamMap: convertToParamMap(this.testParams) + queryParamMap: convertToParamMap(this.testParams), + queryParams: {} as Params, }; } } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts index 2884dbcc8e..42d6874899 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts @@ -1,12 +1,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedWorkflowActionSelectReviewerComponent, - ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER + ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER, } from './advanced-workflow-action-select-reviewer.component'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub'; -import { RouterTestingModule } from '@angular/router/testing'; import { WorkflowActionDataServiceStub } from '../../../shared/testing/workflow-action-data-service.stub'; import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; import { RouteService } from '../../../core/services/route.service'; @@ -25,6 +24,7 @@ import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-res import { NO_ERRORS_SCHEMA } from '@angular/core'; import { RequestService } from '../../../core/data/request.service'; import { RequestServiceStub } from '../../../shared/testing/request-service.stub'; +import { RouterStub } from '../../../shared/testing/router.stub'; const claimedTaskId = '2'; const workflowId = '1'; @@ -37,18 +37,19 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { let claimedTaskDataService: ClaimedTaskDataServiceStub; let notificationService: NotificationsServiceStub; + let router: RouterStub; let workflowActionDataService: WorkflowItemDataServiceStub; let workflowItemDataService: WorkflowItemDataServiceStub; beforeEach(async () => { claimedTaskDataService = new ClaimedTaskDataServiceStub(); notificationService = new NotificationsServiceStub(); + router = new RouterStub(); workflowActionDataService = new WorkflowActionDataServiceStub(); workflowItemDataService = new WorkflowItemDataServiceStub(); await TestBed.configureTestingModule({ imports: [ - RouterTestingModule, TranslateModule.forRoot(), ], declarations: [ @@ -66,10 +67,12 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { queryParams: { claimedTask: claimedTaskId, workflow: 'testaction', + previousSearchQuery: 'Thor%20Love%20and%20Thunder', }, }, }, }, + { provide: Router, useValue: router }, { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, { provide: NotificationsService, useValue: notificationService }, { provide: RouteService, useValue: routeServiceStub }, @@ -85,14 +88,30 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { fixture = TestBed.createComponent(AdvancedWorkflowActionSelectReviewerComponent); component = fixture.componentInstance; fixture.detectChanges(); - spyOn(component, 'previousPage'); }); afterEach(() => { fixture.debugElement.nativeElement.remove(); }); + describe('previousPage', () => { + it('should navigate back to the Workflow tasks page with the previous query', () => { + component.previousPage(); + + expect(router.navigate).toHaveBeenCalledWith(['/mydspace'], { + queryParams: { + configuration: 'workflow', + query: 'Thor Love and Thunder', + }, + }); + }); + }); + describe('performAction', () => { + beforeEach(() => { + spyOn(component, 'previousPage'); + }); + it('should call the claimedTaskDataService with the list of selected ePersons', () => { spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); component.selectedReviewers = [EPersonMock, EPersonMock2]; diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index b9f90e1736..54eb87f2cd 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -12,7 +12,7 @@ import { } from '../../../access-control/group-registry/group-form/members-list/members-list.component'; import { Subscription } from 'rxjs'; import { EPerson } from '../../../core/eperson/models/eperson.model'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { RouteService } from '../../../core/services/route.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -20,7 +20,8 @@ import { TranslateService } from '@ngx-translate/core'; import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; import { ReviewersListComponent } from './reviewers-list/reviewers-list.component'; -import { RequestService } from 'src/app/core/data/request.service'; +import { RequestService } from '../../../core/data/request.service'; +import { hasValue } from '../../../shared/empty.util'; export const ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer'; export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction'; @@ -138,7 +139,13 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf * switching between the different pages */ previousPage(): void { - void this.router.navigate(['/mydspace'], { queryParams: { configuration: 'workflow' } }); + const queryParams: Params = { + configuration: 'workflow', + }; + if (hasValue(this.route.snapshot.queryParams.previousSearchQuery)) { + queryParams.query = decodeURIComponent(this.route.snapshot.queryParams.previousSearchQuery); + } + void this.router.navigate(['/mydspace'], { queryParams: queryParams }); } } From f0ceb645c75d54e21f9705939caabbc920dfe118 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 10 Feb 2023 12:50:36 +0100 Subject: [PATCH 135/173] 99053: Passed previousSearchQuery to AdvancedWorkflowActionSelectReviewerComponent --- ...claimed-task-actions-abstract.component.ts | 16 +++++++--- ...k-action-select-reviewer.component.spec.ts | 24 ++++++++++++++- ...d-task-action-select-reviewer.component.ts | 12 +++++++- src/app/shared/testing/active-router.stub.ts | 3 +- ...w-action-select-reviewer.component.spec.ts | 29 +++++++++++++++---- ...rkflow-action-select-reviewer.component.ts | 14 ++++++--- 6 files changed, 82 insertions(+), 16 deletions(-) diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts index 06d51d67a3..32d9a230eb 100644 --- a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts @@ -3,6 +3,7 @@ import { ClaimedTaskActionsAbstractComponent } from './claimed-task-actions-abst import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; +import { Params } from '@angular/router'; /** * Abstract component for rendering an advanced claimed task's action @@ -47,11 +48,18 @@ export abstract class AdvancedClaimedTaskActionsAbstractComponent extends Claime */ openAdvancedClaimedTaskTab(): void { void this.router.navigate([this.workflowTaskPageRoute], { - queryParams: { - workflow: this.workflowType, - claimedTask: this.object.id, - }, + queryParams: this.getQueryParams(), }); } + /** + * The {@link Params} that need to be given to the workflow page. + */ + getQueryParams(): Params { + return { + workflow: this.workflowType, + claimedTask: this.object.id, + }; + } + } diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts index db670e3cdc..60f47a60b8 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedClaimedTaskActionSelectReviewerComponent } from './advanced-claimed-task-action-select-reviewer.component'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { RouterStub } from '../../../testing/router.stub'; import { NotificationsServiceStub } from '../../../testing/notifications-service.stub'; import { NotificationsService } from '../../../notifications/notifications.service'; @@ -22,6 +22,7 @@ import { } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { of as observableOf } from 'rxjs'; +import { ActivatedRouteStub } from '../../../testing/active-router.stub'; const taskId = 'claimed-task-1'; const workflowId = 'workflow-1'; @@ -36,12 +37,14 @@ describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { let component: AdvancedClaimedTaskActionSelectReviewerComponent; let fixture: ComponentFixture; + let route: ActivatedRouteStub; let claimedTaskDataService: ClaimedTaskDataServiceStub; let notificationService: NotificationsServiceStub; let router: RouterStub; let searchService: SearchServiceStub; beforeEach(async () => { + route = new ActivatedRouteStub(); claimedTaskDataService = new ClaimedTaskDataServiceStub(); notificationService = new NotificationsServiceStub(); router = new RouterStub(); @@ -56,6 +59,7 @@ describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { NgbTooltip, ], providers: [ + { provide: ActivatedRoute, useValue: route }, { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, { provide: NotificationsService, useValue: notificationService }, { provide: RequestService, useValue: {} }, @@ -95,4 +99,22 @@ describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { }, }); }); + + it('should navigate to the advanced workflow page with a previousSearchQuery when clicked anq a query is defined', () => { + spyOnProperty(route, 'snapshot').and.returnValue({ + queryParams: { + query: 'Thor%20Love%20and%20Thunder', + } + }); + component.workflowTaskPageRoute = `/workflowitems/${workflowId}/advanced`; + fixture.debugElement.query(By.css('.selectReviewerAction')).nativeElement.click(); + + expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${workflowId}/advanced`], { + queryParams: { + workflow: ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, + claimedTask: taskId, + previousSearchQuery: 'Thor%20Love%20and%20Thunder', + }, + }); + }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts index 7473c737d9..2331a8bd7f 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts @@ -3,7 +3,7 @@ import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-deco import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advanced-claimed-task-actions-abstract.component'; -import { Router } from '@angular/router'; +import { Router, Params, ActivatedRoute } from '@angular/router'; import { NotificationsService } from '../../../notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { SearchService } from '../../../../core/shared/search/search.service'; @@ -12,6 +12,7 @@ import { ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; +import { hasValue } from '../../../empty.util'; /** * Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionSelectReviewerComponent} @@ -38,8 +39,17 @@ export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedCl protected translate: TranslateService, protected searchService: SearchService, protected requestService: RequestService, + protected route: ActivatedRoute, ) { super(injector, router, notificationsService, translate, searchService, requestService); } + getQueryParams(): Params { + const params: Params = super.getQueryParams(); + if (hasValue(this.route.snapshot.queryParams.query)) { + params.previousSearchQuery = this.route.snapshot.queryParams.query; + } + return params; + } + } diff --git a/src/app/shared/testing/active-router.stub.ts b/src/app/shared/testing/active-router.stub.ts index aa4bfce438..86d92a2fb8 100644 --- a/src/app/shared/testing/active-router.stub.ts +++ b/src/app/shared/testing/active-router.stub.ts @@ -54,7 +54,8 @@ export class ActivatedRouteStub { get snapshot() { return { params: this.testParams, - queryParamMap: convertToParamMap(this.testParams) + queryParamMap: convertToParamMap(this.testParams), + queryParams: {} as Params, }; } } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts index 2884dbcc8e..42d6874899 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts @@ -1,12 +1,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { AdvancedWorkflowActionSelectReviewerComponent, - ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER + ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER, } from './advanced-workflow-action-select-reviewer.component'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub'; -import { RouterTestingModule } from '@angular/router/testing'; import { WorkflowActionDataServiceStub } from '../../../shared/testing/workflow-action-data-service.stub'; import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; import { RouteService } from '../../../core/services/route.service'; @@ -25,6 +24,7 @@ import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-res import { NO_ERRORS_SCHEMA } from '@angular/core'; import { RequestService } from '../../../core/data/request.service'; import { RequestServiceStub } from '../../../shared/testing/request-service.stub'; +import { RouterStub } from '../../../shared/testing/router.stub'; const claimedTaskId = '2'; const workflowId = '1'; @@ -37,18 +37,19 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { let claimedTaskDataService: ClaimedTaskDataServiceStub; let notificationService: NotificationsServiceStub; + let router: RouterStub; let workflowActionDataService: WorkflowItemDataServiceStub; let workflowItemDataService: WorkflowItemDataServiceStub; beforeEach(async () => { claimedTaskDataService = new ClaimedTaskDataServiceStub(); notificationService = new NotificationsServiceStub(); + router = new RouterStub(); workflowActionDataService = new WorkflowActionDataServiceStub(); workflowItemDataService = new WorkflowItemDataServiceStub(); await TestBed.configureTestingModule({ imports: [ - RouterTestingModule, TranslateModule.forRoot(), ], declarations: [ @@ -66,10 +67,12 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { queryParams: { claimedTask: claimedTaskId, workflow: 'testaction', + previousSearchQuery: 'Thor%20Love%20and%20Thunder', }, }, }, }, + { provide: Router, useValue: router }, { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, { provide: NotificationsService, useValue: notificationService }, { provide: RouteService, useValue: routeServiceStub }, @@ -85,14 +88,30 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { fixture = TestBed.createComponent(AdvancedWorkflowActionSelectReviewerComponent); component = fixture.componentInstance; fixture.detectChanges(); - spyOn(component, 'previousPage'); }); afterEach(() => { fixture.debugElement.nativeElement.remove(); }); + describe('previousPage', () => { + it('should navigate back to the Workflow tasks page with the previous query', () => { + component.previousPage(); + + expect(router.navigate).toHaveBeenCalledWith(['/mydspace'], { + queryParams: { + configuration: 'workflow', + query: 'Thor Love and Thunder', + }, + }); + }); + }); + describe('performAction', () => { + beforeEach(() => { + spyOn(component, 'previousPage'); + }); + it('should call the claimedTaskDataService with the list of selected ePersons', () => { spyOn(claimedTaskDataService, 'submitTask').and.returnValue(observableOf(new ProcessTaskResponse(true))); component.selectedReviewers = [EPersonMock, EPersonMock2]; diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index b9f90e1736..226ddccf4c 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -12,15 +12,15 @@ import { } from '../../../access-control/group-registry/group-form/members-list/members-list.component'; import { Subscription } from 'rxjs'; import { EPerson } from '../../../core/eperson/models/eperson.model'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute, Params, Router } from '@angular/router'; import { WorkflowItemDataService } from '../../../core/submission/workflowitem-data.service'; import { RouteService } from '../../../core/services/route.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.service'; -import { ReviewersListComponent } from './reviewers-list/reviewers-list.component'; -import { RequestService } from 'src/app/core/data/request.service'; +import { RequestService } from '../../../core/data/request.service'; +import { hasValue } from '../../../shared/empty.util'; export const ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER = 'submit_select_reviewer'; export const ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER = 'selectrevieweraction'; @@ -138,7 +138,13 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf * switching between the different pages */ previousPage(): void { - void this.router.navigate(['/mydspace'], { queryParams: { configuration: 'workflow' } }); + const queryParams: Params = { + configuration: 'workflow', + }; + if (hasValue(this.route.snapshot.queryParams.previousSearchQuery)) { + queryParams.query = decodeURIComponent(this.route.snapshot.queryParams.previousSearchQuery); + } + void this.router.navigate(['/mydspace'], { queryParams: queryParams }); } } From 356962a571663a425916397334e807fe95bbc05a Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 10 Feb 2023 15:52:51 +0100 Subject: [PATCH 136/173] 99053: Pass all queryParameters to advanced workflow pages by default --- ...claimed-task-actions-abstract.component.ts | 29 ++++++++++++++++--- .../claimed-task-actions.component.ts | 1 - ...aimed-task-action-rating.component.spec.ts | 6 +++- ...ed-claimed-task-action-rating.component.ts | 5 ++-- ...k-action-select-reviewer.component.spec.ts | 18 ------------ ...d-task-action-select-reviewer.component.ts | 13 ++------- src/app/shared/testing/location.stub.ts | 7 +++++ ...w-action-select-reviewer.component.spec.ts | 14 ++++++++- ...rkflow-action-select-reviewer.component.ts | 12 ++++---- 9 files changed, 62 insertions(+), 43 deletions(-) create mode 100644 src/app/shared/testing/location.stub.ts diff --git a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts index 32d9a230eb..cf627fc1ce 100644 --- a/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/abstract/advanced-claimed-task-actions-abstract.component.ts @@ -1,9 +1,13 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Injector } from '@angular/core'; import { ClaimedTaskActionsAbstractComponent } from './claimed-task-actions-abstract.component'; import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { getAdvancedWorkflowRoute } from '../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; -import { Params } from '@angular/router'; +import { Params, Router, ActivatedRoute, NavigationExtras } from '@angular/router'; +import { NotificationsService } from '../../../notifications/notifications.service'; +import { TranslateService } from '@ngx-translate/core'; +import { SearchService } from '../../../../core/shared/search/search.service'; +import { RequestService } from '../../../../core/data/request.service'; /** * Abstract component for rendering an advanced claimed task's action @@ -27,6 +31,18 @@ export abstract class AdvancedClaimedTaskActionsAbstractComponent extends Claime */ workflowTaskPageRoute: string; + constructor( + protected injector: Injector, + protected router: Router, + protected notificationsService: NotificationsService, + protected translate: TranslateService, + protected searchService: SearchService, + protected requestService: RequestService, + protected route: ActivatedRoute, + ) { + super(injector, router, notificationsService, translate, searchService, requestService); + } + ngOnInit(): void { super.ngOnInit(); this.initPageRoute(); @@ -47,9 +63,14 @@ export abstract class AdvancedClaimedTaskActionsAbstractComponent extends Claime * Navigates to the advanced workflow page based on the {@link workflow}. */ openAdvancedClaimedTaskTab(): void { - void this.router.navigate([this.workflowTaskPageRoute], { + const navigationExtras: NavigationExtras = { queryParams: this.getQueryParams(), - }); + }; + if (Object.keys(this.route.snapshot.queryParams).length > 0) { + navigationExtras.state = {}; + navigationExtras.state.previousQueryParams = this.route.snapshot.queryParams; + } + void this.router.navigate([this.workflowTaskPageRoute], navigationExtras); } /** diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts index 8f4d109ba1..9f31c34279 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts @@ -16,7 +16,6 @@ import { RequestService } from '../../../core/data/request.service'; import { SearchService } from '../../../core/shared/search/search.service'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; -import { WORKFLOW_TASK_OPTION_RETURN_TO_POOL } from './return-to-pool/claimed-task-actions-return-to-pool.component'; import { getWorkflowItemViewRoute } from '../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; /** diff --git a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts index c192a68aa7..bb41fedfb5 100644 --- a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.spec.ts @@ -9,7 +9,7 @@ import { SearchServiceStub } from '../../../testing/search-service.stub'; import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service'; import { NotificationsService } from '../../../notifications/notifications.service'; import { RequestService } from '../../../../core/data/request.service'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { SearchService } from '../../../../core/shared/search/search.service'; import { Location } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; @@ -19,6 +19,7 @@ import { } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component'; import { of as observableOf } from 'rxjs'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { ActivatedRouteStub } from '../../../testing/active-router.stub'; const taskId = 'claimed-task-1'; const workflowId = 'workflow-1'; @@ -35,12 +36,14 @@ describe('AdvancedClaimedTaskActionRatingComponent', () => { let claimedTaskDataService: ClaimedTaskDataServiceStub; let notificationService: NotificationsServiceStub; + let route: ActivatedRouteStub; let router: RouterStub; let searchService: SearchServiceStub; beforeEach(async () => { claimedTaskDataService = new ClaimedTaskDataServiceStub(); notificationService = new NotificationsServiceStub(); + route = new ActivatedRouteStub(); router = new RouterStub(); searchService = new SearchServiceStub(); @@ -52,6 +55,7 @@ describe('AdvancedClaimedTaskActionRatingComponent', () => { AdvancedClaimedTaskActionRatingComponent, ], providers: [ + { provide: ActivatedRoute, useValue: route }, { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, { provide: NotificationsService, useValue: notificationService }, { provide: RequestService, useValue: {} }, diff --git a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts index 8699dc702e..a1cc81e050 100644 --- a/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/rating/advanced-claimed-task-action-rating.component.ts @@ -1,5 +1,5 @@ import { Component, Injector } from '@angular/core'; -import { Router } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { NotificationsService } from '../../../notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { SearchService } from '../../../../core/shared/search/search.service'; @@ -38,8 +38,9 @@ export class AdvancedClaimedTaskActionRatingComponent extends AdvancedClaimedTas protected translate: TranslateService, protected searchService: SearchService, protected requestService: RequestService, + protected route: ActivatedRoute, ) { - super(injector, router, notificationsService, translate, searchService, requestService); + super(injector, router, notificationsService, translate, searchService, requestService, route); } } diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts index 60f47a60b8..81fe423481 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.spec.ts @@ -99,22 +99,4 @@ describe('AdvancedClaimedTaskActionSelectReviewerComponent', () => { }, }); }); - - it('should navigate to the advanced workflow page with a previousSearchQuery when clicked anq a query is defined', () => { - spyOnProperty(route, 'snapshot').and.returnValue({ - queryParams: { - query: 'Thor%20Love%20and%20Thunder', - } - }); - component.workflowTaskPageRoute = `/workflowitems/${workflowId}/advanced`; - fixture.debugElement.query(By.css('.selectReviewerAction')).nativeElement.click(); - - expect(router.navigate).toHaveBeenCalledWith([`/workflowitems/${workflowId}/advanced`], { - queryParams: { - workflow: ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, - claimedTask: taskId, - previousSearchQuery: 'Thor%20Love%20and%20Thunder', - }, - }); - }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts index 2331a8bd7f..d6217320ba 100644 --- a/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/select-reviewer/advanced-claimed-task-action-select-reviewer.component.ts @@ -3,7 +3,7 @@ import { rendersWorkflowTaskOption } from '../switcher/claimed-task-actions-deco import { AdvancedClaimedTaskActionsAbstractComponent } from '../abstract/advanced-claimed-task-actions-abstract.component'; -import { Router, Params, ActivatedRoute } from '@angular/router'; +import { Router, ActivatedRoute } from '@angular/router'; import { NotificationsService } from '../../../notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { SearchService } from '../../../../core/shared/search/search.service'; @@ -12,7 +12,6 @@ import { ADVANCED_WORKFLOW_ACTION_SELECT_REVIEWER, ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER } from '../../../../workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component'; -import { hasValue } from '../../../empty.util'; /** * Advanced Workflow button that redirect to the {@link AdvancedWorkflowActionSelectReviewerComponent} @@ -41,15 +40,7 @@ export class AdvancedClaimedTaskActionSelectReviewerComponent extends AdvancedCl protected requestService: RequestService, protected route: ActivatedRoute, ) { - super(injector, router, notificationsService, translate, searchService, requestService); - } - - getQueryParams(): Params { - const params: Params = super.getQueryParams(); - if (hasValue(this.route.snapshot.queryParams.query)) { - params.previousSearchQuery = this.route.snapshot.queryParams.query; - } - return params; + super(injector, router, notificationsService, translate, searchService, requestService, route); } } diff --git a/src/app/shared/testing/location.stub.ts b/src/app/shared/testing/location.stub.ts new file mode 100644 index 0000000000..7e130c4de2 --- /dev/null +++ b/src/app/shared/testing/location.stub.ts @@ -0,0 +1,7 @@ +export class LocationStub { + + getState(): unknown { + return {}; + } + +} diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts index 42d6874899..cf2be8e0b8 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Location } from '@angular/common'; import { AdvancedWorkflowActionSelectReviewerComponent, ADVANCED_WORKFLOW_TASK_OPTION_SELECT_REVIEWER, @@ -25,6 +26,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { RequestService } from '../../../core/data/request.service'; import { RequestServiceStub } from '../../../shared/testing/request-service.stub'; import { RouterStub } from '../../../shared/testing/router.stub'; +import { LocationStub } from '../../../shared/testing/location.stub'; const claimedTaskId = '2'; const workflowId = '1'; @@ -36,6 +38,7 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { let fixture: ComponentFixture; let claimedTaskDataService: ClaimedTaskDataServiceStub; + let location: LocationStub; let notificationService: NotificationsServiceStub; let router: RouterStub; let workflowActionDataService: WorkflowItemDataServiceStub; @@ -43,6 +46,7 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { beforeEach(async () => { claimedTaskDataService = new ClaimedTaskDataServiceStub(); + location = new LocationStub(); notificationService = new NotificationsServiceStub(); router = new RouterStub(); workflowActionDataService = new WorkflowActionDataServiceStub(); @@ -72,9 +76,10 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { }, }, }, - { provide: Router, useValue: router }, { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, + { provide: Location, useValue: location }, { provide: NotificationsService, useValue: notificationService }, + { provide: Router, useValue: router }, { provide: RouteService, useValue: routeServiceStub }, { provide: WorkflowActionDataService, useValue: workflowActionDataService }, { provide: WorkflowItemDataService, useValue: workflowItemDataService }, @@ -96,6 +101,13 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { describe('previousPage', () => { it('should navigate back to the Workflow tasks page with the previous query', () => { + spyOn(location, 'getState').and.returnValue({ + previousQueryParams: { + configuration: 'workflow', + query: 'Thor Love and Thunder', + }, + }); + component.previousPage(); expect(router.navigate).toHaveBeenCalledWith(['/mydspace'], { diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index 226ddccf4c..5d8ece8c01 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Location } from '@angular/common'; import { rendersAdvancedWorkflowTaskOption } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; @@ -62,6 +63,7 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf protected workflowActionService: WorkflowActionDataService, protected claimedTaskDataService: ClaimedTaskDataService, protected requestService: RequestService, + protected location: Location, ) { super(route, workflowItemService, router, routeService, notificationsService, translationService, workflowActionService, claimedTaskDataService, requestService); } @@ -138,11 +140,11 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf * switching between the different pages */ previousPage(): void { - const queryParams: Params = { - configuration: 'workflow', - }; - if (hasValue(this.route.snapshot.queryParams.previousSearchQuery)) { - queryParams.query = decodeURIComponent(this.route.snapshot.queryParams.previousSearchQuery); + let queryParams: Params = (this.location.getState() as { [key: string]: any }).previousQueryParams; + if (!hasValue(queryParams)) { + queryParams = { + configuration: 'workflow', + }; } void this.router.navigate(['/mydspace'], { queryParams: queryParams }); } From dce84dbe70d4ea200a3c38576008792e013b165f Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 10 Feb 2023 16:39:27 +0100 Subject: [PATCH 137/173] 99053: Save queryParams in init to prevent them being overwritten --- .../advanced-workflow-action-rating.component.spec.ts | 3 +++ ...anced-workflow-action-select-reviewer.component.spec.ts | 1 + .../advanced-workflow-action-select-reviewer.component.ts | 4 ++-- .../advanced-workflow-action.component.spec.ts | 6 +++++- .../advanced-workflow-action.component.ts | 4 +++- .../workflow-item-action-page.component.spec.ts | 6 +++++- .../workflow-item-action-page.component.ts | 7 ++++++- .../workflow-item-delete.component.spec.ts | 4 +++- .../workflow-item-delete/workflow-item-delete.component.ts | 7 +++++-- .../workflow-item-send-back.component.spec.ts | 4 +++- .../workflow-item-send-back.component.ts | 7 +++++-- 11 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts index 8fe495c617..d6d6f973c4 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-rating/advanced-workflow-action-rating.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Location } from '@angular/common'; import { AdvancedWorkflowActionRatingComponent, ADVANCED_WORKFLOW_TASK_OPTION_RATING @@ -28,6 +29,7 @@ import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-res import { RatingAdvancedWorkflowInfo } from '../../../core/tasks/models/rating-advanced-workflow-info.model'; import { RequestService } from '../../../core/data/request.service'; import { RequestServiceStub } from '../../../shared/testing/request-service.stub'; +import { LocationStub } from '../../../shared/testing/location.stub'; const claimedTaskId = '2'; const workflowId = '1'; @@ -77,6 +79,7 @@ describe('AdvancedWorkflowActionRatingComponent', () => { }, }, { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, + { provide: Location, useValue: new LocationStub() }, { provide: NotificationsService, useValue: notificationService }, { provide: RouteService, useValue: routeServiceStub }, { provide: Router, useValue: new RouterStub() }, diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts index cf2be8e0b8..71952ce9c7 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.spec.ts @@ -108,6 +108,7 @@ describe('AdvancedWorkflowActionSelectReviewerComponent', () => { }, }); + component.ngOnInit(); component.previousPage(); expect(router.navigate).toHaveBeenCalledWith(['/mydspace'], { diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts index 5d8ece8c01..329af73351 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action-select-reviewer/advanced-workflow-action-select-reviewer.component.ts @@ -65,7 +65,7 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf protected requestService: RequestService, protected location: Location, ) { - super(route, workflowItemService, router, routeService, notificationsService, translationService, workflowActionService, claimedTaskDataService, requestService); + super(route, workflowItemService, router, routeService, notificationsService, translationService, workflowActionService, claimedTaskDataService, requestService, location); } ngOnDestroy(): void { @@ -140,7 +140,7 @@ export class AdvancedWorkflowActionSelectReviewerComponent extends AdvancedWorkf * switching between the different pages */ previousPage(): void { - let queryParams: Params = (this.location.getState() as { [key: string]: any }).previousQueryParams; + let queryParams: Params = this.previousQueryParameters; if (!hasValue(queryParams)) { queryParams = { configuration: 'workflow', diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts index f57adcb05f..f6dfbf627f 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Location } from '@angular/common'; import { AdvancedWorkflowActionComponent } from './advanced-workflow-action.component'; import { Component } from '@angular/core'; import { MockComponent } from 'ng-mocks'; @@ -20,6 +21,7 @@ import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-res import { WorkflowItemDataServiceStub } from '../../../shared/testing/workflow-item-data-service.stub'; import { RequestService } from '../../../core/data/request.service'; import { RequestServiceStub } from '../../../shared/testing/request-service.stub'; +import { LocationStub } from '../../../shared/testing/location.stub'; const workflowId = '1'; @@ -28,17 +30,18 @@ describe('AdvancedWorkflowActionComponent', () => { let fixture: ComponentFixture; let claimedTaskDataService: ClaimedTaskDataServiceStub; + let location: LocationStub; let notificationService: NotificationsServiceStub; let workflowActionDataService: WorkflowActionDataServiceStub; let workflowItemDataService: WorkflowItemDataServiceStub; beforeEach(async () => { claimedTaskDataService = new ClaimedTaskDataServiceStub(); + location = new LocationStub(); notificationService = new NotificationsServiceStub(); workflowActionDataService = new WorkflowActionDataServiceStub(); workflowItemDataService = new WorkflowItemDataServiceStub(); - await TestBed.configureTestingModule({ imports: [ TranslateModule.forRoot(), @@ -63,6 +66,7 @@ describe('AdvancedWorkflowActionComponent', () => { }, }, { provide: ClaimedTaskDataService, useValue: claimedTaskDataService }, + { provide: Location, useValue: location }, { provide: NotificationsService, useValue: notificationService }, { provide: RouteService, useValue: routeServiceStub }, { provide: WorkflowActionDataService, useValue: workflowActionDataService }, diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts index 5621e56578..73fd6dc63e 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-action/advanced-workflow-action.component.ts @@ -13,6 +13,7 @@ import { ClaimedTaskDataService } from '../../../core/tasks/claimed-task-data.se import { map } from 'rxjs/operators'; import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; import { RequestService } from '../../../core/data/request.service'; +import { Location } from '@angular/common'; /** * Abstract component for rendering an advanced claimed task's workflow page @@ -38,8 +39,9 @@ export abstract class AdvancedWorkflowActionComponent extends WorkflowItemAction protected workflowActionService: WorkflowActionDataService, protected claimedTaskDataService: ClaimedTaskDataService, protected requestService: RequestService, + protected location: Location, ) { - super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService); + super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService, location); } ngOnInit(): void { diff --git a/src/app/workflowitems-edit-page/workflow-item-action-page.component.spec.ts b/src/app/workflowitems-edit-page/workflow-item-action-page.component.spec.ts index 11f88788dc..c4dea0f30c 100644 --- a/src/app/workflowitems-edit-page/workflow-item-action-page.component.spec.ts +++ b/src/app/workflowitems-edit-page/workflow-item-action-page.component.spec.ts @@ -18,6 +18,8 @@ import { RouterStub } from '../shared/testing/router.stub'; import { NotificationsServiceStub } from '../shared/testing/notifications-service.stub'; import { RequestService } from '../core/data/request.service'; import { RequestServiceStub } from '../shared/testing/request-service.stub'; +import { Location } from '@angular/common'; +import { LocationStub } from '../shared/testing/location.stub'; const type = 'testType'; describe('WorkflowItemActionPageComponent', () => { @@ -52,6 +54,7 @@ describe('WorkflowItemActionPageComponent', () => { { provide: ActivatedRoute, useValue: new ActivatedRouteStub({}, { wfi: createSuccessfulRemoteDataObject(wfi) }) }, { provide: Router, useClass: RouterStub }, { provide: RouteService, useValue: {} }, + { provide: Location, useValue: new LocationStub() }, { provide: NotificationsService, useClass: NotificationsServiceStub }, { provide: WorkflowItemDataService, useValue: wfiService }, { provide: RequestService, useClass: RequestServiceStub }, @@ -115,8 +118,9 @@ class TestComponent extends WorkflowItemActionPageComponent { protected notificationsService: NotificationsService, protected translationService: TranslateService, protected requestService: RequestService, + protected location: Location, ) { - super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService); + super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService, location); } getType(): string { diff --git a/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts b/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts index 276b2054fe..b8998a6dd7 100644 --- a/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts +++ b/src/app/workflowitems-edit-page/workflow-item-action-page.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { Location } from '@angular/common'; import { Observable, forkJoin } from 'rxjs'; import { map, switchMap, take } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; @@ -24,6 +25,7 @@ export abstract class WorkflowItemActionPageComponent implements OnInit { public type; public wfi$: Observable; public item$: Observable; + protected previousQueryParameters?: Params; constructor(protected route: ActivatedRoute, protected workflowItemService: WorkflowItemDataService, @@ -32,6 +34,7 @@ export abstract class WorkflowItemActionPageComponent implements OnInit { protected notificationsService: NotificationsService, protected translationService: TranslateService, protected requestService: RequestService, + protected location: Location, ) { } @@ -42,6 +45,7 @@ export abstract class WorkflowItemActionPageComponent implements OnInit { this.type = this.getType(); this.wfi$ = this.route.data.pipe(map((data: Data) => data.wfi as RemoteData), getRemoteDataPayload()); this.item$ = this.wfi$.pipe(switchMap((wfi: WorkflowItem) => (wfi.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload()))); + this.previousQueryParameters = (this.location.getState() as { [key: string]: any }).previousQueryParams; } /** @@ -72,10 +76,11 @@ export abstract class WorkflowItemActionPageComponent implements OnInit { previousPage() { this.routeService.getPreviousUrl().pipe(take(1)) .subscribe((url) => { + let params: Params = {}; if (isEmpty(url)) { url = '/mydspace'; + params = this.previousQueryParameters; } - const params: Params = {}; if (url.split('?').length > 1) { for (const param of url.split('?')[1].split('&')) { params[param.split('=')[0]] = decodeURIComponent(param.split('=')[1]); diff --git a/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.spec.ts b/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.spec.ts index a4e8f7d849..89a7029b4a 100644 --- a/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.spec.ts +++ b/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.spec.ts @@ -1,5 +1,5 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; - +import { Location } from '@angular/common'; import { WorkflowItemDeleteComponent } from './workflow-item-delete.component'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { ActivatedRoute, Router } from '@angular/router'; @@ -17,6 +17,7 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { RouterStub } from '../../shared/testing/router.stub'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; import { getMockRequestService } from '../../shared/mocks/request.service.mock'; +import { LocationStub } from '../../shared/testing/location.stub'; describe('WorkflowItemDeleteComponent', () => { let component: WorkflowItemDeleteComponent; @@ -50,6 +51,7 @@ describe('WorkflowItemDeleteComponent', () => { { provide: ActivatedRoute, useValue: new ActivatedRouteStub({}, { wfi: createSuccessfulRemoteDataObject(wfi) }) }, { provide: Router, useClass: RouterStub }, { provide: RouteService, useValue: {} }, + { provide: Location, useValue: new LocationStub() }, { provide: NotificationsService, useClass: NotificationsServiceStub }, { provide: WorkflowItemDataService, useValue: wfiService }, { provide: RequestService, useValue: getMockRequestService() }, diff --git a/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.ts b/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.ts index faa2bf543a..011398369d 100644 --- a/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.ts +++ b/src/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component.ts @@ -11,6 +11,7 @@ import { map } from 'rxjs/operators'; import { RemoteData } from '../../core/data/remote-data'; import { NoContent } from '../../core/shared/NoContent.model'; import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { Location } from '@angular/common'; @Component({ selector: 'ds-workflow-item-delete', @@ -26,8 +27,10 @@ export class WorkflowItemDeleteComponent extends WorkflowItemActionPageComponent protected routeService: RouteService, protected notificationsService: NotificationsService, protected translationService: TranslateService, - protected requestService: RequestService) { - super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService); + protected requestService: RequestService, + protected location: Location, + ) { + super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService, location); } /** diff --git a/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.spec.ts b/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.spec.ts index d9dde8244c..1196e05593 100644 --- a/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.spec.ts +++ b/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.spec.ts @@ -1,5 +1,5 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; - +import { Location } from '@angular/common'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { ActivatedRoute, Router } from '@angular/router'; import { RouteService } from '../../core/services/route.service'; @@ -17,6 +17,7 @@ import { RouterStub } from '../../shared/testing/router.stub'; import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; import { getMockRequestService } from '../../shared/mocks/request.service.mock'; +import { LocationStub } from '../../shared/testing/location.stub'; describe('WorkflowItemSendBackComponent', () => { let component: WorkflowItemSendBackComponent; @@ -50,6 +51,7 @@ describe('WorkflowItemSendBackComponent', () => { { provide: ActivatedRoute, useValue: new ActivatedRouteStub({}, { wfi: createSuccessfulRemoteDataObject(wfi) }) }, { provide: Router, useClass: RouterStub }, { provide: RouteService, useValue: {} }, + { provide: Location, useValue: new LocationStub() }, { provide: NotificationsService, useClass: NotificationsServiceStub }, { provide: WorkflowItemDataService, useValue: wfiService }, { provide: RequestService, useValue: getMockRequestService() }, diff --git a/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts b/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts index 432b417f43..a3c03bcfb1 100644 --- a/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts +++ b/src/app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component.ts @@ -7,6 +7,7 @@ import { RouteService } from '../../core/services/route.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { RequestService } from '../../core/data/request.service'; +import { Location } from '@angular/common'; @Component({ selector: 'ds-workflow-item-send-back', @@ -22,8 +23,10 @@ export class WorkflowItemSendBackComponent extends WorkflowItemActionPageCompone protected routeService: RouteService, protected notificationsService: NotificationsService, protected translationService: TranslateService, - protected requestService: RequestService) { - super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService); + protected requestService: RequestService, + protected location: Location, + ) { + super(route, workflowItemService, router, routeService, notificationsService, translationService, requestService, location); } /** From 1d69e1581adbffd53b01b1c7342669b383273242 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 10 Feb 2023 17:45:59 +0100 Subject: [PATCH 138/173] 97755: Call spy before component is created --- src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts index 65026c1504..fe8e4187d3 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.spec.ts @@ -55,6 +55,7 @@ describe('AdminSidebarComponent', () => { isAuthorized: observableOf(true) }); scriptService = jasmine.createSpyObj('scriptService', { scriptWithNameExistsAndCanExecute: observableOf(true) }); + spyOn(menuService, 'addSection').and.callThrough(); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule], declarations: [AdminSidebarComponent], @@ -184,10 +185,6 @@ describe('AdminSidebarComponent', () => { }); describe('menu', () => { - beforeEach(() => { - spyOn(menuService, 'addSection'); - }); - describe('for regular user', () => { beforeEach(() => { authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake(() => { From 8ddcd42d04f9a6afa0c84c2bc8576f9cd3588e51 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Feb 2023 18:14:15 +0000 Subject: [PATCH 139/173] Bump http-cache-semantics from 4.1.0 to 4.1.1 Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/kornelski/http-cache-semantics/releases) - [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: http-cache-semantics 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 3843ac4dab..2fd4fa3bc8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6140,9 +6140,9 @@ htmlparser2@^6.0.0, htmlparser2@^6.1.0: entities "^2.0.0" http-cache-semantics@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" - integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + version "4.1.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== http-deceiver@^1.2.7: version "1.2.7" From d82526af8abd4d5c6be501fa045f509326015e8b Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Fri, 10 Feb 2023 19:47:20 +0100 Subject: [PATCH 140/173] [CST-7755] WIP refactoring --- ...admin-workflow-list-element.component.html | 6 +- ...t-admin-workflow-list-element.component.ts | 127 ++++++++++++++++-- .../models/submission-object.model.ts | 4 +- ...table-object-component-loader.component.ts | 14 +- .../abstract-listable-element.component.ts | 5 + .../item/item-list-element.component.html | 2 +- .../object-list/object-list.component.html | 7 +- .../object-list/object-list.component.ts | 5 + ...-search-result-list-element.component.html | 37 ++--- ...em-search-result-list-element.component.ts | 14 +- .../supervision-order-status.component.html | 13 ++ .../supervision-order-status.component.scss | 0 ...supervision-order-status.component.spec.ts | 25 ++++ .../supervision-order-status.component.ts | 88 ++++++++++++ src/app/shared/shared.module.ts | 42 +++--- src/assets/i18n/en.json5 | 16 +-- 16 files changed, 330 insertions(+), 75 deletions(-) create mode 100644 src/app/shared/object-list/supervision-order-status/supervision-order-status.component.html create mode 100644 src/app/shared/object-list/supervision-order-status/supervision-order-status.component.scss create mode 100644 src/app/shared/object-list/supervision-order-status/supervision-order-status.component.spec.ts create mode 100644 src/app/shared/object-list/supervision-order-status/supervision-order-status.component.ts diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.html b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.html index 32ba7b9cc8..8963db01bd 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.html +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.html @@ -6,9 +6,13 @@ +
+ +
+ diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.ts index ee6cb16bbe..c57d918126 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-list-element/workflow-item/workflow-item-search-result-admin-workflow-list-element.component.ts @@ -1,21 +1,46 @@ import { Component, Inject, OnInit } from '@angular/core'; + +import { BehaviorSubject, Observable } from 'rxjs'; +import { map, mergeMap, take, tap } from 'rxjs/operators'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateService } from '@ngx-translate/core'; + import { ViewMode } from '../../../../../core/shared/view-mode.model'; -import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { + listableObjectComponent +} from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator'; import { Context } from '../../../../../core/shared/context.model'; import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model'; -import { Observable } from 'rxjs'; import { LinkService } from '../../../../../core/cache/builders/link.service'; import { followLink } from '../../../../../shared/utils/follow-link-config.model'; import { RemoteData } from '../../../../../core/data/remote-data'; -import { getAllSucceededRemoteData, getRemoteDataPayload } from '../../../../../core/shared/operators'; +import { + getAllSucceededRemoteData, + getFirstCompletedRemoteData, + getRemoteDataPayload +} from '../../../../../core/shared/operators'; import { Item } from '../../../../../core/shared/item.model'; -import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; +import { + SearchResultListElementComponent +} from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component'; import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; -import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; +import { + WorkflowItemSearchResult +} from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface'; -import { WorkspaceItemSearchResult } from '../../../../../shared/object-collection/shared/workspace-item-search-result.model'; +import { + WorkspaceItemSearchResult +} from '../../../../../shared/object-collection/shared/workspace-item-search-result.model'; import { SupervisionOrder } from '../../../../../core/supervision-order/models/supervision-order.model'; +import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; +import { PaginatedList } from '../../../../../core/data/paginated-list.model'; +import { ConfirmationModalComponent } from '../../../../../shared/confirmation-modal/confirmation-modal.component'; +import { hasValue } from '../../../../../shared/empty.util'; +import { + SupervisionOrderListEntry +} from '../../../../../shared/object-list/supervision-order-status/supervision-order-status.component'; +import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; @listableObjectComponent(WorkflowItemSearchResult, ViewMode.ListElement, Context.AdminWorkflowSearch) @listableObjectComponent(WorkspaceItemSearchResult, ViewMode.ListElement, Context.AdminWorkflowSearch) @@ -25,7 +50,7 @@ import { SupervisionOrder } from '../../../../../core/supervision-order/models/s templateUrl: './workflow-item-search-result-admin-workflow-list-element.component.html' }) /** - * The component for displaying a list element for an workflow item on the admin workflow search page + * The component for displaying a list element for a workflow item on the admin workflow search page */ export class WorkflowItemSearchResultAdminWorkflowListElementComponent extends SearchResultListElementComponent implements OnInit { @@ -34,14 +59,25 @@ export class WorkflowItemSearchResultAdminWorkflowListElementComponent extends S */ public item$: Observable; + /** + * The id of the item linked to the workflow item + */ + public itemId: string; + /** * The supervision orders linked to the workflow item */ - public supervisionOrder$: Observable; + public supervisionOrder$: BehaviorSubject = new BehaviorSubject([]); + + private messagePrefix = 'workflow-item.search.result'; constructor(private linkService: LinkService, - protected truncatableService: TruncatableService, protected dsoNameService: DSONameService, + protected modalService: NgbModal, + protected notificationsService: NotificationsService, + protected supervisionOrderDataService: SupervisionOrderDataService, + protected translateService: TranslateService, + protected truncatableService: TruncatableService, @Inject(APP_CONFIG) protected appConfig: AppConfig ) { super(truncatableService, dsoNameService, appConfig); @@ -53,7 +89,78 @@ export class WorkflowItemSearchResultAdminWorkflowListElementComponent extends S ngOnInit(): void { super.ngOnInit(); this.dso = this.linkService.resolveLink(this.dso, followLink('item')); - this.supervisionOrder$ = (this.dso.supervisionOrders as Observable>)?.pipe(getAllSucceededRemoteData(), getRemoteDataPayload()); this.item$ = (this.dso.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload()); + + this.item$.pipe( + take(1), + tap((item: Item) => this.itemId = item.id), + mergeMap((item: Item) => this.retrieveSupervisorOrders(item.id)) + ).subscribe((supervisionOrderList: SupervisionOrder[]) => { + this.supervisionOrder$.next(supervisionOrderList); + }) + } + + /** + * Deletes the Group from the Repository. The Group will be the only that this form is showing. + * It'll either show a success or error message depending on whether the delete was successful or not. + */ + deleteSupervisionOrder(supervisionOrderEntry: SupervisionOrderListEntry) { + const modalRef = this.modalService.open(ConfirmationModalComponent); + modalRef.componentInstance.dso = supervisionOrderEntry.group; + modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-supervision.modal.header'; + modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-supervision.modal.info'; + modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-supervision.modal.cancel'; + modalRef.componentInstance.confirmLabel = this.messagePrefix + '.delete-supervision.modal.confirm'; + modalRef.componentInstance.brandColor = 'danger'; + modalRef.componentInstance.confirmIcon = 'fas fa-trash'; + modalRef.componentInstance.response.pipe( + take(1), + mergeMap((confirm: boolean) => { + if (confirm && hasValue(supervisionOrderEntry.supervisionOrder.id)) { + return this.supervisionOrderDataService.delete(supervisionOrderEntry.supervisionOrder.id).pipe( + take(1), + tap((result: boolean) => { + if (result) { + this.notificationsService.success( + null, + this.translateService.get( + this.messagePrefix + '.notification.deleted.success', + { name: this.dsoNameService.getName(supervisionOrderEntry.group) } + ) + ); + } else { + this.notificationsService.error( + null, + this.translateService.get( + this.messagePrefix + '.notification.deleted.failure', + { name: this.dsoNameService.getName(supervisionOrderEntry.group) } + ) + ); + } + }), + mergeMap((result: boolean) => result ? this.retrieveSupervisorOrders(this.itemId) : this.supervisionOrder$.asObservable()) + ) + } else { + return this.supervisionOrder$.asObservable() + } + }) + ).subscribe((supervisionOrderList: SupervisionOrder[]) => { + this.supervisionOrder$.next(supervisionOrderList) + }) + } + + /** + * Retrieve the list of SupervisionOrder object related to the given item + * + * @param itemId + * @private + */ + private retrieveSupervisorOrders(itemId): Observable { + return this.supervisionOrderDataService.searchByItem( + itemId, false, true, followLink('group') + ).pipe( + getFirstCompletedRemoteData(), + map((soRD: RemoteData>) => soRD.hasSucceeded && !soRD.hasNoContent ? soRD.payload.page : []) + ); } } diff --git a/src/app/core/submission/models/submission-object.model.ts b/src/app/core/submission/models/submission-object.model.ts index 3d373d2522..e2d1a4396b 100644 --- a/src/app/core/submission/models/submission-object.model.ts +++ b/src/app/core/submission/models/submission-object.model.ts @@ -15,6 +15,8 @@ import { excludeFromEquals } from '../../utilities/equals.decorators'; import { WorkspaceitemSectionsObject } from './workspaceitem-sections.model'; import { CacheableObject } from '../../cache/cacheable-object.model'; import { SUPERVISION_ORDER } from '../../supervision-order/models/supervision-order.resource-type'; +import { PaginatedList } from '../../data/paginated-list.model'; +import { SupervisionOrder } from '../../supervision-order/models/supervision-order.model'; export interface SubmissionObjectError { message: string; @@ -101,6 +103,6 @@ export abstract class SubmissionObject extends DSpaceObject implements Cacheable */ @link(SUPERVISION_ORDER) /* This was changed from 'Observable> | WorkspaceItem' to 'any' to prevent issues in templates with async */ - supervisionOrders?: any; + supervisionOrders?: Observable>>; } diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 9779ecd830..59ce71ff6c 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -13,7 +13,7 @@ import { ViewChild } from '@angular/core'; -import { Observable, Subscription } from 'rxjs'; +import { Subscription } from 'rxjs'; import { take } from 'rxjs/operators'; import { ListableObject } from '../listable-object.model'; @@ -41,11 +41,6 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges */ @Input() object: ListableObject; - /** - * The supervision order to determine supervision orders - */ - @Input() supervisionOrders: Observable; - /** * The index of the object in the list */ @@ -76,6 +71,11 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges */ @Input() showLabel = true; + /** + * Whether to show the supervision orders badges or not + */ + @Input() showSupervisionOrderBadges = false; + /** * The value to display for this element */ @@ -128,7 +128,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges */ protected inAndOutputNames: string[] = [ 'object', - 'supervisionOrders', + 'showSupervisionOrderBadges', 'index', 'linkType', 'listID', diff --git a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts index dde9cf61dc..f292ac5b76 100644 --- a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts +++ b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts @@ -46,6 +46,11 @@ export class AbstractListableElementComponent { */ @Input() showLabel = true; + /** + * Whether to show the supervision orders badges or not + */ + @Input() showSupervisionOrderBadges = false; + /** * The context we matched on to get this component */ diff --git a/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.html b/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.html index f244da97bd..4bf45e7f3b 100644 --- a/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.html +++ b/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.html @@ -1 +1 @@ - + diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index 863d328a69..a0ef3e1cb9 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -25,8 +25,13 @@ - diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index 65e2b508da..9d064115e6 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -81,6 +81,11 @@ export class ObjectListComponent { */ @Input() showPaginator = true; + /** + * Whether to show the supervision orders badges or not + */ + @Input() showSupervisionOrderBadges = false; + /** * Emit when one of the listed object has changed. */ diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html index 64108681f6..bf1d51bfc2 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.html @@ -23,37 +23,24 @@ - - - ( - ) - - - - - ; + + + ( + ) + + + + + ; + - - - + +
- -
-
- {{'item.search.result.list.element.supervised-by' | translate}} -
-
- - {{supervisionOrder.group._name}} - X - -
-
-
diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts index 2fa72d2ac0..7ed96fdc68 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts @@ -1,5 +1,7 @@ import { Component, Inject } from '@angular/core'; -import { listableObjectComponent } from '../../../../../object-collection/shared/listable-object/listable-object.decorator'; +import { + listableObjectComponent +} from '../../../../../object-collection/shared/listable-object/listable-object.decorator'; import { ViewMode } from '../../../../../../core/shared/view-mode.model'; import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model'; import { SearchResultListElementComponent } from '../../../search-result-list-element.component'; @@ -8,7 +10,7 @@ import { getItemPageRoute } from '../../../../../../item-page/item-page-routing- import { SupervisionOrderDataService } from '../../../../../../core/supervision-order/supervision-order-data.service'; import { TruncatableService } from '../../../../../../shared/truncatable/truncatable.service'; import { DSONameService } from '../../../../../../core/breadcrumbs/dso-name.service'; -import { AppConfig, APP_CONFIG } from '../../../../../../../config/app-config.interface'; +import { APP_CONFIG, AppConfig } from '../../../../../../../config/app-config.interface'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { combineLatest, filter, map, Observable, switchMap, take } from 'rxjs'; import { ConfirmationModalComponent } from '../../../../../../shared/confirmation-modal/confirmation-modal.component'; @@ -16,11 +18,15 @@ import { hasValue } from '../../../../../../shared/empty.util'; import { NotificationsService } from '../../../../../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; import { followLink } from '../../../../../../shared/utils/follow-link-config.model'; -import { getAllSucceededRemoteListPayload, getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators'; +import { + getAllSucceededRemoteData, + getAllSucceededRemoteListPayload, + getFirstSucceededRemoteDataPayload, + getRemoteDataPayload +} from '../../../../../../core/shared/operators'; import { SupervisionOrder } from '../../../../../../core/supervision-order/models/supervision-order.model'; import { Group } from '../../../../../../core/eperson/models/group.model'; import { ResourcePolicyDataService } from '../../../../../../core/resource-policy/resource-policy-data.service'; -import { getAllSucceededRemoteData, getRemoteDataPayload } from '../../../../../../core/shared/operators'; import { AuthService } from '../../../../../../core/auth/auth.service'; import { EPerson } from '../../../../../../core/eperson/models/eperson.model'; import { EPersonDataService } from '../../../../../../core/eperson/eperson-data.service'; diff --git a/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.html b/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.html new file mode 100644 index 0000000000..2e4f8d39cd --- /dev/null +++ b/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.html @@ -0,0 +1,13 @@ + +
+
+ {{'workflow-item.search.result.list.element.supervised-by' | translate}} +
+
+ + {{supervisionOrder.group.name}} + X + +
+
+
diff --git a/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.scss b/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.spec.ts b/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.spec.ts new file mode 100644 index 0000000000..9a5516b8e9 --- /dev/null +++ b/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SupervisionOrderStatusComponent } from './supervision-order-status.component'; + +describe('SupervisionOrderStatusComponent', () => { + let component: SupervisionOrderStatusComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SupervisionOrderStatusComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SupervisionOrderStatusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.ts b/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.ts new file mode 100644 index 0000000000..92cbf17885 --- /dev/null +++ b/src/app/shared/object-list/supervision-order-status/supervision-order-status.component.ts @@ -0,0 +1,88 @@ +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; + +import { BehaviorSubject, from, Observable } from 'rxjs'; +import { map, mergeMap, reduce } from 'rxjs/operators'; + +import { SupervisionOrder } from '../../../core/supervision-order/models/supervision-order.model'; +import { Group } from '../../../core/eperson/models/group.model'; +import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; +import { isNotEmpty } from '../../empty.util'; +import { RemoteData } from '../../../core/data/remote-data'; + +export interface SupervisionOrderListEntry { + supervisionOrder: SupervisionOrder; + group: Group +} + +@Component({ + selector: 'ds-supervision-order-status', + templateUrl: './supervision-order-status.component.html', + styleUrls: ['./supervision-order-status.component.scss'] +}) +export class SupervisionOrderStatusComponent implements OnChanges { + + /** + * The list of supervision order object to show + */ + @Input() supervisionOrderList: SupervisionOrder[] = []; + + /** + * The groups the user belongs to + */ + groups: Group[]; + + /** + * List of the supervision orders combined with the group + */ + supervisionOrderEntries$: BehaviorSubject = new BehaviorSubject([]); + + @Output() delete: EventEmitter = new EventEmitter(); + + ngOnChanges(changes: SimpleChanges): void { + if (changes && changes.supervisionOrderList) { + this.getSupervisionOrderEntries(changes.supervisionOrderList.currentValue) + .subscribe((supervisionOrderEntries: SupervisionOrderListEntry[]) => { + this.supervisionOrderEntries$.next(supervisionOrderEntries) + }) + } + console.log('ngOnChanges', changes); + } + + /** + * Create a list of SupervisionOrderListEntry by the given SupervisionOrder list + * + * @param supervisionOrderList + */ + private getSupervisionOrderEntries(supervisionOrderList: SupervisionOrder[]): Observable { + return from(supervisionOrderList).pipe( + mergeMap((so: SupervisionOrder) => so.group.pipe( + getFirstCompletedRemoteData(), + map((sogRD: RemoteData) => { + if (sogRD.hasSucceeded) { + const entry: SupervisionOrderListEntry = { + supervisionOrder: so, + group: sogRD.payload + }; + return entry; + } else { + return null; + } + }) + )), + reduce((acc: SupervisionOrderListEntry[], value: any) => { + if (isNotEmpty(value)) { + return [...acc, value] + } else { + return acc; + } + }, []), + ) + } + + /** + * Emit a delete event with the given SupervisionOrderListEntry. + */ + deleteSupervisionOrder(supervisionOrder: SupervisionOrderListEntry) { + this.delete.emit(supervisionOrder); + } +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 3c985d46b9..624d519ad1 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -100,19 +100,19 @@ import { CreateCommunityParentSelectorComponent } from './dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component'; import { - ThemedCreateCommunityParentSelectorComponent + ThemedCreateCommunityParentSelectorComponent } from './dso-selector/modal-wrappers/create-community-parent-selector/themed-create-community-parent-selector.component'; import { CreateItemParentSelectorComponent } from './dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component'; import { - ThemedCreateItemParentSelectorComponent + ThemedCreateItemParentSelectorComponent } from './dso-selector/modal-wrappers/create-item-parent-selector/themed-create-item-parent-selector.component'; import { CreateCollectionParentSelectorComponent } from './dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component'; import { - ThemedCreateCollectionParentSelectorComponent + ThemedCreateCollectionParentSelectorComponent } from './dso-selector/modal-wrappers/create-collection-parent-selector/themed-create-collection-parent-selector.component'; import { CommunitySearchResultListElementComponent @@ -124,19 +124,19 @@ import { EditItemSelectorComponent } from './dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component'; import { - ThemedEditItemSelectorComponent + ThemedEditItemSelectorComponent } from './dso-selector/modal-wrappers/edit-item-selector/themed-edit-item-selector.component'; import { EditCommunitySelectorComponent } from './dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component'; import { - ThemedEditCommunitySelectorComponent + ThemedEditCommunitySelectorComponent } from './dso-selector/modal-wrappers/edit-community-selector/themed-edit-community-selector.component'; import { EditCollectionSelectorComponent } from './dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component'; import { - ThemedEditCollectionSelectorComponent + ThemedEditCollectionSelectorComponent } from './dso-selector/modal-wrappers/edit-collection-selector/themed-edit-collection-selector.component'; import { RoleDirective } from './roles/role.directive'; import { UserMenuComponent } from './auth-nav-menu/user-menu/user-menu.component'; @@ -242,14 +242,20 @@ import { MenuModule } from './menu/menu.module'; import { ListableNotificationObjectComponent } from './object-list/listable-notification-object/listable-notification-object.component'; -import { SupervisionGroupSelectorComponent } from './dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component'; +import { + SupervisionGroupSelectorComponent +} from './dso-selector/modal-wrappers/supervision-group-selector/supervision-group-selector.component'; import { EpersonGroupListComponent } from './form/eperson-group-list/eperson-group-list.component'; import { EpersonSearchBoxComponent } from './form/eperson-group-list/eperson-search-box/eperson-search-box.component'; import { GroupSearchBoxComponent } from './form/eperson-group-list/group-search-box/group-search-box.component'; import { ThemedCollectionDropdownComponent } from './collection-dropdown/themed-collection-dropdown.component'; import { MetadataFieldWrapperComponent } from './metadata-field-wrapper/metadata-field-wrapper.component'; -import { LogInExternalProviderComponent } from './log-in/methods/log-in-external-provider/log-in-external-provider.component'; - +import { + LogInExternalProviderComponent +} from './log-in/methods/log-in-external-provider/log-in-external-provider.component'; +import { + SupervisionOrderStatusComponent +} from './object-list/supervision-order-status/supervision-order-status.component'; const MODULES = [ @@ -452,18 +458,20 @@ const DIRECTIVES = [ ...COMPONENTS, ...ENTRY_COMPONENTS, ...DIRECTIVES, + SupervisionOrderStatusComponent, ], providers: [ ...PROVIDERS ], - exports: [ - ...MODULES, - ...PIPES, - ...COMPONENTS, - ...ENTRY_COMPONENTS, - ...DIRECTIVES, - TranslateModule, - ] + exports: [ + ...MODULES, + ...PIPES, + ...COMPONENTS, + ...ENTRY_COMPONENTS, + ...DIRECTIVES, + TranslateModule, + SupervisionOrderStatusComponent, + ] }) /** diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 47fbbaf518..892f686fb8 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2271,21 +2271,21 @@ "item.truncatable-part.show-less": "Collapse", - "item.search.result.delete-supervision.modal.header": "Delete Supervision Order", + "workflow-item.search.result.delete-supervision.modal.header": "Delete Supervision Order", - "item.search.result.delete-supervision.modal.info": "Are you sure you want to delete Supervision Order", + "workflow-item.search.result.delete-supervision.modal.info": "Are you sure you want to delete Supervision Order", - "item.search.result.delete-supervision.modal.cancel": "Cancel", + "workflow-item.search.result.delete-supervision.modal.cancel": "Cancel", - "item.search.result.delete-supervision.modal.confirm": "Delete", + "workflow-item.search.result.delete-supervision.modal.confirm": "Delete", - "item.search.result.notification.deleted.success": "Successfully deleted supervision order \"{{name}}\"", + "workflow-item.search.result.notification.deleted.success": "Successfully deleted supervision order \"{{name}}\"", - "item.search.result.notification.deleted.failure.title": "Failed to delete supervision order \"{{name}}\"", + "workflow-item.search.result.notification.deleted.failure": "Failed to delete supervision order \"{{name}}\"", - "item.search.result.notification.deleted.failure.content": "Failed to delete supervision order", + "workflow-item.search.result.list.element.supervised-by": "Supervised by:", - "item.search.result.list.element.supervised-by": "Supervised by:", + "workflow-item.search.result.list.element.supervised.remove-tooltip": "Remove supervision group", From 851fe7869ed5c47089e0430e82542c4d08a89477 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Sat, 11 Feb 2023 03:29:17 +0100 Subject: [PATCH 141/173] [CST-7757] Missing label --- src/assets/i18n/en.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index bf32aebe47..fa6f2694fa 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4748,6 +4748,8 @@ "subscriptions.modal.new-subscription-form.submit": "Submit", + "subscriptions.modal.new-subscription-form.processing": "Processing...", + "subscriptions.modal.create.success": "Subscribed to {{ type }} successfully.", "subscriptions.modal.delete.success": "Subscription deleted successfully", From c74ef49797cb0fe9e816cb9648a7503b7094304f Mon Sep 17 00:00:00 2001 From: cris Date: Sat, 11 Feb 2023 17:13:52 +0000 Subject: [PATCH 142/173] fixed issue in browser console --- .../mydspace-item-collection/item-collection.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.ts b/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.ts index 8f857540d4..2f2a20ede3 100644 --- a/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.ts +++ b/src/app/shared/object-collection/shared/mydspace-item-collection/item-collection.component.ts @@ -40,7 +40,9 @@ export class ItemCollectionComponent implements OnInit { */ ngOnInit() { - this.linkService.resolveLinks(this.object, followLink('workflowitem', {}, + this.linkService.resolveLinks(this.object, followLink('workflowitem', { + isOptional: true + }, followLink('collection',{}) )); this.collection$ = (this.object.workflowitem as Observable>).pipe( From eeda26e122859bfac8fcbc6ea806eae86c19431e Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 13 Feb 2023 10:42:56 +0100 Subject: [PATCH 143/173] 8408 - sort ordering is parametrized and used only when no text query is send. --- config/config.example.yml | 5 +++++ config/config.yml | 6 +++--- .../dso-selector/dso-selector.component.ts | 4 +++- .../create-community-parent-selector.component.ts | 6 ------ .../dso-selector-modal-wrapper.component.ts | 5 +++-- .../scope-selector-modal.component.ts | 7 +------ src/config/app-config.interface.ts | 2 ++ src/config/default-app-config.ts | 7 +++++++ src/config/discovery-sort.config.ts | 14 ++++++++++++++ src/environments/environment.test.ts | 4 ++++ 10 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 src/config/discovery-sort.config.ts diff --git a/config/config.example.yml b/config/config.example.yml index 9abf167b90..c5a9ea6f85 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -310,3 +310,8 @@ info: markdown: enabled: false mathjax: false + +# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. +collectionSelectionSort: + sortMetadata: "dc.title" + sortDirection: "ASC" \ No newline at end of file diff --git a/config/config.yml b/config/config.yml index b5eecd112f..38ac562b15 100644 --- a/config/config.yml +++ b/config/config.yml @@ -1,5 +1,5 @@ rest: - ssl: true - host: api7.dspace.org - port: 443 + ssl: false + host: localhost + port: 9090 nameSpace: /server diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts index 4b5d8c7614..52ca9224b1 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts @@ -227,6 +227,8 @@ export class DSOSelectorComponent implements OnInit, OnDestroy { * @param useCache Whether or not to use the cache */ search(query: string, page: number, useCache: boolean = true): Observable>>> { + // default sort is only used when there is not query + var efectiveSort=query?null:this.sort; return this.searchService.search( new PaginatedSearchOptions({ query: query, @@ -234,7 +236,7 @@ export class DSOSelectorComponent implements OnInit, OnDestroy { pagination: Object.assign({}, this.defaultPagination, { currentPage: page }), - sort: this.sort + sort: efectiveSort }), null, useCache, diff --git a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts index f344c1c3f3..a7f583df50 100644 --- a/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component.ts @@ -12,7 +12,6 @@ import { getCommunityCreateRoute, COMMUNITY_PARENT_PARAMETER } from '../../../../community-page/community-page-routing-paths'; -import {SortDirection, SortOptions} from '../../../../core/cache/models/sort-options.model'; /** * Component to wrap a button - for top communities - @@ -31,11 +30,6 @@ export class CreateCommunityParentSelectorComponent extends DSOSelectorModalWrap selectorTypes = [DSpaceObjectType.COMMUNITY]; action = SelectorActionType.CREATE; - /** - * Default DSO ordering - */ - defaultSort = new SortOptions('dc.title', SortDirection.ASC); - constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute, private router: Router) { super(activeModal, route); } diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index 2a25da2f72..fcb8aea9d2 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -5,7 +5,8 @@ import { RemoteData } from '../../../core/data/remote-data'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; import { hasValue, isNotEmpty } from '../../empty.util'; -import {SortDirection, SortOptions} from '../../../core/cache/models/sort-options.model'; +import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; +import { environment } from '../../../../environments/environment'; export enum SelectorActionType { CREATE = 'create', @@ -53,7 +54,7 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit { /** * Default DSO ordering */ - defaultSort = new SortOptions('dc.title', SortDirection.ASC); + defaultSort = new SortOptions(environment.collectionSelectionSort.sortMetadata, environment.collectionSelectionSort.sortDirection as SortDirection); constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) { } diff --git a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts index 416242520c..86c3010287 100644 --- a/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts +++ b/src/app/shared/search-form/scope-selector-modal/scope-selector-modal.component.ts @@ -4,7 +4,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'; import { DSOSelectorModalWrapperComponent, SelectorActionType } from '../../dso-selector/modal-wrappers/dso-selector-modal-wrapper.component'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; -import {SortDirection, SortOptions} from '../../../core/cache/models/sort-options.model'; + /** * Component to wrap a button - to select the entire repository - * and a list of parent communities - for scope selection @@ -33,11 +33,6 @@ export class ScopeSelectorModalComponent extends DSOSelectorModalWrapperComponen */ scopeChange = new EventEmitter(); - /** - * Default DSO ordering - */ - defaultSort = new SortOptions('dc.title', SortDirection.ASC); - constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) { super(activeModal, route); } diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index ce9c8b3bf7..cc7227d11e 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -20,6 +20,7 @@ import { InfoConfig } from './info-config.interface'; import { CommunityListConfig } from './community-list-config.interface'; import { HomeConfig } from './homepage-config.interface'; import { MarkdownConfig } from './markdown-config.interface'; +import { DiscoverySortConfig } from './discovery-sort.config'; interface AppConfig extends Config { ui: UIServerConfig; @@ -44,6 +45,7 @@ interface AppConfig extends Config { actuators: ActuatorsConfig info: InfoConfig; markdown: MarkdownConfig; + collectionSelectionSort: DiscoverySortConfig; } /** diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 276d2d7150..572f757f86 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -20,6 +20,7 @@ import { InfoConfig } from './info-config.interface'; import { CommunityListConfig } from './community-list-config.interface'; import { HomeConfig } from './homepage-config.interface'; import { MarkdownConfig } from './markdown-config.interface'; +import { DiscoverySortConfig } from './discovery-sort.config'; export class DefaultAppConfig implements AppConfig { production = false; @@ -385,4 +386,10 @@ export class DefaultAppConfig implements AppConfig { enabled: false, mathjax: false, }; + + // Configuration that determines the metadata sorting of community and collection edition and creation when there are not a search query. + collectionSelectionSort: DiscoverySortConfig = { + sortMetadata:"dc.title", + sortDirection:"ASC", + }; } diff --git a/src/config/discovery-sort.config.ts b/src/config/discovery-sort.config.ts new file mode 100644 index 0000000000..02428f70a8 --- /dev/null +++ b/src/config/discovery-sort.config.ts @@ -0,0 +1,14 @@ +import { Config } from './config.interface'; + +/** + * Config that determines a metadata sorting config. + * It's created mainly to sort by metadata community and collection edition and creation + */ +export class DiscoverySortConfig implements Config { + + public sortMetadata: string; + /** + * ASC / DESC values expected + */ + public sortDirection: string; +} diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 19eec26a14..2cdbee9c29 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -283,4 +283,8 @@ export const environment: BuildConfig = { enabled: false, mathjax: false, }, + collectionSelectionSort: { + sortMetadata:"dc.title", + sortDirection:"ASC", + }, }; From 363136027ec0e05d01e2a9d5c1ba6cbbc9dbba45 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Mon, 13 Feb 2023 10:36:13 +0100 Subject: [PATCH 144/173] [CST-7755] WIP revert changes on list components --- ...arch-result-list-element.component.spec.ts | 103 +------------ ...arch-result-list-element.component.spec.ts | 101 +------------ ...arch-result-list-element.component.spec.ts | 101 +------------ ...arch-result-list-element.component.spec.ts | 99 +------------ ...arch-result-list-element.component.spec.ts | 98 +------------ ...on-search-result-list-element.component.ts | 20 +-- ...arch-result-list-element.component.spec.ts | 103 +------------ ...table-object-component-loader.component.ts | 6 - .../abstract-listable-element.component.ts | 10 -- .../item/item-list-element.component.html | 2 +- .../object-list/object-list.component.html | 1 - .../object-list/object-list.component.ts | 5 - ...arch-result-list-element.component.spec.ts | 102 +------------ ...em-search-result-list-element.component.ts | 136 +----------------- 14 files changed, 16 insertions(+), 871 deletions(-) diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts index 3a0e73e46c..178ed86c40 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts @@ -1,7 +1,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { of as observableOf, of } from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { JournalIssueSearchResultListElementComponent } from './journal-issue-search-result-list-element.component'; import { Item } from '../../../../../core/shared/item.model'; @@ -10,43 +10,9 @@ import { TruncatableService } from '../../../../../shared/truncatable/truncatabl import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; -import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; -import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; -import { TranslateService } from '@ngx-translate/core'; -import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; -import { PageInfo } from '../../../../../core/shared/page-info.model'; -import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; -import { GroupMock } from '../../../../../shared/testing/group-mock'; -import { hot } from 'jasmine-marbles'; -import { AuthService } from '../../../../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service'; -import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; -import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; -import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; -import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; -import { EPerson } from '../../../../../core/eperson/models/eperson.model'; -import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let journalIssueListElementComponent: JournalIssueSearchResultListElementComponent; let fixture: ComponentFixture; -let authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(true) -}); - -const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { - getAuthenticatedUserFromStore: () => { - return of(EPersonMock); - } -}); - -const user = Object.assign(new EPerson(), { - id: 'userId', - groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), - _links: { self: { href: 'test.com/uuid/1234567654321' } } -}); -const epersonService = jasmine.createSpyObj('epersonService', { - findById: createSuccessfulRemoteDataObject$(user), -}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), @@ -104,65 +70,12 @@ const enviromentNoThumbs = { } }; -const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { - searchByItem: jasmine.createSpy('searchByItem'), -}); - -const supervisionOrder: any = { - id: '1', - type: 'supervisionOrder', - uuid: 'supervision-order-1', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; -const anothersupervisionOrder: any = { - id: '2', - type: 'supervisionOrder', - uuid: 'supervision-order-2', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; - -const pageInfo = new PageInfo(); -const array = [supervisionOrder, anothersupervisionOrder]; -const paginatedList = buildPaginatedList(pageInfo, array); -const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); - describe('JournalIssueSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - { provide: NotificationsService, useValue: {}}, - { provide: TranslateService, useValue: {}}, - { provide: ResourcePolicyDataService, useValue: {}}, - { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: epersonService}, - { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -174,9 +87,6 @@ describe('JournalIssueSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(JournalIssueSearchResultListElementComponent); journalIssueListElementComponent = fixture.componentInstance; @@ -254,13 +164,6 @@ describe('JournalIssueSearchResultListElementComponent', () => { declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, - {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - {provide: NotificationsService, useValue: {}}, - {provide: TranslateService, useValue: {}}, - {provide: ResourcePolicyDataService, useValue: {}}, - {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: epersonService}, - {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -278,9 +181,7 @@ describe('JournalIssueSearchResultListElementComponent', () => { describe('with environment.browseBy.showThumbnails set to false', () => { beforeEach(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); + journalIssueListElementComponent.object = mockItemWithMetadata; fixture.detectChanges(); }); diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts index 67665403d1..71fa83a3b1 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts @@ -1,7 +1,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { of as observableOf, of } from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { Item } from '../../../../../core/shared/item.model'; import { JournalVolumeSearchResultListElementComponent } from './journal-volume-search-result-list-element.component'; @@ -10,43 +10,10 @@ import { TruncatableService } from '../../../../../shared/truncatable/truncatabl import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; -import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; -import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; -import { TranslateService } from '@ngx-translate/core'; -import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; -import { PageInfo } from '../../../../../core/shared/page-info.model'; -import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; -import { GroupMock } from '../../../../../shared/testing/group-mock'; -import { hot } from 'jasmine-marbles'; -import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; -import { AuthService } from '../../../../../core/auth/auth.service'; -import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; -import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service'; -import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; -import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; -import { EPerson } from '../../../../../core/eperson/models/eperson.model'; -import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let journalVolumeListElementComponent: JournalVolumeSearchResultListElementComponent; let fixture: ComponentFixture; -let authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(true) -}); -const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { - getAuthenticatedUserFromStore: () => { - return of(EPersonMock); - } -}); - -const user = Object.assign(new EPerson(), { - id: 'userId', - groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), - _links: { self: { href: 'test.com/uuid/1234567654321' } } -}); -const epersonService = jasmine.createSpyObj('epersonService', { - findById: createSuccessfulRemoteDataObject$(user), -}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), { @@ -102,65 +69,12 @@ const enviromentNoThumbs = { } }; -const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { - searchByItem: jasmine.createSpy('searchByItem'), -}); - -const supervisionOrder: any = { - id: '1', - type: 'supervisionOrder', - uuid: 'supervision-order-1', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; -const anothersupervisionOrder: any = { - id: '2', - type: 'supervisionOrder', - uuid: 'supervision-order-2', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; - -const pageInfo = new PageInfo(); -const array = [supervisionOrder, anothersupervisionOrder]; -const paginatedList = buildPaginatedList(pageInfo, array); -const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); - describe('JournalVolumeSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [JournalVolumeSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - { provide: NotificationsService, useValue: {}}, - { provide: TranslateService, useValue: {}}, - { provide: ResourcePolicyDataService, useValue: {}}, - { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: epersonService}, - { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -172,9 +86,6 @@ describe('JournalVolumeSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(JournalVolumeSearchResultListElementComponent); journalVolumeListElementComponent = fixture.componentInstance; @@ -251,13 +162,6 @@ describe('JournalVolumeSearchResultListElementComponent', () => { declarations: [JournalVolumeSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, - {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - {provide: NotificationsService, useValue: {}}, - {provide: TranslateService, useValue: {}}, - {provide: ResourcePolicyDataService, useValue: {}}, - {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: epersonService}, - {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -269,9 +173,6 @@ describe('JournalVolumeSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(JournalVolumeSearchResultListElementComponent); journalVolumeListElementComponent = fixture.componentInstance; })); diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts index 63e8abda28..07970d7128 100644 --- a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts @@ -1,7 +1,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { of as observableOf, of } from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { JournalSearchResultListElementComponent } from './journal-search-result-list-element.component'; import { Item } from '../../../../../core/shared/item.model'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; @@ -10,42 +10,9 @@ import { ItemSearchResult } from '../../../../../shared/object-collection/shared import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; -import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; -import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; -import { TranslateService } from '@ngx-translate/core'; -import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; -import { PageInfo } from '../../../../../core/shared/page-info.model'; -import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; -import { GroupMock } from '../../../../../shared/testing/group-mock'; -import { hot } from 'jasmine-marbles'; -import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; -import { AuthService } from '../../../../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service'; -import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; -import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; -import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; -import { EPerson } from '../../../../../core/eperson/models/eperson.model'; -import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let journalListElementComponent: JournalSearchResultListElementComponent; let fixture: ComponentFixture; -let authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(true) -}); - -const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { - getAuthenticatedUserFromStore: () => { - return of(EPersonMock); - } -}); -const user = Object.assign(new EPerson(), { - id: 'userId', - groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), - _links: { self: { href: 'test.com/uuid/1234567654321' } } -}); -const epersonService = jasmine.createSpyObj('epersonService', { - findById: createSuccessfulRemoteDataObject$(user), -}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), @@ -98,65 +65,12 @@ const enviromentNoThumbs = { } }; -const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { - searchByItem: jasmine.createSpy('searchByItem'), -}); - -const supervisionOrder: any = { - id: '1', - type: 'supervisionOrder', - uuid: 'supervision-order-1', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; -const anothersupervisionOrder: any = { - id: '2', - type: 'supervisionOrder', - uuid: 'supervision-order-2', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; - -const pageInfo = new PageInfo(); -const array = [supervisionOrder, anothersupervisionOrder]; -const paginatedList = buildPaginatedList(pageInfo, array); -const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); - describe('JournalSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [JournalSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - { provide: NotificationsService, useValue: {}}, - { provide: TranslateService, useValue: {}}, - { provide: ResourcePolicyDataService, useValue: {}}, - { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: epersonService}, - { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -168,9 +82,6 @@ describe('JournalSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(JournalSearchResultListElementComponent); journalListElementComponent = fixture.componentInstance; @@ -223,13 +134,6 @@ describe('JournalSearchResultListElementComponent', () => { declarations: [JournalSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, - {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - {provide: NotificationsService, useValue: {}}, - {provide: TranslateService, useValue: {}}, - {provide: ResourcePolicyDataService, useValue: {}}, - {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: epersonService}, - {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -241,9 +145,6 @@ describe('JournalSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(JournalSearchResultListElementComponent); journalListElementComponent = fixture.componentInstance; })); diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts index 102393ed8f..9609a9582a 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts @@ -1,7 +1,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { of as observableOf, of } from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { OrgUnitSearchResultListElementComponent } from './org-unit-search-result-list-element.component'; import { Item } from '../../../../../core/shared/item.model'; import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe'; @@ -12,41 +12,9 @@ import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../../shared/mocks/translate-loader.mock'; -import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; -import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; -import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; -import { PageInfo } from '../../../../../core/shared/page-info.model'; -import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; -import { GroupMock } from '../../../../../shared/testing/group-mock'; -import { hot } from 'jasmine-marbles'; -import { AuthService } from '../../../../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service'; -import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; -import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; -import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; -import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; -import { EPerson } from '../../../../../core/eperson/models/eperson.model'; -import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let orgUnitListElementComponent: OrgUnitSearchResultListElementComponent; let fixture: ComponentFixture; -let authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(true) -}); - -const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { - getAuthenticatedUserFromStore: () => { - return of(EPersonMock); - } -}); -const user = Object.assign(new EPerson(), { - id: 'userId', - groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), - _links: { self: { href: 'test.com/uuid/1234567654321' } } -}); -const epersonService = jasmine.createSpyObj('epersonService', { - findById: createSuccessfulRemoteDataObject$(user), -}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), @@ -97,52 +65,6 @@ const enviromentNoThumbs = { } }; -const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { - searchByItem: jasmine.createSpy('searchByItem'), -}); - -const supervisionOrder: any = { - id: '1', - type: 'supervisionOrder', - uuid: 'supervision-order-1', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; -const anothersupervisionOrder: any = { - id: '2', - type: 'supervisionOrder', - uuid: 'supervision-order-2', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; - -const pageInfo = new PageInfo(); -const array = [supervisionOrder, anothersupervisionOrder]; -const paginatedList = buildPaginatedList(pageInfo, array); -const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); - describe('OrgUnitSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -156,12 +78,6 @@ describe('OrgUnitSearchResultListElementComponent', () => { declarations: [ OrgUnitSearchResultListElementComponent , TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - { provide: NotificationsService, useValue: {}}, - { provide: ResourcePolicyDataService, useValue: {}}, - { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: epersonService}, - { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -173,9 +89,6 @@ describe('OrgUnitSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(OrgUnitSearchResultListElementComponent); orgUnitListElementComponent = fixture.componentInstance; @@ -235,12 +148,6 @@ describe('OrgUnitSearchResultListElementComponent', () => { declarations: [OrgUnitSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, - {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - {provide: NotificationsService, useValue: {}}, - {provide: ResourcePolicyDataService, useValue: {}}, - {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: epersonService}, - {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -258,9 +165,7 @@ describe('OrgUnitSearchResultListElementComponent', () => { describe('with environment.browseBy.showThumbnails set to false', () => { beforeEach(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); + orgUnitListElementComponent.object = mockItemWithMetadata; fixture.detectChanges(); }); diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts index 25b3a37c83..31018520f6 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts @@ -1,7 +1,7 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { of as observableOf, of } from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { PersonSearchResultListElementComponent } from './person-search-result-list-element.component'; import { Item } from '../../../../../core/shared/item.model'; @@ -12,44 +12,9 @@ import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service import { APP_CONFIG } from '../../../../../../config/app-config.interface'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../../shared/mocks/translate-loader.mock'; -import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; -import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; -import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; -import { hot } from 'jasmine-marbles'; -import { PageInfo } from '../../../../../core/shared/page-info.model'; -import { GroupMock } from '../../../../../shared/testing/group-mock'; -import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; -import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; -import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; -import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; -import { AuthService } from '../../../../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service'; -import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; -import { EPerson } from '../../../../../core/eperson/models/eperson.model'; -import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let personListElementComponent: PersonSearchResultListElementComponent; let fixture: ComponentFixture; -const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { - searchByItem: jasmine.createSpy('searchByItem'), -}); -let authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(true) -}); - -const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { - getAuthenticatedUserFromStore: () => { - return of(EPersonMock); - } -}); -const user = Object.assign(new EPerson(), { - id: 'userId', - groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), - _links: { self: { href: 'test.com/uuid/1234567654321' } } -}); -const epersonService = jasmine.createSpyObj('epersonService', { - findById: createSuccessfulRemoteDataObject$(user), -}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), @@ -100,48 +65,6 @@ const enviromentNoThumbs = { } }; -const supervisionOrder: any = { - id: '1', - type: 'supervisionOrder', - uuid: 'supervision-order-1', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; -const anothersupervisionOrder: any = { - id: '2', - type: 'supervisionOrder', - uuid: 'supervision-order-2', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; - -const pageInfo = new PageInfo(); -const array = [supervisionOrder, anothersupervisionOrder]; -const paginatedList = buildPaginatedList(pageInfo, array); -const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); - describe('PersonSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ @@ -155,12 +78,6 @@ describe('PersonSearchResultListElementComponent', () => { declarations: [PersonSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - { provide: NotificationsService, useValue: {} }, - { provide: ResourcePolicyDataService, useValue: {}}, - { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: epersonService}, - { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -172,9 +89,6 @@ describe('PersonSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(PersonSearchResultListElementComponent); personListElementComponent = fixture.componentInstance; @@ -234,12 +148,6 @@ describe('PersonSearchResultListElementComponent', () => { declarations: [PersonSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, - {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService}, - {provide: NotificationsService, useValue: {}}, - {provide: ResourcePolicyDataService, useValue: {}}, - {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: epersonService}, - {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -257,9 +165,7 @@ describe('PersonSearchResultListElementComponent', () => { describe('with environment.browseBy.showThumbnails set to false', () => { beforeEach(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); + personListElementComponent.object = mockItemWithMetadata; fixture.detectChanges(); }); 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 09142fc02e..217d7baef9 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 @@ -9,14 +9,6 @@ import { import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface'; -import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; -import { TranslateService } from '@ngx-translate/core'; -import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; -import { AuthService } from '../../../../../core/auth/auth.service'; -import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; -import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service'; @listableObjectComponent('PersonSearchResult', ViewMode.ListElement) @Component({ @@ -32,17 +24,9 @@ export class PersonSearchResultListElementComponent extends ItemSearchResultList public constructor( protected truncatableService: TruncatableService, protected dsoNameService: DSONameService, - @Inject(APP_CONFIG) protected appConfig: AppConfig, - protected supervisionOrderDataService: SupervisionOrderDataService, - protected modalService: NgbModal, - protected notificationsService: NotificationsService, - protected translateService: TranslateService, - protected resourcePolicyService: ResourcePolicyDataService, - protected authService: AuthService, - protected epersonService: EPersonDataService, - protected authorizationService: AuthorizationDataService + @Inject(APP_CONFIG) protected appConfig: AppConfig ) { - super(truncatableService, dsoNameService, appConfig, supervisionOrderDataService, modalService, notificationsService, translateService, resourcePolicyService, authService, epersonService, authorizationService); + super(truncatableService, dsoNameService, appConfig); } /** diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts index 60487af8c8..0cb3e63e87 100644 --- a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts +++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts @@ -1,6 +1,6 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; -import { of as observableOf, of } from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model'; import { ProjectSearchResultListElementComponent } from './project-search-result-list-element.component'; import { Item } from '../../../../../core/shared/item.model'; @@ -10,43 +10,9 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock'; import { By } from '@angular/platform-browser'; import { APP_CONFIG } from '../../../../../../config/app-config.interface'; -import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; -import { NotificationsService } from '../../../../../shared/notifications/notifications.service'; -import { TranslateService } from '@ngx-translate/core'; -import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils'; -import { PageInfo } from '../../../../../core/shared/page-info.model'; -import { buildPaginatedList } from '../../../../../core/data/paginated-list.model'; -import { GroupMock } from '../../../../../shared/testing/group-mock'; -import { hot } from 'jasmine-marbles'; -import { AuthService } from '../../../../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service'; -import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service'; -import { ResourcePolicyDataService } from '../../../../../core/resource-policy/resource-policy-data.service'; -import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub'; -import { EPersonMock } from '../../../../../shared/testing/eperson.mock'; -import { EPerson } from '../../../../../core/eperson/models/eperson.model'; -import { createPaginatedList } from '../../../../../shared/testing/utils.test'; let projectListElementComponent: ProjectSearchResultListElementComponent; let fixture: ComponentFixture; -let authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(true) -}); - -const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { - getAuthenticatedUserFromStore: () => { - return of(EPersonMock); - } -}); - -const user = Object.assign(new EPerson(), { - id: 'userId', - groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), - _links: { self: { href: 'test.com/uuid/1234567654321' } } -}); -const epersonService = jasmine.createSpyObj('epersonService', { - findById: createSuccessfulRemoteDataObject$(user), -}); const mockItemWithMetadata: ItemSearchResult = Object.assign( new ItemSearchResult(), @@ -98,65 +64,12 @@ const enviromentNoThumbs = { } }; -const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { - searchByItem: jasmine.createSpy('searchByItem'), -}); - -const supervisionOrder: any = { - id: '1', - type: 'supervisionOrder', - uuid: 'supervision-order-1', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; -const anothersupervisionOrder: any = { - id: '2', - type: 'supervisionOrder', - uuid: 'supervision-order-2', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; - -const pageInfo = new PageInfo(); -const array = [supervisionOrder, anothersupervisionOrder]; -const paginatedList = buildPaginatedList(pageInfo, array); -const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); - describe('ProjectSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ProjectSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - { provide: NotificationsService, useValue: {}}, - { provide: TranslateService, useValue: {}}, - { provide: ResourcePolicyDataService, useValue: {}}, - { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: epersonService}, - { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -168,9 +81,6 @@ describe('ProjectSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(ProjectSearchResultListElementComponent); projectListElementComponent = fixture.componentInstance; @@ -223,13 +133,6 @@ describe('ProjectSearchResultListElementComponent', () => { declarations: [ProjectSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, - {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - {provide: NotificationsService, useValue: {}}, - {provide: TranslateService, useValue: {}}, - {provide: ResourcePolicyDataService, useValue: {}}, - {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: epersonService}, - {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } @@ -248,9 +151,7 @@ describe('ProjectSearchResultListElementComponent', () => { describe('with environment.browseBy.showThumbnails set to false', () => { beforeEach(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); + projectListElementComponent.object = mockItemWithMetadata; fixture.detectChanges(); }); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 59ce71ff6c..6b75c59181 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -71,11 +71,6 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges */ @Input() showLabel = true; - /** - * Whether to show the supervision orders badges or not - */ - @Input() showSupervisionOrderBadges = false; - /** * The value to display for this element */ @@ -128,7 +123,6 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges */ protected inAndOutputNames: string[] = [ 'object', - 'showSupervisionOrderBadges', 'index', 'linkType', 'listID', diff --git a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts index f292ac5b76..7d4e107b2b 100644 --- a/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts +++ b/src/app/shared/object-collection/shared/object-collection-element/abstract-listable-element.component.ts @@ -16,11 +16,6 @@ export class AbstractListableElementComponent { */ @Input() object: T; - /** - * The supervision orders to render in this list element - */ - @Input() supervisionOrders: T; - /** * The link type to determine the type of link rendered in this element */ @@ -46,11 +41,6 @@ export class AbstractListableElementComponent { */ @Input() showLabel = true; - /** - * Whether to show the supervision orders badges or not - */ - @Input() showSupervisionOrderBadges = false; - /** * The context we matched on to get this component */ diff --git a/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.html b/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.html index 4bf45e7f3b..3877e2f335 100644 --- a/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.html +++ b/src/app/shared/object-list/item-list-element/item-types/item/item-list-element.component.html @@ -1 +1 @@ - + diff --git a/src/app/shared/object-list/object-list.component.html b/src/app/shared/object-list/object-list.component.html index a0ef3e1cb9..b8712b85c5 100644 --- a/src/app/shared/object-list/object-list.component.html +++ b/src/app/shared/object-list/object-list.component.html @@ -31,7 +31,6 @@ [context]="context" [linkType]="linkType" [listID]="selectionConfig?.listId" - [showSupervisionOrderBadges]="showSupervisionOrderBadges" (contentChange)="contentChange.emit($event)"> diff --git a/src/app/shared/object-list/object-list.component.ts b/src/app/shared/object-list/object-list.component.ts index 9d064115e6..65e2b508da 100644 --- a/src/app/shared/object-list/object-list.component.ts +++ b/src/app/shared/object-list/object-list.component.ts @@ -81,11 +81,6 @@ export class ObjectListComponent { */ @Input() showPaginator = true; - /** - * Whether to show the supervision orders badges or not - */ - @Input() showSupervisionOrderBadges = false; - /** * Emit when one of the listed object has changed. */ diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts index d4ca14c105..7665b7d64e 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.spec.ts @@ -1,7 +1,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { of as observableOf, of } from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { ItemSearchResultListElementComponent } from './item-search-result-list-element.component'; import { Item } from '../../../../../../core/shared/item.model'; import { TruncatePipe } from '../../../../../utils/truncate.pipe'; @@ -10,44 +10,10 @@ import { ItemSearchResult } from '../../../../../object-collection/shared/item-s import { DSONameService } from '../../../../../../core/breadcrumbs/dso-name.service'; import { DSONameServiceMock, UNDEFINED_NAME } from '../../../../../mocks/dso-name.service.mock'; import { APP_CONFIG } from '../../../../../../../config/app-config.interface'; -import { SupervisionOrderDataService } from '../../../../../../core/supervision-order/supervision-order-data.service'; -import { NotificationsService } from '../../../../../../shared/notifications/notifications.service'; -import { TranslateService } from '@ngx-translate/core'; -import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../../../../shared/remote-data.utils'; -import { PageInfo } from '../../../../../../core/shared/page-info.model'; -import { buildPaginatedList } from '../../../../../../core/data/paginated-list.model'; -import { GroupMock } from '../../../../../../shared/testing/group-mock'; -import { hot } from 'jasmine-marbles'; -import { AuthService } from '../../../../../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../../../../../core/data/feature-authorization/authorization-data.service'; -import { EPersonDataService } from '../../../../../../core/eperson/eperson-data.service'; -import { ResourcePolicyDataService } from '../../../../../../core/resource-policy/resource-policy-data.service'; -import { AuthServiceStub } from '../../../../../../shared/testing/auth-service.stub'; -import { EPersonMock } from '../../../../../../shared/testing/eperson.mock'; -import { EPerson } from 'src/app/core/eperson/models/eperson.model'; -import { createPaginatedList } from 'src/app/shared/testing/utils.test'; let publicationListElementComponent: ItemSearchResultListElementComponent; let fixture: ComponentFixture; const dcTitle = 'This is just another title'; -let authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(true) -}); - -const authService: AuthServiceStub = Object.assign(new AuthServiceStub(), { - getAuthenticatedUserFromStore: () => { - return of(EPersonMock); - } -}); -const user = Object.assign(new EPerson(), { - id: 'userId', - groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), - _links: { self: { href: 'test.com/uuid/1234567654321' } } -}); -const epersonService = jasmine.createSpyObj('epersonService', { - findById: createSuccessfulRemoteDataObject$(user), -}); - const mockItemWithMetadata: ItemSearchResult = Object.assign(new ItemSearchResult(), { hitHighlights: { 'dc.title': [{ @@ -218,65 +184,12 @@ const enviromentNoThumbs = { } }; -const supervisionOrderDataService: any = jasmine.createSpyObj('supervisionOrderDataService', { - searchByItem: jasmine.createSpy('searchByItem'), -}); - -const supervisionOrder: any = { - id: '1', - type: 'supervisionOrder', - uuid: 'supervision-order-1', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; -const anothersupervisionOrder: any = { - id: '2', - type: 'supervisionOrder', - uuid: 'supervision-order-2', - _links: { - item: { - href: 'https://rest.api/rest/api/eperson' - }, - group: { - href: 'https://rest.api/rest/api/group' - }, - self: { - href: 'https://rest.api/rest/api/supervisionorders/1' - }, - }, - item: observableOf(createSuccessfulRemoteDataObject({})), - group: observableOf(createSuccessfulRemoteDataObject(GroupMock)) -}; - -const pageInfo = new PageInfo(); -const array = [supervisionOrder, anothersupervisionOrder]; -const paginatedList = buildPaginatedList(pageInfo, array); -const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); - describe('ItemSearchResultListElementComponent', () => { beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ declarations: [ItemSearchResultListElementComponent, TruncatePipe], providers: [ { provide: TruncatableService, useValue: {} }, - { provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - { provide: NotificationsService, useValue: {}}, - { provide: TranslateService, useValue: {}}, - { provide: ResourcePolicyDataService, useValue: {}}, - { provide: AuthService, useValue: authService}, - { provide: EPersonDataService, useValue: epersonService}, - { provide: AuthorizationDataService, useValue: authorizationService}, { provide: DSONameService, useClass: DSONameServiceMock }, { provide: APP_CONFIG, useValue: environmentUseThumbs } ], @@ -288,9 +201,6 @@ describe('ItemSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(ItemSearchResultListElementComponent); publicationListElementComponent = fixture.componentInstance; @@ -463,13 +373,6 @@ describe('ItemSearchResultListElementComponent', () => { declarations: [ItemSearchResultListElementComponent, TruncatePipe], providers: [ {provide: TruncatableService, useValue: {}}, - {provide: SupervisionOrderDataService, useValue: supervisionOrderDataService }, - {provide: NotificationsService, useValue: {}}, - {provide: TranslateService, useValue: {}}, - {provide: ResourcePolicyDataService, useValue: {}}, - {provide: AuthService, useValue: authService}, - {provide: EPersonDataService, useValue: epersonService}, - {provide: AuthorizationDataService, useValue: authorizationService}, {provide: DSONameService, useClass: DSONameServiceMock}, { provide: APP_CONFIG, useValue: enviromentNoThumbs } ], @@ -481,9 +384,6 @@ describe('ItemSearchResultListElementComponent', () => { })); beforeEach(waitForAsync(() => { - supervisionOrderDataService.searchByItem.and.returnValue(hot('a|', { - a: paginatedListRD - })); fixture = TestBed.createComponent(ItemSearchResultListElementComponent); publicationListElementComponent = fixture.componentInstance; })); diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts index 7ed96fdc68..f84ae642ad 100644 --- a/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component.ts @@ -1,37 +1,10 @@ -import { Component, Inject } from '@angular/core'; -import { - listableObjectComponent -} from '../../../../../object-collection/shared/listable-object/listable-object.decorator'; +import { Component } from '@angular/core'; +import { listableObjectComponent } from '../../../../../object-collection/shared/listable-object/listable-object.decorator'; import { ViewMode } from '../../../../../../core/shared/view-mode.model'; import { ItemSearchResult } from '../../../../../object-collection/shared/item-search-result.model'; import { SearchResultListElementComponent } from '../../../search-result-list-element.component'; import { Item } from '../../../../../../core/shared/item.model'; import { getItemPageRoute } from '../../../../../../item-page/item-page-routing-paths'; -import { SupervisionOrderDataService } from '../../../../../../core/supervision-order/supervision-order-data.service'; -import { TruncatableService } from '../../../../../../shared/truncatable/truncatable.service'; -import { DSONameService } from '../../../../../../core/breadcrumbs/dso-name.service'; -import { APP_CONFIG, AppConfig } from '../../../../../../../config/app-config.interface'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { combineLatest, filter, map, Observable, switchMap, take } from 'rxjs'; -import { ConfirmationModalComponent } from '../../../../../../shared/confirmation-modal/confirmation-modal.component'; -import { hasValue } from '../../../../../../shared/empty.util'; -import { NotificationsService } from '../../../../../../shared/notifications/notifications.service'; -import { TranslateService } from '@ngx-translate/core'; -import { followLink } from '../../../../../../shared/utils/follow-link-config.model'; -import { - getAllSucceededRemoteData, - getAllSucceededRemoteListPayload, - getFirstSucceededRemoteDataPayload, - getRemoteDataPayload -} from '../../../../../../core/shared/operators'; -import { SupervisionOrder } from '../../../../../../core/supervision-order/models/supervision-order.model'; -import { Group } from '../../../../../../core/eperson/models/group.model'; -import { ResourcePolicyDataService } from '../../../../../../core/resource-policy/resource-policy-data.service'; -import { AuthService } from '../../../../../../core/auth/auth.service'; -import { EPerson } from '../../../../../../core/eperson/models/eperson.model'; -import { EPersonDataService } from '../../../../../../core/eperson/eperson-data.service'; -import { AuthorizationDataService } from '../../../../../../core/data/feature-authorization/authorization-data.service'; -import { FeatureID } from '../../../../../../core/data/feature-authorization/feature-id'; @listableObjectComponent('PublicationSearchResult', ViewMode.ListElement) @listableObjectComponent(ItemSearchResult, ViewMode.ListElement) @@ -44,8 +17,6 @@ import { FeatureID } from '../../../../../../core/data/feature-authorization/fea * The component for displaying a list element for an item search result of the type Publication */ export class ItemSearchResultListElementComponent extends SearchResultListElementComponent { - messagePrefix = 'item.search.result'; - /** * Route to the item's page */ @@ -56,112 +27,9 @@ export class ItemSearchResultListElementComponent extends SearchResultListElemen */ showThumbnails: boolean; - /** - * List of the supervision orders combined with the group - */ - supervisionOrder$: Observable<{ supervisionOrder: SupervisionOrder; group: Group; }[]>; - - /** - * The groups the user belongs to - */ - groups: Group[]; - - constructor( - protected truncatableService: TruncatableService, - protected dsoNameService: DSONameService, - @Inject(APP_CONFIG) protected appConfig: AppConfig, - protected supervisionOrderDataService: SupervisionOrderDataService, - protected modalService: NgbModal, - protected notificationsService: NotificationsService, - protected translateService: TranslateService, - protected resourcePolicyService: ResourcePolicyDataService, - protected authService: AuthService, - protected epersonService: EPersonDataService, - protected authorizationService: AuthorizationDataService - ) { super(truncatableService, dsoNameService, appConfig); } - ngOnInit(): void { super.ngOnInit(); this.showThumbnails = this.appConfig.browseBy.showThumbnails; - let isAdmin = false; - this.authorizationService.isAuthorized(FeatureID.AdministratorOf).subscribe(isadmin => { - isAdmin = isadmin; - }); - - this.authService.getAuthenticatedUserFromStore().pipe( - filter((user: EPerson) => hasValue(user.id)), - switchMap((user: EPerson) => this.epersonService.findById(user.id, true, true, followLink('groups'))), - getAllSucceededRemoteData(), - getRemoteDataPayload(), - switchMap((user: EPerson) => user.groups), - ).subscribe(groups => { - this.groups = groups?.payload?.page; - }); - this.itemPageRoute = getItemPageRoute(this.dso); - if (this.supervisionOrders) { - this.resourcePolicyService.searchByResource( - this.dso.uuid, null, false, true, - followLink('eperson'), followLink('group') - ).pipe( - getAllSucceededRemoteData(), - ).subscribe((result) => { - this.supervisionOrder$ = this.supervisionOrderDataService.searchByItem(this.dso.uuid, false, true, followLink('group')).pipe( - getAllSucceededRemoteListPayload(), - switchMap((supervisionOrders: SupervisionOrder[]) => { - const supervisionOrdersArray = supervisionOrders.map((supervisionOrder: SupervisionOrder) => { - return supervisionOrder.group.pipe( - getFirstSucceededRemoteDataPayload(), - map((group: Group) => { - let isAuthorized = false; - result.payload.page.forEach(resourcePolicy => { - resourcePolicy.group.subscribe(res => { - if (isAdmin || (res.payload && res.payload.uuid === group.uuid && this.groups.find(groups => groups.uuid === group.uuid))) { - isAuthorized = true; - } - }); - }); - return isAuthorized ? ({ supervisionOrder, group }) : null; - }), - ); - }); - return combineLatest(supervisionOrdersArray).pipe( - map(array => array.filter(hasValue)) - ); - })); - }); - } - } - - /** - * Deletes the Group from the Repository. The Group will be the only that this form is showing. - * It'll either show a success or error message depending on whether the delete was successful or not. - */ - deleteSupervisionOrder(supervisionOrder) { - const modalRef = this.modalService.open(ConfirmationModalComponent); - modalRef.componentInstance.dso = supervisionOrder.group; - modalRef.componentInstance.headerLabel = this.messagePrefix + '.delete-supervision.modal.header'; - modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-supervision.modal.info'; - modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-supervision.modal.cancel'; - modalRef.componentInstance.confirmLabel = this.messagePrefix + '.delete-supervision.modal.confirm'; - modalRef.componentInstance.brandColor = 'danger'; - modalRef.componentInstance.confirmIcon = 'fas fa-trash'; - modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { - if (confirm) { - if (hasValue(supervisionOrder.supervisionOrder.id)) { - this.supervisionOrderDataService.delete(supervisionOrder.supervisionOrder.id) - .subscribe((rd: boolean) => { - if (rd) { - this.supervisionOrderDataService.searchByItem(this.dso.uuid, null, null, followLink('group')); - this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.deleted.success', { name: supervisionOrder.group._name })); - } else { - this.notificationsService.error( - this.translateService.get(this.messagePrefix + '.notification.deleted.failure.title', { name: supervisionOrder.group._name }), - this.translateService.get(this.messagePrefix + '.notification.deleted.failure.content')); - } - }); - } - } - }); } } From e2668cdf972e22ee5142427d551e48956848e342 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 13 Feb 2023 12:22:07 +0100 Subject: [PATCH 145/173] DS-8408 - lint errors --- .../dso-selector/dso-selector/dso-selector.component.ts | 2 +- src/config/default-app-config.ts | 4 ++-- src/config/discovery-sort.config.ts | 2 +- src/environments/environment.test.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts index 52ca9224b1..05fb512c9a 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts @@ -228,7 +228,7 @@ export class DSOSelectorComponent implements OnInit, OnDestroy { */ search(query: string, page: number, useCache: boolean = true): Observable>>> { // default sort is only used when there is not query - var efectiveSort=query?null:this.sort; + var efectiveSort=query ? null : this.sort; return this.searchService.search( new PaginatedSearchOptions({ query: query, diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 095a20a4e5..89a627f459 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -425,7 +425,7 @@ export class DefaultAppConfig implements AppConfig { // Configuration that determines the metadata sorting of community and collection edition and creation when there are not a search query. collectionSelectionSort: DiscoverySortConfig = { - sortMetadata:"dc.title", - sortDirection:"ASC", + sortMetadata:'dc.title', + sortDirection:'ASC', }; } diff --git a/src/config/discovery-sort.config.ts b/src/config/discovery-sort.config.ts index 02428f70a8..3f08b3fb17 100644 --- a/src/config/discovery-sort.config.ts +++ b/src/config/discovery-sort.config.ts @@ -1,7 +1,7 @@ import { Config } from './config.interface'; /** - * Config that determines a metadata sorting config. + * Config that determines a metadata sorting config. * It's created mainly to sort by metadata community and collection edition and creation */ export class DiscoverySortConfig implements Config { diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index c352cdca92..8b132dab60 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -298,8 +298,8 @@ export const environment: BuildConfig = { mathjax: false, }, collectionSelectionSort: { - sortMetadata:"dc.title", - sortDirection:"ASC", + sortMetadata:'dc.title', + sortDirection:'ASC', }, vocabularies: [ From 0121e6d895de30c6c1054b8087fa6ecb1b7dd0bd Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Mon, 13 Feb 2023 12:34:13 +0100 Subject: [PATCH 146/173] DS-8408 - more lint problems --- .../shared/dso-selector/dso-selector/dso-selector.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts index 05fb512c9a..fe64c0a41e 100644 --- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts +++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts @@ -228,7 +228,7 @@ export class DSOSelectorComponent implements OnInit, OnDestroy { */ search(query: string, page: number, useCache: boolean = true): Observable>>> { // default sort is only used when there is not query - var efectiveSort=query ? null : this.sort; + let efectiveSort = query ? null : this.sort; return this.searchService.search( new PaginatedSearchOptions({ query: query, From 078bdd287fbeca3912cc894f2356241925172081 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 13 Feb 2023 15:45:56 +0100 Subject: [PATCH 147/173] [CST-7757] Fix delete message --- .../subscription-modal.component.html | 2 +- .../subscription-modal.component.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html index 0e723a8140..a71498f002 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html @@ -27,7 +27,7 @@ -

{{'subscriptions.modal.delete-info' | translate}}

+

{{'subscriptions.modal.delete-info' | translate}}

diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index c094e37ef2..b8426bc635 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -117,6 +117,11 @@ export class SearchComponent implements OnInit { */ @Input() selectionConfig: SelectionConfig; + /** + * A boolean representing if show csv export button + */ + @Input() showCsvExport = false; + /** * A boolean representing if show search sidebar button */ diff --git a/src/app/shared/search/themed-search.component.ts b/src/app/shared/search/themed-search.component.ts index 64a2befeb2..bdeb724779 100644 --- a/src/app/shared/search/themed-search.component.ts +++ b/src/app/shared/search/themed-search.component.ts @@ -19,7 +19,7 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode templateUrl: '../theme-support/themed.component.html', }) export class ThemedSearchComponent extends ThemedComponent { - protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = ['configurationList', 'context', 'configuration', 'fixedFilterQuery', 'useCachedVersionIfAvailable', 'inPlaceSearch', 'linkType', 'paginationId', 'searchEnabled', 'sideBarWidth', 'searchFormPlaceholder', 'selectable', 'selectionConfig', 'showSidebar', 'showViewModes', 'useUniquePageId', 'viewModeList', 'showScopeSelector', 'resultFound', 'deselectObject', 'selectObject', 'trackStatistics']; + protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = ['configurationList', 'context', 'configuration', 'fixedFilterQuery', 'useCachedVersionIfAvailable', 'inPlaceSearch', 'linkType', 'paginationId', 'searchEnabled', 'sideBarWidth', 'searchFormPlaceholder', 'selectable', 'selectionConfig', 'showCsvExport', 'showSidebar', 'showViewModes', 'useUniquePageId', 'viewModeList', 'showScopeSelector', 'resultFound', 'deselectObject', 'selectObject', 'trackStatistics']; @Input() configurationList: SearchConfigurationOption[] = []; @@ -47,6 +47,8 @@ export class ThemedSearchComponent extends ThemedComponent { @Input() selectionConfig: SelectionConfig; + @Input() showCsvExport = false; + @Input() showSidebar = true; @Input() showViewModes = true; From d810cbcc8fea2aea627223f46cd25c8a964c9390 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 15 Feb 2023 11:36:56 +0300 Subject: [PATCH 153/173] src/assets/i18n: Improve English strings for file upload Simple improvement to the wording in the file upload section of the item submission form. Also add a period at the end of the string. Fixes: #2096 --- src/assets/i18n/ar.json5 | 4 ++-- src/assets/i18n/bn.json5 | 2 +- src/assets/i18n/ca.json5 | 2 +- src/assets/i18n/cs.json5 | 4 ++-- src/assets/i18n/de.json5 | 2 +- src/assets/i18n/en.json5 | 2 +- src/assets/i18n/es.json5 | 2 +- src/assets/i18n/fi.json5 | 2 +- src/assets/i18n/fr.json5 | 2 +- src/assets/i18n/gd.json5 | 2 +- src/assets/i18n/hu.json5 | 2 +- src/assets/i18n/ja.json5 | 4 ++-- src/assets/i18n/kk.json5 | 2 +- src/assets/i18n/lv.json5 | 2 +- src/assets/i18n/nl.json5 | 2 +- src/assets/i18n/pt-BR.json5 | 2 +- src/assets/i18n/pt-PT.json5 | 2 +- src/assets/i18n/sv.json5 | 2 +- src/assets/i18n/sw.json5 | 4 ++-- src/assets/i18n/tr.json5 | 2 +- src/assets/i18n/uk.json5 | 2 +- 21 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/assets/i18n/ar.json5 b/src/assets/i18n/ar.json5 index 24e6e14603..1edbdf7981 100644 --- a/src/assets/i18n/ar.json5 +++ b/src/assets/i18n/ar.json5 @@ -6459,9 +6459,9 @@ // TODO New key - Add a translation "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", // TODO New key - Add a translation - "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", // "submission.sections.upload.no-entry": "No", // TODO New key - Add a translation diff --git a/src/assets/i18n/bn.json5 b/src/assets/i18n/bn.json5 index 55ec1820dd..8b903c4a65 100644 --- a/src/assets/i18n/bn.json5 +++ b/src/assets/i18n/bn.json5 @@ -5803,7 +5803,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "অনুগ্রহ করে মনে রাখবেন যে {{collectionName}} সংগ্রহে আপলোড করা ফাইলগুলি অ্যাক্সেসযোগ্য হবে, একক ফাইলের জন্য স্পষ্টভাবে যা নির্ধারণ করা হয়েছে তা ছাড়াও, নিম্নলিখিত গ্রুপ(গুলি) সহ:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "এখানে আপনি বর্তমানে আইটেমটিতে সমস্ত ফাইল পাবেন। আপনি ফাইল মেটাডেটা এবং অ্যাক্সেস শর্তাদি আপডেট করতে পারেন অথবা অতিরিক্ত ফাইল আপলোড করুন - পৃষ্ঠাটিতে সর্বত্র ড্র্যাগ করুন এবং ড্রপ করুন ", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/ca.json5 b/src/assets/i18n/ca.json5 index 24698baac0..246a92061c 100644 --- a/src/assets/i18n/ca.json5 +++ b/src/assets/i18n/ca.json5 @@ -6353,7 +6353,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Tingueu en compte que els fitxers carregats a la col·lecció {{ collectionName }} seran accessibles, a més del que es decideixi explícitament per al fitxer, per als grups següents:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Aquí trobareu tots els fitxers que es troben actualment a l'ítem. Podeu actualitzar les metadades del fitxer i les condicions d'accés o incloure fitxers addicionals simplement arrossegant i deixant anar a qualsevol lloc de la pàgina", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index a04e89b2c3..5ed1474384 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -6327,9 +6327,9 @@ // TODO New key - Add a translation "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", // TODO New key - Add a translation - "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", // "submission.sections.upload.no-entry": "No", // TODO New key - Add a translation diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index c1979be6b6..82d422621c 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -5203,7 +5203,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Bitte beachten Sie, dass in diese Sammlung {{collectionName}} hochgeladene Dateien zugüglich zu dem, was für einzelne Dateien entschieden wurde, für folgende Gruppe(n) zugänglich sein:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Hier finden Sie alle Dateien, die aktuell zum Item gehören. Sie können ihre Metadaten und Zugriffsrechte bearbeiten oder weitere Dateien per Drag & Drop hinzufügen.", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 8fcac7d040..fc4c6aa74d 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4581,7 +4581,7 @@ "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", - "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index ae41bfdcb5..3184e4f22b 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -6353,7 +6353,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Tenga en cuenta que los archivos cargados en la colección {{ collectionName }} serán accesibles, además de lo que se decida explícitamente para el fichero, para los siguientes grupos:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Aquí encontrará todos los archivos que se encuentran actualmente en el ítem. Puede actualizar los metadatos del fichero y las condiciones de acceso e incluir ficheros adicionales simplemente arrastrando-y-soltando en cualquier lugar de la página", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/fi.json5 b/src/assets/i18n/fi.json5 index 2a077006cc..05ae06d46b 100644 --- a/src/assets/i18n/fi.json5 +++ b/src/assets/i18n/fi.json5 @@ -4914,7 +4914,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Yksittäisten tiedostojen pääsyrajoitusten lisäksi {{collectionName}}-kokoelmaan ladatut tiedostot ovat seuraavien ryhmien saatavilla:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Tietueen kaikki tiedostot on lueteltu tässä. Voit päivittää tiedoston metadataa ja pääsyehtoja tai ladata lisää tiedostoja raahaamalla ne mihin hyvänsä sivun kohtaan", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 381e13a743..1e042c81fd 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -5670,7 +5670,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Veuillez noter que, en complément des accès spécifiquement accordés pour le fichier, les fichiers téléchargés dans la collection {{collectionName}} seront accessibles par défaut au(x) groupe(s) suivant(s) :", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Vous trouverez ici tous les fichiers actuellement associés à l'Item. Vous pouvez éditer les métadonnés et les niveaux d'accès de ce(s) fichier(s) ou télécharger des fichiers complémentaires simplement en les glissant n'importe où sur cette page", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/gd.json5 b/src/assets/i18n/gd.json5 index 083de54781..4584145452 100644 --- a/src/assets/i18n/gd.json5 +++ b/src/assets/i18n/gd.json5 @@ -5801,7 +5801,7 @@ // TODO New key - Add a translation "submission.sections.upload.header.policy.default.withlist": "Thoir fa-near gum bi cothrom air faidhlichean a chaidh a luchdachadh gu cruinneachadh {{collectionName}}, cho math ris na chaidh a cho-dhùnadh airson an fhaidhle air leth, aig a' bhuidheann/na buidhnean a leanas:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "An seo gheibh thu na faidhlichean gu lèir san nì an-dràsta. Is urrainn dhut metadata an fhaidhle ùrachadh agus cothrom fhaighinn air cumhachan no faidhlichean eile a luchdachadh suas le bhith gan slaodadh agus gan leigeil às air feadh na duilleig", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/hu.json5 b/src/assets/i18n/hu.json5 index 0e73e9dbae..bfa4a53ccf 100644 --- a/src/assets/i18n/hu.json5 +++ b/src/assets/i18n/hu.json5 @@ -4924,7 +4924,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Kérjük, vegye figyelembe, hogy a {{collectionName}} gyűjteménybe feltöltött állományok elérhetők lesznek, azon kívül amit az egyedi állományokról kifejezetten eldöntött, a következő csoport(ok)ban:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Itt megtalálja a tárgyban lévő valamennyi állományt. Frissítheti az állomány metaadatait és hozzáférési feltételeit vagy feltölthet további állományokat azzal, hogy behúzza azokat bárhova az oldalra", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/ja.json5 b/src/assets/i18n/ja.json5 index 71b32cf70b..83a755c923 100644 --- a/src/assets/i18n/ja.json5 +++ b/src/assets/i18n/ja.json5 @@ -6459,9 +6459,9 @@ // TODO New key - Add a translation "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", // TODO New key - Add a translation - "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", // "submission.sections.upload.no-entry": "No", // TODO New key - Add a translation diff --git a/src/assets/i18n/kk.json5 b/src/assets/i18n/kk.json5 index b661ce21d3..0fd092df02 100644 --- a/src/assets/i18n/kk.json5 +++ b/src/assets/i18n/kk.json5 @@ -6283,7 +6283,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "{{collectionName}} жинағына жүктелген файлдар жеке файл үшін нақты анықталғаннан басқа, келесі топпен (топтармен) қол жетімді болатындығын ескеріңіз:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Мұнда сіз осы элементтегі барлық файлдарды таба аласыз. Файл метадеректерін және кіру шарттарын жаңартуға немесе қосымша файлдарды олардыбетінде сүйреп апару арқылы жүктеуге болады.", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/lv.json5 b/src/assets/i18n/lv.json5 index e00ad3517b..79ff8ce855 100644 --- a/src/assets/i18n/lv.json5 +++ b/src/assets/i18n/lv.json5 @@ -5379,7 +5379,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Lūdzu, ņemiet vērā, ka augšupielādētie faili kolekcijā {{collectionName}} būs pieejami papildus tam, kas ir skaidri noteikts par atsevišķu failu, ar šādām grupām:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Šeit atradīsit visus failus, kas pašlaik atrodas materiālā. Varat atjaunināt failu metadatus un piekļuves nosacījumus vai augšupielādēt papildu failus, vienkārši ievelkot un atstājot tos visur lapā", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/nl.json5 b/src/assets/i18n/nl.json5 index b02e3a5276..1ebdeaa566 100644 --- a/src/assets/i18n/nl.json5 +++ b/src/assets/i18n/nl.json5 @@ -5752,7 +5752,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Let op: bestanden in de collectie {{collectionName}} zullen niet alleen toegankelijk zijn volgens de expliciet toegekende rechten per bestand, maar ook volgens de volgende groep(en):", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Hier vindt u alle bestanden van het item. U kunt de metadata en toegangsrechten bewerken of meer bestanden toevoegen door ze naar deze pagina te slepen.", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index f68436d00b..7dca26aa4e 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -6066,7 +6066,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Por favor note que arquivos enviados a coleção {{collectionName}} serão acessíveis, de acordo com o que está explicitamente definido no arquivo, no(s) seguinte(s) grupo(s):", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Aqui vocẽ encontra todos os arquivos que estão atualmente no item. Você pode atualizar os metadados do arquivo e condições de acesso ou enviar arquivos adicionais apenas arrastando os arquivos em qualquer lugar da página", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/pt-PT.json5 b/src/assets/i18n/pt-PT.json5 index b277e866c0..62e7c53e36 100644 --- a/src/assets/i18n/pt-PT.json5 +++ b/src/assets/i18n/pt-PT.json5 @@ -5117,7 +5117,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Por favor note que os ficheiros enviados à coleção {{collectionName}} serão acessíveis, de acordo com o que está explicitamente definido no ficheiro, no(s) seguinte(s) grupo(s):", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Aqui encontra todos os ficheiros que estão atualmente no item. Pode atualizar os metadados do ficheiro e condições de acesso ou carregar ficheiros adicionais arrastando os ficheiros em qualquer lugar desta página", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/sv.json5 b/src/assets/i18n/sv.json5 index d04b95e144..4c9db83b16 100644 --- a/src/assets/i18n/sv.json5 +++ b/src/assets/i18n/sv.json5 @@ -5954,7 +5954,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Notera att uppladdade filer i samlingen {{collectionName}} också kommer att vara åtkompliga för följande grupp(er):", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Här visas samtliga filer som ingår i posten. Du kan uppdatera filernas metadata och villkor för åtkomst, samt ladda upp nya filer genom att dra och släppa dem här", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/sw.json5 b/src/assets/i18n/sw.json5 index 5e00b54599..4f68655019 100644 --- a/src/assets/i18n/sw.json5 +++ b/src/assets/i18n/sw.json5 @@ -6459,9 +6459,9 @@ // TODO New key - Add a translation "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", // TODO New key - Add a translation - "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", // "submission.sections.upload.no-entry": "No", // TODO New key - Add a translation diff --git a/src/assets/i18n/tr.json5 b/src/assets/i18n/tr.json5 index fff02c5967..92e0c5b6e2 100644 --- a/src/assets/i18n/tr.json5 +++ b/src/assets/i18n/tr.json5 @@ -4905,7 +4905,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "{{collectionName}} koleksiyonuna yüklenen dosyalara, tek dosya için açıkça karar verilenlere ek olarak aşağıdaki gruplarla erişilebilir olacağını lütfen unutmayın:", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Burada, o anda öğede bulunan tüm dosyaları bulacaksınız. Dosya metadatalarını güncelleyebilir ve koşullara erişebilir veya sayfanın her yerine sürükleyip bırakarak ek dosyalar yükleyebilirsiniz", // "submission.sections.upload.no-entry": "No", diff --git a/src/assets/i18n/uk.json5 b/src/assets/i18n/uk.json5 index 0ba5b0109a..6ee2bd9f1c 100644 --- a/src/assets/i18n/uk.json5 +++ b/src/assets/i18n/uk.json5 @@ -5225,7 +5225,7 @@ // "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):", "submission.sections.upload.header.policy.default.withlist": "Зверніть увагу, що завантажені файли в {{collectionName}} зібрання будуть доступні, для користувачів, що входять у групу(и):", - // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files just dragging & dropping them everywhere in the page", + // "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or upload additional files by dragging & dropping them anywhere on the page.", "submission.sections.upload.info": "Тут ви знайдете всі файли, які зараз містяться в документі. Ви можете оновити метадані файлу та умови доступу або завантажити додаткові файли, просто перетягнувши їх сюди на сторінці", // "submission.sections.upload.no-entry": "No", From b676c1ab7967234392381d4362ac0137f2b3bb4c Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Wed, 15 Feb 2023 18:43:49 +0100 Subject: [PATCH 154/173] [CST-8935] Fix issue with missing matcher providers --- src/app/app.module.ts | 4 ++++ src/app/shared/form/form.module.ts | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 750d63beda..89e361821b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -9,6 +9,8 @@ import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router import { MetaReducer, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store'; import { TranslateModule } from '@ngx-translate/core'; import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to'; +import { DYNAMIC_MATCHER_PROVIDERS } from '@ng-dynamic-forms/core'; + import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { appEffects } from './app.effects'; @@ -101,6 +103,8 @@ const PROVIDERS = [ useClass: LogInterceptor, multi: true }, + // register the dynamic matcher used by form. MUST be provided by the app module + ...DYNAMIC_MATCHER_PROVIDERS, ]; const DECLARATIONS = [ diff --git a/src/app/shared/form/form.module.ts b/src/app/shared/form/form.module.ts index 61fc7e3c39..51ebaee1b8 100644 --- a/src/app/shared/form/form.module.ts +++ b/src/app/shared/form/form.module.ts @@ -21,7 +21,7 @@ import { DsDynamicLookupRelationExternalSourceTabComponent } from './builder/ds- import { SharedModule } from '../shared.module'; import { TranslateModule } from '@ngx-translate/core'; import { SearchModule } from '../search/search.module'; -import { DYNAMIC_FORM_CONTROL_MAP_FN, DYNAMIC_MATCHER_PROVIDERS, DynamicFormLayoutService, DynamicFormsCoreModule, DynamicFormService, DynamicFormValidationService } from '@ng-dynamic-forms/core'; +import { DYNAMIC_FORM_CONTROL_MAP_FN, DynamicFormLayoutService, DynamicFormsCoreModule, DynamicFormService, DynamicFormValidationService } from '@ng-dynamic-forms/core'; import { ExistingMetadataListElementComponent } from './builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component'; import { ExistingRelationListElementComponent } from './builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component'; import { ExternalSourceEntryImportModalComponent } from './builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component'; @@ -101,7 +101,6 @@ const DIRECTIVES = [ provide: DYNAMIC_FORM_CONTROL_MAP_FN, useValue: dsDynamicFormControlMapFn }, - ...DYNAMIC_MATCHER_PROVIDERS, VocabularyTreeviewService, DynamicFormLayoutService, DynamicFormService, From 938bf33ad24bc8d0545c8fc936bfc6bcaadf93d0 Mon Sep 17 00:00:00 2001 From: aroman-arvo Date: Wed, 15 Feb 2023 19:20:20 +0100 Subject: [PATCH 155/173] DS-8404 - fix config param name --- config/config.example.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.example.yml b/config/config.example.yml index 7c784b4a8e..f1e6be76aa 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -372,5 +372,5 @@ vocabularies: # Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. comcolSelectionSort: - sortMetadata: 'dc.title' + sortField: 'dc.title' sortDirection: 'ASC' \ No newline at end of file From 028d4192bb7e4f0f69f60b02b7912d5982b49de7 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 15 Feb 2023 11:37:37 +0100 Subject: [PATCH 156/173] translation fixes in security form --- src/assets/i18n/de.json5 | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 82d422621c..6c8eb4bc8f 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -3838,17 +3838,17 @@ // "profile.security.form.error.matching-passwords": "The passwords do not match.", "profile.security.form.error.matching-passwords": "Die Passwörter stimmen nicht überein.", - // "profile.security.form.error.password-length": "The password should be at least 6 characters long.", - "profile.security.form.error.password-length": "Das Passwort sollte mindestens 6 Zeichen lang sein.", - // "profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", - "profile.security.form.info": "Optional können Sie ein neues Passwort in das Feld darunter eingeben und es durch erneute Eingabe in das zweite Feld bestätigen. Es sollte mindestens sechs Zeichen lang sein.", + "profile.security.form.info": "Im nachfolgenden Feld können Sie bei Bedarf ein neues Passwort eingeben und es durch erneute Eingabe in das zweite Feld bestätigen. Es sollte mindestens sechs Zeichen lang sein.", // "profile.security.form.label.password": "Password", "profile.security.form.label.password": "Passwort", // "profile.security.form.label.passwordrepeat": "Retype to confirm", - "profile.security.form.label.passwordrepeat": "Zum Bestätigen erneut eingeben", + "profile.security.form.label.passwordrepeat": "Zum Bestätigen das Passwort erneut eingeben", + + // "profile.security.form.label.current-password": "Current password" + "profile.security.form.label.current-password": "Ihr aktuelles Passwort" // "profile.security.form.notifications.success.content": "Your changes to the password were saved.", "profile.security.form.notifications.success.content": "Ihr geändertes Passwort wurde gespeichert.", @@ -3862,8 +3862,14 @@ // "profile.security.form.notifications.error.not-long-enough": "The password has to be at least 6 characters long.", "profile.security.form.notifications.error.not-long-enough": "Das Passwort sollte mindestens 6 Zeichen lang sein.", + // "profile.security.form.notifications.error.change-failed": "An error occurred while trying to change the password. Please check if the current password is correct.", + "profile.security.form.notifications.error.change-failed": "Es ist ein Fehler beim Ändern des Passworts aufgetreten. Bitte überprüfen Sie, ob Ihr aktuelles Passwort korrekt eingegeben wurde.", + // "profile.security.form.notifications.error.not-same": "The provided passwords are not the same.", "profile.security.form.notifications.error.not-same": "Die angegebenen Passwörter sind nicht identisch.", + + // "profile.security.form.notifications.error.general": "Please fill required fields of security form.", + "profile.security.form.notifications.error.general": "Bitte füllen Sie alle Pflichtfelder im Formular aus.", // "profile.title": "Update Profile", "profile.title": "Profil aktualisieren", From 3ed9ae2d3ea343767c362e223c4579cbc7bda419 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 15 Feb 2023 12:14:42 +0100 Subject: [PATCH 157/173] remove minimum passwort length value from translated messages --- src/assets/i18n/de.json5 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 6c8eb4bc8f..e5d074d7f5 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -1982,8 +1982,8 @@ // "forgot-password.form.head": "Forgot Password", "forgot-password.form.head": "Passwort vergessen", - // "forgot-password.form.info": "Enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", - "forgot-password.form.info": "Bitte geben Sie ein neues Passwort (mindestens sechs Zeichen) ein und bestätigen Sie es erneut.", + // "forgot-password.form.info": "Enter a new password in the box below, and confirm it by typing it again into the second box.", + "forgot-password.form.info": "Bitte geben Sie ein neues Passwort ein und bestätigen Sie es erneut.", // "forgot-password.form.card.security": "Security", "forgot-password.form.card.security": "Sicherheit", @@ -3838,8 +3838,8 @@ // "profile.security.form.error.matching-passwords": "The passwords do not match.", "profile.security.form.error.matching-passwords": "Die Passwörter stimmen nicht überein.", - // "profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", - "profile.security.form.info": "Im nachfolgenden Feld können Sie bei Bedarf ein neues Passwort eingeben und es durch erneute Eingabe in das zweite Feld bestätigen. Es sollte mindestens sechs Zeichen lang sein.", + // "profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box.", + "profile.security.form.info": "Im nachfolgenden Feld können Sie bei Bedarf ein neues Passwort eingeben und es durch erneute Eingabe in das zweite Feld bestätigen.", // "profile.security.form.label.password": "Password", "profile.security.form.label.password": "Passwort", @@ -3975,8 +3975,8 @@ // "register-page.create-profile.security.header": "Security", "register-page.create-profile.security.header": "Sicherheit", - // "register-page.create-profile.security.info": "Please enter a password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.", - "register-page.create-profile.security.info": "Bitte geben Sie ein Passwort in das unten stehende Feld ein und bestätigen Sie es, indem Sie es in das zweite Feld erneut eingeben. Es sollte mindestens sechs Zeichen lang sein.", + // "register-page.create-profile.security.info": "Please enter a password in the box below, and confirm it by typing it again into the second box.", + "register-page.create-profile.security.info": "Bitte geben Sie ein Passwort in das unten stehende Feld ein und bestätigen Sie es, indem Sie es in das zweite Feld erneut eingeben.", // "register-page.create-profile.security.label.password": "Password *", "register-page.create-profile.security.label.password": "Passwort *", From 26705a5cf75b4301991f1edbfa4994b4c55e3800 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 15 Feb 2023 18:32:54 +0100 Subject: [PATCH 158/173] fixed missing comma --- src/assets/i18n/de.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index e5d074d7f5..9dbd703255 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -3848,7 +3848,7 @@ "profile.security.form.label.passwordrepeat": "Zum Bestätigen das Passwort erneut eingeben", // "profile.security.form.label.current-password": "Current password" - "profile.security.form.label.current-password": "Ihr aktuelles Passwort" + "profile.security.form.label.current-password": "Ihr aktuelles Passwort", // "profile.security.form.notifications.success.content": "Your changes to the password were saved.", "profile.security.form.notifications.success.content": "Ihr geändertes Passwort wurde gespeichert.", From e5e6639d214adc7d5e52003577618a2cbf0d40c0 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Wed, 15 Feb 2023 18:34:02 +0100 Subject: [PATCH 159/173] added comma to default translation --- src/assets/i18n/de.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 9dbd703255..df4c2cb4ee 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -3847,7 +3847,7 @@ // "profile.security.form.label.passwordrepeat": "Retype to confirm", "profile.security.form.label.passwordrepeat": "Zum Bestätigen das Passwort erneut eingeben", - // "profile.security.form.label.current-password": "Current password" + // "profile.security.form.label.current-password": "Current password", "profile.security.form.label.current-password": "Ihr aktuelles Passwort", // "profile.security.form.notifications.success.content": "Your changes to the password were saved.", From bf22eb5582e7ff76483a9d6d95b1061af1b2a41a Mon Sep 17 00:00:00 2001 From: cris Date: Wed, 15 Feb 2023 22:14:31 +0000 Subject: [PATCH 160/173] suggestionOption.name displayed --- .../dso-input-suggestions/dso-input-suggestions.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html index 7515b830b9..64c88bbd78 100644 --- a/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html +++ b/src/app/shared/input-suggestions/dso-input-suggestions/dso-input-suggestions.component.html @@ -14,6 +14,7 @@
- + diff --git a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.scss b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.scss index 98d8659570..49e0384661 100644 --- a/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.scss +++ b/src/app/admin/admin-registries/metadata-schema/metadata-schema.component.scss @@ -1,3 +1,8 @@ .selectable-row:hover { cursor: pointer; } + +:host ::ng-deep #metadatadatafieldgroup { + display: flex; + flex-wrap: wrap; +} From ec3f7736d455b14218526fa89f50d4bcaa6b64e3 Mon Sep 17 00:00:00 2001 From: cris Date: Fri, 24 Feb 2023 16:19:41 +0000 Subject: [PATCH 172/173] avoiding drag and drop in put for multiple select option --- .../models/array-group/dynamic-form-array.component.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html index d518d59da2..dd19e6158d 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html @@ -12,12 +12,11 @@ [formGroupName]="idx" [ngClass]="[getClass('element', 'group'), getClass('grid', 'group')]" cdkDrag - cdkDragHandle [cdkDragDisabled]="dragDisabled" [cdkDragPreviewClass]="'ds-submission-reorder-dragging'" [class.grey-background]="model.isInlineGroupArray"> -
+
From 200e95a390e5764b4289851c36f109f7b2498f5d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 26 Feb 2023 00:53:26 +0100 Subject: [PATCH 173/173] Fix vertical alignment of text & buttons in tables --- .../epeople-registry.component.html | 8 ++++---- .../epeople-registry.component.spec.ts | 14 +++++++------- .../subgroup-list/subgroups-list.component.html | 2 +- .../group-registry/groups-registry.component.html | 4 ++-- .../bitstream-formats.component.html | 2 +- src/styles/_global-styles.scss | 5 +++++ 6 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.html b/src/app/access-control/epeople-registry/epeople-registry.component.html index 2d87f21d26..3a7806a231 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.html +++ b/src/app/access-control/epeople-registry/epeople-registry.component.html @@ -8,7 +8,7 @@
@@ -30,7 +30,7 @@
{{'item.edit.modify.overview.field'| translate}}
{{subscription.subscriptionType}} + {{ 'subscriptions.frequency.'+ parameterList.value | translate}}, diff --git a/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts b/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts index a0caba97ee..00f0b8d22c 100644 --- a/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts +++ b/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts @@ -103,7 +103,7 @@ describe('SubscriptionViewComponent', () => { }); it('should have subscription paramenter info', () => { - expect(de.query(By.css('.subscription-parmenters > span'))).toBeTruthy(); + expect(de.query(By.css('.subscription-parameters > span'))).toBeTruthy(); }); it('should have subscription action info', () => { diff --git a/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts b/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts index 9f7fca7caf..4c6c5c6d21 100644 --- a/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts +++ b/src/app/shared/subscriptions/subscription-view/subscription-view.component.ts @@ -19,6 +19,9 @@ import { SubscriptionModalComponent } from '../subscription-modal/subscription-m templateUrl: './subscription-view.component.html', styleUrls: ['./subscription-view.component.scss'] }) +/** + * Table row representing a subscription that displays all information and action buttons to manage it + */ export class SubscriptionViewComponent { /** diff --git a/src/app/subscriptions-page/subscriptions-page.component.ts b/src/app/subscriptions-page/subscriptions-page.component.ts index 7307218d0a..05c587ba12 100644 --- a/src/app/subscriptions-page/subscriptions-page.component.ts +++ b/src/app/subscriptions-page/subscriptions-page.component.ts @@ -20,6 +20,9 @@ import { hasValue } from '../shared/empty.util'; templateUrl: './subscriptions-page.component.html', styleUrls: ['./subscriptions-page.component.scss'] }) +/** + * List and allow to manage all the active subscription for the current user + */ export class SubscriptionsPageComponent implements OnInit, OnDestroy { /** From 7a46e6d93cff1b44961c05f8b2832188604cdec5 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Mon, 6 Feb 2023 11:21:12 +0100 Subject: [PATCH 084/173] [CST-7757] Labels renamed --- ...so-page-subscription-button.component.html | 6 ++-- .../subscription-modal.component.html | 12 +++---- src/assets/i18n/en.json5 | 34 +++++++++---------- 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html index 8601dcdda9..15135009fc 100644 --- a/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html +++ b/src/app/shared/dso-page/dso-page-subscription-button/dso-page-subscription-button.component.html @@ -1,8 +1,8 @@ diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html index debc6edf50..e095e094dc 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html @@ -1,6 +1,6 @@
From 39fb61ca856c75f9b6d32518d0b6d97346cfb40b Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 19 Jan 2023 16:15:31 +1300 Subject: [PATCH 104/173] [TLC-249] Lint fixes (imports, quotes) --- src/app/core/data/item-data.service.ts | 2 +- .../item-register-doi/item-registerdoi.component.ts | 4 ++-- .../item-status/item-status.component.ts | 10 +++++----- .../identifiers/section-identifiers.component.spec.ts | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 79719e502c..96007f5598 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -257,7 +257,7 @@ export abstract class BaseItemDataService extends IdentifiableDataService headers = headers.append('Content-Type', 'application/json'); options.headers = headers; // Pass identifier type as a simple parameter, no need for full JSON data - let hrefWithParams: string = this.buildHrefWithParams(href, [new RequestParam("type", "doi")]); + let hrefWithParams: string = this.buildHrefWithParams(href, [new RequestParam('type', 'doi')]); const request = new PostRequest(requestId, hrefWithParams, JSON.stringify({}), options); this.requestService.send(request); }); diff --git a/src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.ts b/src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.ts index 875bcbac7e..5f20c2a3d9 100644 --- a/src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.ts +++ b/src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.ts @@ -11,9 +11,9 @@ import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../../ import { first, map } from 'rxjs/operators'; import { hasValue } from '../../../shared/empty.util'; import { Observable } from 'rxjs'; -import {getItemEditRoute, getItemPageRoute} from '../../item-page-routing-paths'; +import { getItemPageRoute } from '../../item-page-routing-paths'; import { IdentifierDataService } from '../../../core/data/identifier-data.service'; -import {Identifier} from '../../../shared/object-list/identifier-data/identifier.model'; +import { Identifier } from '../../../shared/object-list/identifier-data/identifier.model'; @Component({ selector: 'ds-item-registerdoi', diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index 20f167fb80..0f3b44f26d 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -4,7 +4,7 @@ import { Item } from '../../../core/shared/item.model'; import { ActivatedRoute } from '@angular/router'; import { ItemOperation } from '../item-operation/itemOperation.model'; import {distinctUntilChanged, first, map, mergeMap, toArray} from 'rxjs/operators'; -import {BehaviorSubject, Observable, from as observableFrom, Subscription, combineLatest, of} from 'rxjs'; +import { BehaviorSubject, Observable, from as observableFrom, Subscription, combineLatest } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; @@ -13,10 +13,10 @@ import { hasValue } from '../../../shared/empty.util'; import { getAllSucceededRemoteDataPayload, } from '../../../core/shared/operators'; -import {IdentifierDataService} from '../../../core/data/identifier-data.service'; -import {Identifier} from '../../../shared/object-list/identifier-data/identifier.model'; -import {ConfigurationProperty} from '../../../core/shared/configuration-property.model'; -import {ConfigurationDataService} from '../../../core/data/configuration-data.service'; +import { IdentifierDataService } from '../../../core/data/identifier-data.service'; +import { Identifier } from '../../../shared/object-list/identifier-data/identifier.model'; +import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; +import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; @Component({ selector: 'ds-item-status', diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts b/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts index b381bdf33c..6a001c4d5f 100644 --- a/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts +++ b/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts @@ -7,7 +7,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NgxPaginationModule } from 'ngx-pagination'; import { cold } from 'jasmine-marbles'; -import {Observable, of as observableOf} from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { TranslateModule } from '@ngx-translate/core'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; @@ -25,7 +25,7 @@ import { FormService } from '../../../shared/form/form.service'; import { SubmissionFormsConfigDataService } from '../../../core/config/submission-forms-config-data.service'; import { SectionDataObject } from '../models/section-data.model'; import { SectionsType } from '../sections-type'; -import {mockSectionsData, mockSubmissionCollectionId, mockSubmissionId} from '../../../shared/mocks/submission.mock'; +import { mockSubmissionCollectionId, mockSubmissionId } from '../../../shared/mocks/submission.mock'; import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner'; import { SubmissionSectionIdentifiersComponent } from './section-identifiers.component'; import { CollectionDataService } from '../../../core/data/collection-data.service'; From d6f458863f40f6ac0c102f9a0b888e58cae857f2 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Jan 2023 13:01:57 +1300 Subject: [PATCH 105/173] [TLC-249] Improve model of identifier data in workspace section --- ...workspaceitem-section-identifiers.model.ts | 6 +-- .../section-identifiers.component.html | 37 ++++--------------- 2 files changed, 10 insertions(+), 33 deletions(-) diff --git a/src/app/core/submission/models/workspaceitem-section-identifiers.model.ts b/src/app/core/submission/models/workspaceitem-section-identifiers.model.ts index 6a8956dff3..f6bfb1ae04 100644 --- a/src/app/core/submission/models/workspaceitem-section-identifiers.model.ts +++ b/src/app/core/submission/models/workspaceitem-section-identifiers.model.ts @@ -1,9 +1,9 @@ /* * Object model for the data returned by the REST API to present minted identifiers in a submission section */ +import { Identifier } from '../../../shared/object-list/identifier-data/identifier.model'; + export interface WorkspaceitemSectionIdentifiersObject { - doi?: string - handle?: string - otherIdentifiers?: string[] + identifiers?: Identifier[] displayTypes?: string[] } diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.html b/src/app/submission/sections/identifiers/section-identifiers.component.html index 39645704da..dd0b5d2930 100644 --- a/src/app/submission/sections/identifiers/section-identifiers.component.html +++ b/src/app/submission/sections/identifiers/section-identifiers.component.html @@ -3,41 +3,18 @@ Template for the identifiers submission section component @author Kim Shepherd --> - -
+ +
{{'submission.sections.identifiers.info' | translate}}
    - - - -
  • {{'submission.sections.identifiers.no_handle' | translate}}
  • -
    - -
  • {{'submission.sections.identifiers.handle_label' | translate}}{{identifierData.handle}}
  • -
    -
    - - - - -
  • {{'submission.sections.identifiers.no_doi' | translate}}
  • -
    - -
  • {{'submission.sections.identifiers.doi_label' | translate}} - {{identifierData.doi}} -
  • -
    -
    - - - - -
  • {{'submission.sections.identifiers.otherIdentifiers_label' | translate}} - {{identifierData.otherIdentifiers.join(',')}} -
  • + + +
  • {{'submission.sections.identifiers.' + identifier.identifierType + '_label' | translate}} + {{identifier.value}}
+ From 9471aa98977ac843a88ce0748d59759507d9e352 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Jan 2023 14:23:41 +1300 Subject: [PATCH 106/173] [TLC-249] Update en.json5 with DOI status labels --- src/assets/i18n/en.json5 | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 47e9f57b6d..c811383c86 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1918,27 +1918,29 @@ "item.edit.tabs.item-mapper.title": "Item Edit - Collection Mapper", - "item.edit.identifiers.doi.status.1": "Queued for registration", + "item.edit.identifiers.doi.status.UNKNOWN": "Unknown", - "item.edit.identifiers.doi.status.2": "Queued for reservation", + "item.edit.identifiers.doi.status.TO_BE_REGISTERED": "Queued for registration", - "item.edit.identifiers.doi.status.3": "Registered", + "item.edit.identifiers.doi.status.TO_BE_RESERVED": "Queued for reservation", - "item.edit.identifiers.doi.status.4": "Reserved", + "item.edit.identifiers.doi.status.IS_REGISTERED": "Registered", - "item.edit.identifiers.doi.status.5": "Reserved", + "item.edit.identifiers.doi.status.IS_RESERVED": "Reserved", - "item.edit.identifiers.doi.status.6": "Registered", + "item.edit.identifiers.doi.status.UPDATE_RESERVED": "Reserved (update queued)", - "item.edit.identifiers.doi.status.7": "Queued for registration", + "item.edit.identifiers.doi.status.UPDATE_REGISTERED": "Registered (update queued)", - "item.edit.identifiers.doi.status.8": "Queued for deletion", + "item.edit.identifiers.doi.status.UPDATE_BEFORE_REGISTRATION": "Queued for update and registration", - "item.edit.identifiers.doi.status.9": "Deleted", + "item.edit.identifiers.doi.status.TO_BE_DELETED": "Queued for deletion", - "item.edit.identifiers.doi.status.10": "Pending approval", + "item.edit.identifiers.doi.status.DELETED": "Deleted", - "item.edit.identifiers.doi.status.11": "Minted, not registered", + "item.edit.identifiers.doi.status.PENDING": "Pending (not registered)", + + "item.edit.identifiers.doi.status.MINTED": "Minted (not registered)", "item.edit.tabs.status.buttons.registerDOI.label": "Register a new or pending identifier", From b5c6e7f1b76a10827354116221f92d712e468782 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Jan 2023 16:02:23 +1300 Subject: [PATCH 107/173] [TLC-249] Identifier mock data updated in unit test --- .../section-identifiers.component.spec.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts b/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts index 6a001c4d5f..e9663ae20c 100644 --- a/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts +++ b/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts @@ -72,9 +72,20 @@ const mockItem = Object.assign(new Item(), { // Mock identifier data to use with tests const identifierData: WorkspaceitemSectionIdentifiersObject = { - doi: 'https://doi.org/10.33515/dspace/1', - handle: '123456789/999', - otherIdentifiers: ['123-123-123', 'ANBX-159'] + identifiers: [{ + value: 'https://doi.org/10.33515/dspace-61', + identifierType: 'doi', + identifierStatus: 'TO_BE_REGISTERED', + type: 'identifier' + }, + { + value: '123456789/418', + identifierType: 'handle', + identifierStatus: null, + type: 'identifier' + } + ], + displayTypes: ["doi", "handle"] }; // Mock section object to use with tests From b779509bf0ed027d555739c0078f387b5ab7fbea Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 25 Jan 2023 16:14:00 +1300 Subject: [PATCH 108/173] [TLC-249] Identifier mock data updated in unit test --- .../sections/identifiers/section-identifiers.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts b/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts index e9663ae20c..041e2af23a 100644 --- a/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts +++ b/src/app/submission/sections/identifiers/section-identifiers.component.spec.ts @@ -85,7 +85,7 @@ const identifierData: WorkspaceitemSectionIdentifiersObject = { type: 'identifier' } ], - displayTypes: ["doi", "handle"] + displayTypes: ['doi', 'handle'] }; // Mock section object to use with tests From ce84c3fe36080ca888cad48db895398612c2fc0d Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 1 Feb 2023 13:50:29 +1300 Subject: [PATCH 109/173] [TLC-380] Renaming, changes as per review feedback --- src/app/core/data/identifier-data.service.ts | 2 +- .../edit-item-page.routing.module.ts | 6 +++--- ...ard.ts => item-page-register-doi.guard.ts} | 0 ....html => item-register-doi-component.html} | 0 ...ts => item-register-doi.component.spec.ts} | 12 +++++------ ...nent.ts => item-register-doi.component.ts} | 4 ++-- .../item-status/item-status.component.spec.ts | 2 +- .../item-status/item-status.component.ts | 2 +- src/assets/i18n/en.json5 | 20 +++++++++---------- 9 files changed, 24 insertions(+), 24 deletions(-) rename src/app/item-page/edit-item-page/{item-page-registerdoi.guard.ts => item-page-register-doi.guard.ts} (100%) rename src/app/item-page/edit-item-page/item-register-doi/{item-registerdoi-component.html => item-register-doi-component.html} (100%) rename src/app/item-page/edit-item-page/item-register-doi/{item-registerdoi.component.spec.ts => item-register-doi.component.spec.ts} (93%) rename src/app/item-page/edit-item-page/item-register-doi/{item-registerdoi.component.ts => item-register-doi.component.ts} (97%) diff --git a/src/app/core/data/identifier-data.service.ts b/src/app/core/data/identifier-data.service.ts index be4c5c8b59..6596f6493c 100644 --- a/src/app/core/data/identifier-data.service.ts +++ b/src/app/core/data/identifier-data.service.ts @@ -55,7 +55,7 @@ export class IdentifierDataService extends BaseDataService { * Should we allow registration of new DOIs via the item status page? */ public getIdentifierRegistrationConfiguration(): Observable { - return this.configurationService.findByPropertyName('identifiers.item-status.registerDOI').pipe( + return this.configurationService.findByPropertyName('identifiers.item-status.register-doi').pipe( getFirstCompletedRemoteData(), map((propertyRD: RemoteData) => propertyRD.hasSucceeded ? propertyRD.payload.values : []) ); diff --git a/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts b/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts index 22fd528653..88172e2620 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.routing.module.ts @@ -10,7 +10,7 @@ import { ItemStatusComponent } from './item-status/item-status.component'; import { ItemBitstreamsComponent } from './item-bitstreams/item-bitstreams.component'; import { ItemCollectionMapperComponent } from './item-collection-mapper/item-collection-mapper.component'; import { ItemMoveComponent } from './item-move/item-move.component'; -import { ItemRegisterDoiComponent } from './item-register-doi/item-registerdoi.component'; +import { ItemRegisterDoiComponent } from './item-register-doi/item-register-doi.component'; import { ItemRelationshipsComponent } from './item-relationships/item-relationships.component'; import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component'; @@ -40,7 +40,7 @@ import { ItemPageRelationshipsGuard } from './item-page-relationships.guard'; import { ItemPageVersionHistoryGuard } from './item-page-version-history.guard'; import { ItemPageCollectionMapperGuard } from './item-page-collection-mapper.guard'; import { ThemedDsoEditMetadataComponent } from '../../dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component'; -import { ItemPageRegisterDoiGuard } from './item-page-registerdoi.guard'; +import { ItemPageRegisterDoiGuard } from './item-page-register-doi.guard'; /** * Routing module that handles the routing for the Edit Item page administrator functionality @@ -149,7 +149,7 @@ import { ItemPageRegisterDoiGuard } from './item-page-registerdoi.guard'; path: ITEM_EDIT_REGISTER_DOI_PATH, component: ItemRegisterDoiComponent, canActivate: [ItemPageRegisterDoiGuard], - data: { title: 'item.edit.registerdoi.title' }, + data: { title: 'item.edit.register-doi.title' }, }, { path: ITEM_EDIT_AUTHORIZATIONS_PATH, diff --git a/src/app/item-page/edit-item-page/item-page-registerdoi.guard.ts b/src/app/item-page/edit-item-page/item-page-register-doi.guard.ts similarity index 100% rename from src/app/item-page/edit-item-page/item-page-registerdoi.guard.ts rename to src/app/item-page/edit-item-page/item-page-register-doi.guard.ts diff --git a/src/app/item-page/edit-item-page/item-register-doi/item-registerdoi-component.html b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi-component.html similarity index 100% rename from src/app/item-page/edit-item-page/item-register-doi/item-registerdoi-component.html rename to src/app/item-page/edit-item-page/item-register-doi/item-register-doi-component.html diff --git a/src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.spec.ts b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.spec.ts similarity index 93% rename from src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.spec.ts rename to src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.spec.ts index 9f0bb280c7..ac17d7c751 100644 --- a/src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.spec.ts @@ -13,7 +13,7 @@ import { ItemDataService } from '../../../core/data/item-data.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { By } from '@angular/platform-browser'; -import { ItemRegisterDoiComponent } from './item-registerdoi.component'; +import { ItemRegisterDoiComponent } from './item-register-doi.component'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { IdentifierDataService } from '../../../core/data/identifier-data.service'; @@ -83,15 +83,15 @@ describe('ItemRegisterDoiComponent', () => { fixture.detectChanges(); }); - it('should render a page with messages based on the \'registerdoi\' messageKey', () => { + it('should render a page with messages based on the \'register-doi\' messageKey', () => { const header = fixture.debugElement.query(By.css('h2')).nativeElement; - expect(header.innerHTML).toContain('item.edit.registerdoi.header'); + expect(header.innerHTML).toContain('item.edit.register-doi.header'); const description = fixture.debugElement.query(By.css('p')).nativeElement; - expect(description.innerHTML).toContain('item.edit.registerdoi.description'); + expect(description.innerHTML).toContain('item.edit.register-doi.description'); const confirmButton = fixture.debugElement.query(By.css('button.perform-action')).nativeElement; - expect(confirmButton.innerHTML).toContain('item.edit.registerdoi.confirm'); + expect(confirmButton.innerHTML).toContain('item.edit.register-doi.confirm'); const cancelButton = fixture.debugElement.query(By.css('button.cancel')).nativeElement; - expect(cancelButton.innerHTML).toContain('item.edit.registerdoi.cancel'); + expect(cancelButton.innerHTML).toContain('item.edit.register-doi.cancel'); }); describe('performAction', () => { diff --git a/src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.ts b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts similarity index 97% rename from src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.ts rename to src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts index 5f20c2a3d9..9345ba18cc 100644 --- a/src/app/item-page/edit-item-page/item-register-doi/item-registerdoi.component.ts +++ b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts @@ -16,8 +16,8 @@ import { IdentifierDataService } from '../../../core/data/identifier-data.servic import { Identifier } from '../../../shared/object-list/identifier-data/identifier.model'; @Component({ - selector: 'ds-item-registerdoi', - templateUrl: './item-registerdoi-component.html' + selector: 'ds-item-register-doi', + templateUrl: './item-register-doi-component.html' }) /** * Component responsible for rendering the Item Registe DOI page diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.spec.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.spec.ts index 1f447deb9e..a67de2f435 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.spec.ts @@ -41,7 +41,7 @@ describe('ItemStatusComponent', () => { mockConfigurationDataService = jasmine.createSpyObj('configurationDataService', { findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { - name: 'identifiers.item-status.registerDOI', + name: 'identifiers.item-status.register-doi', values: [ 'true' ] diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index 0f3b44f26d..94122f5658 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -108,7 +108,7 @@ export class ItemStatusComponent implements OnInit { ); // Observable for configuration determining whether the Register DOI feature is enabled - let registerConfigEnabled$: Observable = this.configurationService.findByPropertyName('identifiers.item-status.registerDOI').pipe( + let registerConfigEnabled$: Observable = this.configurationService.findByPropertyName('identifiers.item-status.register-doi').pipe( map((enabled: RemoteData) => { let show = false; if (enabled.hasSucceeded) { diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c811383c86..18fa6e0b4b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1327,7 +1327,7 @@ "curation-task.task.vscan.label": "Virus Scan", - "curation-task.task.registerdoi.label": "Register DOI", + "curation-task.task.register-doi.label": "Register DOI", @@ -1942,23 +1942,23 @@ "item.edit.identifiers.doi.status.MINTED": "Minted (not registered)", - "item.edit.tabs.status.buttons.registerDOI.label": "Register a new or pending identifier", + "item.edit.tabs.status.buttons.register-doi.label": "Register a new or pending identifier", - "item.edit.tabs.status.buttons.registerDOI.button": "Register DOI...", + "item.edit.tabs.status.buttons.register-doi.button": "Register DOI...", - "item.edit.registerdoi.header": "Register a new or pending DOI", + "item.edit.register-doi.header": "Register a new or pending DOI", - "item.edit.registerdoi.description": "Review any pending identifiers and item metadata below and click Confirm to proceed with DOI registration, or Cancel to back out", + "item.edit.register-doi.description": "Review any pending identifiers and item metadata below and click Confirm to proceed with DOI registration, or Cancel to back out", - "item.edit.registerdoi.confirm": "Confirm", + "item.edit.register-doi.confirm": "Confirm", - "item.edit.registerdoi.cancel": "Cancel", + "item.edit.register-doi.cancel": "Cancel", - "item.edit.registerdoi.success": "DOI registered successfully. Refresh Item Status page to see new DOI details.", + "item.edit.register-doi.success": "DOI registered successfully. Refresh Item Status page to see new DOI details.", - "item.edit.registerdoi.error": "Error registering DOI", + "item.edit.register-doi.error": "Error registering DOI", - "item.edit.registerdoi.to-update": "The following DOI has already been minted and will be queued for registration online", + "item.edit.register-doi.to-update": "The following DOI has already been minted and will be queued for registration online", "item.edit.item-mapper.buttons.add": "Map item to selected collections", From e86f0d3d13fe8f69130abd86cbbd6e34e4eef5f1 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 1 Feb 2023 16:30:44 +1300 Subject: [PATCH 110/173] [TLC-380] WIP trying new routing / create flow --- src/app/core/data/identifier-data.service.ts | 44 ++++++++++++++++++- src/app/core/data/item-data.service.ts | 6 ++- .../item-register-doi.component.ts | 4 +- 3 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/app/core/data/identifier-data.service.ts b/src/app/core/data/identifier-data.service.ts index 6596f6493c..53f811bc40 100644 --- a/src/app/core/data/identifier-data.service.ts +++ b/src/app/core/data/identifier-data.service.ts @@ -1,4 +1,4 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { NotificationsService } from '../../shared/notifications/notifications.service'; @@ -16,9 +16,18 @@ import { Item } from '../shared/item.model'; import { IDENTIFIERS } from '../../shared/object-list/identifier-data/identifier-data.resource-type'; import { IdentifierData } from '../../shared/object-list/identifier-data/identifier-data.model'; import { getFirstCompletedRemoteData } from '../shared/operators'; -import { map } from 'rxjs/operators'; +import { find, map, switchMap } from 'rxjs/operators'; import {ConfigurationProperty} from '../shared/configuration-property.model'; import {ConfigurationDataService} from './configuration-data.service'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; +import { hasValue } from '../../shared/empty.util'; +import { PostRequest } from './request.models'; +import { GenericConstructor } from '../shared/generic-constructor'; +import { ResponseParsingService } from './parsing.service'; +import { StatusCodeOnlyResponseParsingService } from './status-code-only-response-parsing.service'; +import { sendRequest } from '../shared/request.operators'; +import { RestRequest } from './rest-request.model'; +import { OrcidHistory } from '../orcid/model/orcid-history.model'; /** * The service handling all REST requests to get item identifiers like handles and DOIs @@ -60,4 +69,35 @@ export class IdentifierDataService extends BaseDataService { map((propertyRD: RemoteData) => propertyRD.hasSucceeded ? propertyRD.payload.values : []) ); } + + public registerIdentifier(item: Item, type: string): Observable> { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + let params = new HttpParams(); + params = params.append('type', 'doi'); + options.params = params; + + const requestId = this.requestService.generateRequestId(); + const hrefObs = this.getEndpoint(); + + hrefObs.pipe( + find((href: string) => hasValue(href)), + map((href: string) => { + + const request = new PostRequest(requestId, href, item._links.self.href, options); + Object.assign(request, { + getResponseParser(): GenericConstructor { + return StatusCodeOnlyResponseParsingService; + } + }); + return request; + }) + ).subscribe((request) => { + this.requestService.send(request); + }); + + return this.rdbService.buildFromRequestUUID(requestId); + } } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 96007f5598..7082058612 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -233,7 +233,7 @@ export abstract class BaseItemDataService extends IdentifiableDataService } /** - * Get the endpoint for an item's bundles + * Get the endpoint for an item's identifiers * @param itemId */ public getIdentifiersEndpoint(itemId: string): Observable { @@ -264,6 +264,10 @@ export abstract class BaseItemDataService extends IdentifiableDataService return this.rdbService.buildFromRequestUUID(requestId); } + + + + /** * Get the endpoint to move the item * @param itemId diff --git a/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts index 9345ba18cc..f0ea46ba4a 100644 --- a/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts +++ b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts @@ -82,13 +82,13 @@ export class ItemRegisterDoiComponent extends AbstractSimpleItemActionComponent */ registerDoi() { this.processing = true; - this.itemDataService.registerDOI(this.item.id).pipe(getFirstCompletedRemoteData()).subscribe( + this.identifierDataService.registerIdentifier(this.item, 'doi').subscribe( (response: RemoteData) => { this.processing = false; //this.router.navigateByUrl(getItemEditRoute(this.item)); this.processRestResponse(response); } - ); + ) } } From 0a69560a8c7ce1526985f2005ad1e5d6c04c2c30 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 2 Feb 2023 09:35:49 +1300 Subject: [PATCH 111/173] [TLC-380] Refactor to use new endpoints, better naming --- src/app/core/data/identifier-data.service.ts | 48 ++++++------------- .../edit-item-page.routing-paths.ts | 2 +- .../item-register-doi.component.ts | 13 ++--- .../item-status/item-status.component.ts | 2 +- src/assets/i18n/en.json5 | 4 +- 5 files changed, 26 insertions(+), 43 deletions(-) diff --git a/src/app/core/data/identifier-data.service.ts b/src/app/core/data/identifier-data.service.ts index 53f811bc40..03422dadfb 100644 --- a/src/app/core/data/identifier-data.service.ts +++ b/src/app/core/data/identifier-data.service.ts @@ -16,18 +16,13 @@ import { Item } from '../shared/item.model'; import { IDENTIFIERS } from '../../shared/object-list/identifier-data/identifier-data.resource-type'; import { IdentifierData } from '../../shared/object-list/identifier-data/identifier-data.model'; import { getFirstCompletedRemoteData } from '../shared/operators'; -import { find, map, switchMap } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; import {ConfigurationProperty} from '../shared/configuration-property.model'; import {ConfigurationDataService} from './configuration-data.service'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; -import { hasValue } from '../../shared/empty.util'; import { PostRequest } from './request.models'; -import { GenericConstructor } from '../shared/generic-constructor'; -import { ResponseParsingService } from './parsing.service'; -import { StatusCodeOnlyResponseParsingService } from './status-code-only-response-parsing.service'; import { sendRequest } from '../shared/request.operators'; import { RestRequest } from './rest-request.model'; -import { OrcidHistory } from '../orcid/model/orcid-history.model'; /** * The service handling all REST requests to get item identifiers like handles and DOIs @@ -71,33 +66,20 @@ export class IdentifierDataService extends BaseDataService { } public registerIdentifier(item: Item, type: string): Observable> { - const options: HttpOptions = Object.create({}); - let headers = new HttpHeaders(); - headers = headers.append('Content-Type', 'text/uri-list'); - options.headers = headers; - let params = new HttpParams(); - params = params.append('type', 'doi'); - options.params = params; - const requestId = this.requestService.generateRequestId(); - const hrefObs = this.getEndpoint(); - - hrefObs.pipe( - find((href: string) => hasValue(href)), - map((href: string) => { - - const request = new PostRequest(requestId, href, item._links.self.href, options); - Object.assign(request, { - getResponseParser(): GenericConstructor { - return StatusCodeOnlyResponseParsingService; - } - }); - return request; - }) - ).subscribe((request) => { - this.requestService.send(request); - }); - - return this.rdbService.buildFromRequestUUID(requestId); + return this.getEndpoint().pipe( + map((endpointURL: string) => { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + let params = new HttpParams(); + params = params.append('type', type); + options.params = params; + return new PostRequest(requestId, endpointURL, item._links.self.href, options); + }), + sendRequest(this.requestService), + switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid) as Observable>) + ); } } diff --git a/src/app/item-page/edit-item-page/edit-item-page.routing-paths.ts b/src/app/item-page/edit-item-page/edit-item-page.routing-paths.ts index 2826d06bb4..6b0907dceb 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.routing-paths.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.routing-paths.ts @@ -5,4 +5,4 @@ export const ITEM_EDIT_PUBLIC_PATH = 'public'; export const ITEM_EDIT_DELETE_PATH = 'delete'; export const ITEM_EDIT_MOVE_PATH = 'move'; export const ITEM_EDIT_AUTHORIZATIONS_PATH = 'authorizations'; -export const ITEM_EDIT_REGISTER_DOI_PATH = 'registerdoi'; +export const ITEM_EDIT_REGISTER_DOI_PATH = 'register-doi'; diff --git a/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts index f0ea46ba4a..17ec16b1bd 100644 --- a/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts +++ b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts @@ -7,7 +7,7 @@ import { RemoteData } from '../../../core/data/remote-data'; import { Item } from '../../../core/shared/item.model'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { ItemDataService } from '../../../core/data/item-data.service'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteData } from '../../../core/shared/operators'; +import { getFirstSucceededRemoteData } from '../../../core/shared/operators'; import { first, map } from 'rxjs/operators'; import { hasValue } from '../../../shared/empty.util'; import { Observable } from 'rxjs'; @@ -20,11 +20,11 @@ import { Identifier } from '../../../shared/object-list/identifier-data/identifi templateUrl: './item-register-doi-component.html' }) /** - * Component responsible for rendering the Item Registe DOI page + * Component responsible for rendering the Item Register DOI page */ export class ItemRegisterDoiComponent extends AbstractSimpleItemActionComponent { - protected messageKey = 'registerdoi'; + protected messageKey = 'register-doi'; doiToUpdateMessage = 'item.edit.' + this.messageKey + '.to-update'; identifiers$: Observable; processing = false; @@ -84,9 +84,10 @@ export class ItemRegisterDoiComponent extends AbstractSimpleItemActionComponent this.processing = true; this.identifierDataService.registerIdentifier(this.item, 'doi').subscribe( (response: RemoteData) => { - this.processing = false; - //this.router.navigateByUrl(getItemEditRoute(this.item)); - this.processRestResponse(response); + if (response.hasCompleted) { + this.processing = false; + this.processRestResponse(response); + } } ) } diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index 94122f5658..a7d8f80ea0 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -179,7 +179,7 @@ export class ItemStatusComponent implements OnInit { let tmp_operations = [...operations]; if (show) { // Push the new Register DOI item operation - tmp_operations.push(new ItemOperation('registerDOI', this.getCurrentUrl(item) + '/registerdoi', FeatureID.CanRegisterDOI)); + tmp_operations.push(new ItemOperation('register-doi', this.getCurrentUrl(item) + '/register-doi', FeatureID.CanRegisterDOI)); } // Check authorisations and merge into new operations list observableFrom(tmp_operations).pipe( diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 18fa6e0b4b..0c62342b2d 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1942,7 +1942,7 @@ "item.edit.identifiers.doi.status.MINTED": "Minted (not registered)", - "item.edit.tabs.status.buttons.register-doi.label": "Register a new or pending identifier", + "item.edit.tabs.status.buttons.register-doi.label": "Register a new or pending DOI", "item.edit.tabs.status.buttons.register-doi.button": "Register DOI...", @@ -1954,7 +1954,7 @@ "item.edit.register-doi.cancel": "Cancel", - "item.edit.register-doi.success": "DOI registered successfully. Refresh Item Status page to see new DOI details.", + "item.edit.register-doi.success": "DOI queued for registration successfully.", "item.edit.register-doi.error": "Error registering DOI", From 639fe69c5f52ee972bec75b162f4a0af9227ddb3 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Thu, 2 Feb 2023 09:35:55 +1300 Subject: [PATCH 112/173] [TLC-380] Refactor to use new endpoints, better naming --- src/app/core/data/item-data.service.ts | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index 7082058612..80da91acb3 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -242,32 +242,6 @@ export abstract class BaseItemDataService extends IdentifiableDataService ); } - /** - * Register a DOI for a given item - * @param itemId - */ - public registerDOI(itemId: string): Observable> { - const requestId = this.requestService.generateRequestId(); - const hrefObs = this.getIdentifiersEndpoint(itemId); - hrefObs.pipe( - take(1) - ).subscribe((href) => { - const options: HttpOptions = Object.create({}); - let headers = new HttpHeaders(); - headers = headers.append('Content-Type', 'application/json'); - options.headers = headers; - // Pass identifier type as a simple parameter, no need for full JSON data - let hrefWithParams: string = this.buildHrefWithParams(href, [new RequestParam('type', 'doi')]); - const request = new PostRequest(requestId, hrefWithParams, JSON.stringify({}), options); - this.requestService.send(request); - }); - return this.rdbService.buildFromRequestUUID(requestId); - } - - - - - /** * Get the endpoint to move the item * @param itemId From e65bb88e23dd4210b56f04b206e52766439186b8 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Mon, 6 Feb 2023 10:32:54 +1300 Subject: [PATCH 113/173] [TLC-249] Attempting art's sub fix (doesn't work) --- .../item-status/item-status.component.ts | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index a7d8f80ea0..24b352685e 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -3,7 +3,7 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { Item } from '../../../core/shared/item.model'; import { ActivatedRoute } from '@angular/router'; import { ItemOperation } from '../item-operation/itemOperation.model'; -import {distinctUntilChanged, first, map, mergeMap, toArray} from 'rxjs/operators'; +import { distinctUntilChanged, first, map, mergeMap, switchMap, take, toArray } from 'rxjs/operators'; import { BehaviorSubject, Observable, from as observableFrom, Subscription, combineLatest } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths'; @@ -174,27 +174,36 @@ export class ItemStatusComponent implements OnInit { ); // Subscribe to changes from the showRegister check and rebuild operations list accordingly - this.subs.push(showRegister$.subscribe((show) => { - // Copy the static array first so we don't keep appending to it - let tmp_operations = [...operations]; - if (show) { - // Push the new Register DOI item operation - tmp_operations.push(new ItemOperation('register-doi', this.getCurrentUrl(item) + '/register-doi', FeatureID.CanRegisterDOI)); - } + this.subs.push(showRegister$.pipe( + switchMap((show: boolean) => { + console.dir('show? ' + show); + // Copy the static array first so we don't keep appending to it + let tmp_operations = [...operations]; + if (show) { + // Push the new Register DOI item operation + tmp_operations.push(new ItemOperation('register-doi', this.getCurrentUrl(item) + '/register-doi', FeatureID.CanRegisterDOI)); + } + // emit the operations one at a time + return tmp_operations + }), // Check authorisations and merge into new operations list - observableFrom(tmp_operations).pipe( - mergeMap((operation) => { - if (hasValue(operation.featureID)) { - return this.authorizationService.isAuthorized(operation.featureID, item.self).pipe( - distinctUntilChanged(), - map((authorized) => new ItemOperation(operation.operationKey, operation.operationUrl, operation.featureID, !authorized, authorized)) - ); - } else { - return [operation]; - } - }), - toArray() - ).subscribe((ops) => this.operations$.next(ops)); + mergeMap((operation: ItemOperation) => { + console.dir("operation! " + (operation.featureID)); + if (hasValue(operation.featureID)) { + return this.authorizationService.isAuthorized(operation.featureID, item.self).pipe( + //distinctUntilChanged(), + map((authorized) => new ItemOperation(operation.operationKey, operation.operationUrl, operation.featureID, !authorized, authorized)) + ); + } else { + return [operation]; + } + }), + //take(operations.length + 1), + // wait until all observables have completed and emit them all as a single array + toArray(), + ).subscribe((ops: ItemOperation[]) => { + console.dir('next!'); + this.operations$.next(ops); })); }); From 76407866c02abf70e881dff8577512c879a3a52b Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 7 Feb 2023 14:58:30 +1300 Subject: [PATCH 114/173] [TLC-249] Update item status button logic to avoid nested subs --- .../item-status/item-status.component.ts | 103 ++++++++---------- 1 file changed, 47 insertions(+), 56 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index 24b352685e..c2db518415 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -3,20 +3,21 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { Item } from '../../../core/shared/item.model'; import { ActivatedRoute } from '@angular/router'; import { ItemOperation } from '../item-operation/itemOperation.model'; -import { distinctUntilChanged, first, map, mergeMap, switchMap, take, toArray } from 'rxjs/operators'; -import { BehaviorSubject, Observable, from as observableFrom, Subscription, combineLatest } from 'rxjs'; +import { distinctUntilChanged, first, map, mergeMap, switchMap, toArray } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { getItemEditRoute, 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 { hasValue } from '../../../shared/empty.util'; import { - getAllSucceededRemoteDataPayload, + getAllSucceededRemoteDataPayload, getFirstSucceededRemoteData, getRemoteDataPayload, } from '../../../core/shared/operators'; import { IdentifierDataService } from '../../../core/data/identifier-data.service'; import { Identifier } from '../../../shared/object-list/identifier-data/identifier.model'; import { ConfigurationProperty } from '../../../core/shared/configuration-property.model'; import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; +import { IdentifierData } from '../../../shared/object-list/identifier-data/identifier-data.model'; @Component({ selector: 'ds-item-status', @@ -52,11 +53,6 @@ export class ItemStatusComponent implements OnInit { */ operations$: BehaviorSubject = new BehaviorSubject([]); - /** - * The keys of the actions (to loop over) - */ - actionsKeys; - /** * Identifiers (handles, DOIs) */ @@ -109,27 +105,23 @@ export class ItemStatusComponent implements OnInit { // Observable for configuration determining whether the Register DOI feature is enabled let registerConfigEnabled$: Observable = this.configurationService.findByPropertyName('identifiers.item-status.register-doi').pipe( - map((enabled: RemoteData) => { - let show = false; - if (enabled.hasSucceeded) { - if (enabled.payload !== undefined && enabled.payload !== null) { - if (enabled.payload.values !== undefined) { - enabled.payload.values.forEach((value) => { - show = true; - }); - } - } + getFirstSucceededRemoteData(), + getRemoteDataPayload(), + map((enabled: ConfigurationProperty) => { + if (enabled !== undefined && enabled.values) { + return true; } - return show; + return false; }) ); /* + Construct a base list of operations. The key is used to build messages i18n example: 'item.edit.tabs.status.buttons..label' The value is supposed to be a href for the button */ - const operations = []; + const operations: ItemOperation[] = []; operations.push(new ItemOperation('authorizations', this.getCurrentUrl(item) + '/authorizations', FeatureID.CanManagePolicies, true)); operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl(item) + '/mapper', FeatureID.CanManageMappings, true)); if (item.isWithdrawn) { @@ -146,11 +138,16 @@ export class ItemStatusComponent implements OnInit { operations.push(new ItemOperation('move', this.getCurrentUrl(item) + '/move', FeatureID.CanMove, true)); this.operations$.next(operations); - // Observable that reads identifiers and their status and, and config properties, and decides - // if we're allowed to show a Register DOI feature - let showRegister$: Observable = combineLatest([this.identifiers$, registerConfigEnabled$]).pipe( - distinctUntilChanged(), - map(([identifiers, enabled]) => { + /* + When the identifier data stream changes, determine whether the register DOI button should be shown or not. + This is based on whether the DOI is in the right state (minted or pending, not already queued for registration + or registered) and whether the configuration property identifiers.item-status.register-doi is true + */ + this.identifierDataService.getIdentifierDataFor(item).pipe( + getFirstSucceededRemoteData(), + getRemoteDataPayload(), + mergeMap((data: IdentifierData) => { + let identifiers = data.identifiers; let no_doi = true; let pending = false; if (identifiers !== undefined && identifiers !== null) { @@ -169,43 +166,37 @@ export class ItemStatusComponent implements OnInit { }); } // If there is no DOI, or a pending/minted/null DOI, and the config is enabled, return true - return ((pending || no_doi) && enabled); - }) - ); - - // Subscribe to changes from the showRegister check and rebuild operations list accordingly - this.subs.push(showRegister$.pipe( - switchMap((show: boolean) => { - console.dir('show? ' + show); - // Copy the static array first so we don't keep appending to it - let tmp_operations = [...operations]; - if (show) { - // Push the new Register DOI item operation - tmp_operations.push(new ItemOperation('register-doi', this.getCurrentUrl(item) + '/register-doi', FeatureID.CanRegisterDOI)); - } - // emit the operations one at a time - return tmp_operations + return registerConfigEnabled$.pipe( + map((enabled: boolean) => { + return enabled && (pending || no_doi); + } + )); }), - // Check authorisations and merge into new operations list - mergeMap((operation: ItemOperation) => { - console.dir("operation! " + (operation.featureID)); - if (hasValue(operation.featureID)) { - return this.authorizationService.isAuthorized(operation.featureID, item.self).pipe( - //distinctUntilChanged(), - map((authorized) => new ItemOperation(operation.operationKey, operation.operationUrl, operation.featureID, !authorized, authorized)) + // Switch map pushes the register DOI operation onto a copy of the base array then returns to the pipe + switchMap((showDoi: boolean) => { + let ops = [...operations]; + if (showDoi) { + ops.push(new ItemOperation('register-doi', this.getCurrentUrl(item) + '/register-doi', FeatureID.CanRegisterDOI, true)); + } + return ops; + }), + // Merge map checks and transforms each operation in the array based on whether it is authorized or not (disabled) + mergeMap((op: ItemOperation) => { + if (hasValue(op.featureID)) { + return this.authorizationService.isAuthorized(op.featureID, item.self).pipe( + distinctUntilChanged(), + map((authorized) => new ItemOperation(op.operationKey, op.operationUrl, op.featureID, !authorized, authorized)) ); } else { - return [operation]; + return [op]; } }), - //take(operations.length + 1), - // wait until all observables have completed and emit them all as a single array + // Wait for all operations to be emitted and return as an array toArray(), - ).subscribe((ops: ItemOperation[]) => { - console.dir('next!'); - this.operations$.next(ops); - })); - + ).subscribe((data) => { + // Update the operations$ subject that draws the administrative buttons on the status page + this.operations$.next(data); + }); }); this.itemPageRoute$ = this.itemRD$.pipe( From 08096e682987e67cd8b14f81c9134a7b6698353d Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 7 Feb 2023 17:12:02 +1300 Subject: [PATCH 115/173] [TLC-249] Lint --- .../item-register-doi/item-register-doi.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts index 17ec16b1bd..54878c6026 100644 --- a/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts +++ b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.ts @@ -89,7 +89,7 @@ export class ItemRegisterDoiComponent extends AbstractSimpleItemActionComponent this.processRestResponse(response); } } - ) + ); } } From 83462324447c87630319c342bd936ff7f310fd5c Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 7 Feb 2023 17:22:59 +1300 Subject: [PATCH 116/173] [TLC-249] Fix import typo (from merge conflict) --- src/app/item-page/edit-item-page/edit-item-page.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/item-page/edit-item-page/edit-item-page.module.ts b/src/app/item-page/edit-item-page/edit-item-page.module.ts index bbce184375..24d27b3340 100644 --- a/src/app/item-page/edit-item-page/edit-item-page.module.ts +++ b/src/app/item-page/edit-item-page/edit-item-page.module.ts @@ -36,7 +36,7 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource- import { ItemVersionsModule } from '../versions/item-versions.module'; import { IdentifierDataService } from '../../core/data/identifier-data.service'; import { IdentifierDataComponent } from '../../shared/object-list/identifier-data/identifier-data.component'; -import { ItemRegisterDoiComponent } from './item-register-doi/item-registerdoi.component'; +import { ItemRegisterDoiComponent } from './item-register-doi/item-register-doi.component'; import { DsoSharedModule } from '../../dso-shared/dso-shared.module'; From 129342435fc26eb1d20ac76b8d1a1ad77bbb73bf Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Tue, 7 Feb 2023 17:48:06 +1300 Subject: [PATCH 117/173] [TLC-249] Further test fixes --- .../item-register-doi/item-register-doi.component.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.spec.ts b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.spec.ts index ac17d7c751..af52114642 100644 --- a/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.spec.ts +++ b/src/app/item-page/edit-item-page/item-register-doi/item-register-doi.component.spec.ts @@ -45,7 +45,8 @@ describe('ItemRegisterDoiComponent', () => { mockIdentifierDataService = jasmine.createSpyObj('mockIdentifierDataService', { getIdentifierDataFor: createSuccessfulRemoteDataObject$({'identifiers': []}), - getIdentifierRegistrationConfiguration: createSuccessfulRemoteDataObject$('true') + getIdentifierRegistrationConfiguration: createSuccessfulRemoteDataObject$('true'), + registerIdentifier: createSuccessfulRemoteDataObject$({'identifiers': []}), }); mockItemDataService = jasmine.createSpyObj('mockItemDataService', { @@ -98,7 +99,7 @@ describe('ItemRegisterDoiComponent', () => { it('should call registerDOI function from the ItemDataService', () => { spyOn(comp, 'processRestResponse'); comp.performAction(); - expect(mockItemDataService.registerDOI).toHaveBeenCalledWith(comp.item.id); + expect(mockIdentifierDataService.registerIdentifier).toHaveBeenCalledWith(comp.item, 'doi'); expect(comp.processRestResponse).toHaveBeenCalled(); }); }); From 88cb397dc9dde018741d2a372b72866370db8456 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Tue, 7 Feb 2023 15:01:01 +0100 Subject: [PATCH 118/173] Fix direct CSR By moving from environment.ts to config.yml we made it so the environment is _not_ up to date with the server configuration when main.js is first loaded. Because of this the app behaved as if CSR always happened _after_ SSR, effectively breaking direct CSR. Here the "criterion" for SSR/non-SSR HTML is changed from the related configuration property to the presence of Angular Universal transfer state. This means we can correctly determine when to bootstrap the app for direct CSR, and it's' now "safe" to just send index.html by itself. --- server.ts | 22 +++++----------------- src/index.csr.html | 18 ------------------ src/main.browser.ts | 19 +++++++++++++------ 3 files changed, 18 insertions(+), 41 deletions(-) delete mode 100644 src/index.csr.html diff --git a/server.ts b/server.ts index 81137ad56a..0fecf6bd52 100644 --- a/server.ts +++ b/server.ts @@ -31,7 +31,6 @@ import * as expressStaticGzip from 'express-static-gzip'; import { existsSync, readFileSync } from 'fs'; import { join } from 'path'; -import { APP_BASE_HREF } from '@angular/common'; import { enableProdMode } from '@angular/core'; import { ngExpressEngine } from '@nguniversal/express-engine'; @@ -57,7 +56,7 @@ const DIST_FOLDER = join(process.cwd(), 'dist/browser'); // Set path fir IIIF viewer. const IIIF_VIEWER = join(process.cwd(), 'dist/iiif'); -const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : 'index'; +const indexHtml = join(DIST_FOLDER, 'index.html'); const cookieParser = require('cookie-parser'); @@ -207,7 +206,6 @@ function ngApp(req, res) { baseUrl: environment.ui.nameSpace, originUrl: environment.ui.baseUrl, requestUrl: req.originalUrl, - providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] }, (err, data) => { if (hasNoValue(err) && hasValue(data)) { res.locals.ssr = true; // mark response as SSR @@ -222,25 +220,15 @@ function ngApp(req, res) { if (hasValue(err)) { console.warn('Error details : ', err); } - res.render(indexHtml, { - req, - providers: [{ - provide: APP_BASE_HREF, - useValue: req.baseUrl - }] - }); + + res.sendFile(indexHtml); } }); } else { // If preboot is disabled, just serve the client console.log('Universal off, serving for direct CSR'); - res.render(indexHtml, { - req, - providers: [{ - provide: APP_BASE_HREF, - useValue: req.baseUrl - }] - }); + + res.sendFile(indexHtml); } } diff --git a/src/index.csr.html b/src/index.csr.html deleted file mode 100644 index b1ef4343b1..0000000000 --- a/src/index.csr.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - DSpace - - - - - - - - - - - diff --git a/src/main.browser.ts b/src/main.browser.ts index d5efe828c3..68debfb355 100644 --- a/src/main.browser.ts +++ b/src/main.browser.ts @@ -2,21 +2,27 @@ import 'zone.js'; import 'reflect-metadata'; import 'core-js/es/reflect'; -import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { load as loadWebFont } from 'webfontloader'; -import { hasValue } from './app/shared/empty.util'; - import { BrowserAppModule } from './modules/app/browser-app.module'; import { environment } from './environments/environment'; import { AppConfig } from './config/app-config.interface'; import { extendEnvironmentWithAppConfig } from './config/config.util'; +import { enableProdMode } from '@angular/core'; const bootstrap = () => platformBrowserDynamic() .bootstrapModule(BrowserAppModule, {}); +/** + * We use this to determine have been serven SSR HTML or not. + * + * At this point, {@link environment} may not be in sync with the configuration. + * Therefore, we cannot depend on it to determine how to bootstrap the app. + */ +const hasTransferState = document.querySelector('script#dspace-angular-state') !== null; + const main = () => { // Load fonts async // https://github.com/typekit/webfontloader#configuration @@ -30,22 +36,23 @@ const main = () => { enableProdMode(); } - if (hasValue(environment.universal) && environment.universal.preboot) { + if (hasTransferState) { + // Configuration will be taken from transfer state during initialization return bootstrap(); } else { + // Configuration must be fetched explicitly return fetch('assets/config.json') .then((response) => response.json()) .then((appConfig: AppConfig) => { // extend environment with app config for browser when not prerendered extendEnvironmentWithAppConfig(environment, appConfig); - return bootstrap(); }); } }; // support async tag or hmr -if (document.readyState === 'complete' && hasValue(environment.universal) && !environment.universal.preboot) { +if (document.readyState === 'complete' && !hasTransferState) { main(); } else { document.addEventListener('DOMContentLoaded', main); From ad870829d2e962c6d259ab74e9c5ee1e8562af48 Mon Sep 17 00:00:00 2001 From: Koen Pauwels Date: Wed, 8 Feb 2023 11:13:03 +0100 Subject: [PATCH 119/173] 97732 Remove unnecessary subscriptions --- .../context-help-toggle.component.ts | 13 +++---------- .../context-help-wrapper.component.ts | 19 ++++++------------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/app/header/context-help-toggle/context-help-toggle.component.ts b/src/app/header/context-help-toggle/context-help-toggle.component.ts index 14f9ebeb63..6685df7106 100644 --- a/src/app/header/context-help-toggle/context-help-toggle.component.ts +++ b/src/app/header/context-help-toggle/context-help-toggle.component.ts @@ -1,6 +1,6 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { ContextHelpService } from '../../shared/context-help.service'; -import { Observable, Subscription } from 'rxjs'; +import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; /** @@ -12,22 +12,15 @@ import { map } from 'rxjs/operators'; templateUrl: './context-help-toggle.component.html', styleUrls: ['./context-help-toggle.component.scss'] }) -export class ContextHelpToggleComponent implements OnInit, OnDestroy { +export class ContextHelpToggleComponent implements OnInit { buttonVisible$: Observable; constructor( private contextHelpService: ContextHelpService, ) { } - private subs: Subscription[]; - ngOnInit(): void { this.buttonVisible$ = this.contextHelpService.tooltipCount$().pipe(map(x => x > 0)); - this.subs = [this.buttonVisible$.subscribe()]; - } - - ngOnDestroy() { - this.subs.forEach(sub => sub.unsubscribe()); } onClick() { diff --git a/src/app/shared/context-help-wrapper/context-help-wrapper.component.ts b/src/app/shared/context-help-wrapper/context-help-wrapper.component.ts index c5bb40cf1b..e170d522b5 100644 --- a/src/app/shared/context-help-wrapper/context-help-wrapper.component.ts +++ b/src/app/shared/context-help-wrapper/context-help-wrapper.component.ts @@ -61,8 +61,7 @@ export class ContextHelpWrapperComponent implements OnInit, OnDestroy { parsedContent$: Observable; - private subs: {always: Subscription[], tooltipBound: Subscription[]} - = {always: [], tooltipBound: []}; + private subs: Subscription[] = []; constructor( private translateService: TranslateService, @@ -78,14 +77,13 @@ export class ContextHelpWrapperComponent implements OnInit, OnDestroy { dontParseLinks ? [text] : this.parseLinks(text)) ); this.shouldShowIcon$ = this.contextHelpService.shouldShowIcons$(); - this.subs.always = [this.parsedContent$.subscribe(), this.shouldShowIcon$.subscribe()]; } @ViewChild('tooltip', { static: false }) set setTooltip(tooltip: NgbTooltip) { this.tooltip = tooltip; - this.clearSubs('tooltipBound'); + this.clearSubs(); if (this.tooltip !== undefined) { - this.subs.tooltipBound = [ + this.subs = [ this.contextHelpService.getContextHelp$(this.id) .pipe(hasValueOperator()) .subscribe((ch: ContextHelp) => { @@ -159,13 +157,8 @@ export class ContextHelpWrapperComponent implements OnInit, OnDestroy { }); } - private clearSubs(filter: null | 'tooltipBound' = null) { - if (filter === null) { - [].concat(...Object.values(this.subs)).forEach(sub => sub.unsubscribe()); - this.subs = {always: [], tooltipBound: []}; - } else { - this.subs[filter].forEach(sub => sub.unsubscribe()); - this.subs[filter] = []; - } + private clearSubs() { + this.subs.forEach(sub => sub.unsubscribe()); + this.subs = []; } } From 6bfcc25502394abf78d33020bca42e78fee9571f Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 8 Feb 2023 12:28:14 +0100 Subject: [PATCH 120/173] [CST-7757] subscriptions data service tests --- .../subscriptions-data.service.spec.ts | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/app/shared/subscriptions/subscriptions-data.service.spec.ts diff --git a/src/app/shared/subscriptions/subscriptions-data.service.spec.ts b/src/app/shared/subscriptions/subscriptions-data.service.spec.ts new file mode 100644 index 0000000000..9c4c69123d --- /dev/null +++ b/src/app/shared/subscriptions/subscriptions-data.service.spec.ts @@ -0,0 +1,133 @@ +import { SubscriptionsDataService } from './subscriptions-data.service'; +import { createNoContentRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; +import { Subscription } from './models/subscription.model'; +import { DSOChangeAnalyzer } from '../../core/data/dso-change-analyzer.service'; +import { HttpClient } from '@angular/common/http'; +import { NotificationsService } from '../notifications/notifications.service'; +import { RequestService } from '../../core/data/request.service'; +import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; +import { Store } from '@ngrx/store'; +import { ObjectCacheService } from '../../core/cache/object-cache.service'; +import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; +import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { getMockRequestService } from '../mocks/request.service.mock'; +import { getMockRemoteDataBuildService } from '../mocks/remote-data-build.service.mock'; +import { SearchDataImpl } from '../../core/data/base/search-data'; +import { NotificationsServiceStub } from '../testing/notifications-service.stub'; +import { HALEndpointServiceStub } from '../testing/hal-endpoint-service.stub'; +import { createPaginatedList } from '../testing/utils.test'; + + +describe('SubscriptionsDataService', () => { + + + let service: SubscriptionsDataService; + let searchData: SearchDataImpl; + + let comparator: DSOChangeAnalyzer; + let http: HttpClient; + let notificationsService: NotificationsService; + let requestService: RequestService; + let rdbService: RemoteDataBuildService; + let store: Store; + let objectCache: ObjectCacheService; + let halService: HALEndpointService; + let nameService: DSONameService; + + function initService() { + comparator = {} as any; + http = {} as HttpClient; + notificationsService = new NotificationsServiceStub() as any; + requestService = getMockRequestService(); + rdbService = getMockRemoteDataBuildService(); + halService = new HALEndpointServiceStub('linkPath') as any; + service = new SubscriptionsDataService(comparator, http, notificationsService, requestService, rdbService, store, objectCache, halService, nameService); + spyOn((service as any).deleteData, 'delete').and.returnValue(createNoContentRemoteDataObject$()); + } + + describe('createSubscription', () => { + + beforeEach(() => { + initService(); + }); + + it('should create the subscription', () => { + const id = 'test-id'; + const ePerson = 'test-ePerson'; + const subscription = new Subscription(); + service.createSubscription(subscription, ePerson, id).subscribe((res) => { + expect(requestService.generateRequestId).toHaveBeenCalled(); + expect(res.hasCompleted).toBeTrue(); + }); + }); + + }); + + describe('deleteSubscription', () => { + + beforeEach(() => { + initService(); + }); + + it('should delete the subscription', () => { + const id = 'test-id'; + service.deleteSubscription(id).subscribe((res) => { + expect((service as any).deleteData.delete).toHaveBeenCalledWith(id); + expect(res.hasCompleted).toBeTrue(); + }); + }); + + }); + + describe('updateSubscription', () => { + + beforeEach(() => { + initService(); + }); + + it('should update the subscription', () => { + const id = 'test-id'; + const ePerson = 'test-ePerson'; + const subscription = new Subscription(); + service.updateSubscription(subscription, ePerson, id).subscribe((res) => { + expect(requestService.generateRequestId).toHaveBeenCalled(); + expect(res.hasCompleted).toBeTrue(); + }); + }); + + }); + + describe('findByEPerson', () => { + + beforeEach(() => { + initService(); + }); + + it('should update the subscription', () => { + const ePersonId = 'test-ePersonId'; + spyOn(service, 'findListByHref').and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList())); + service.findByEPerson(ePersonId).subscribe((res) => { + expect(service.findListByHref).toHaveBeenCalled(); + expect(res.hasCompleted).toBeTrue(); + }); + }); + + }); + + describe('getSubscriptionsByPersonDSO', () => { + + beforeEach(() => { + initService(); + }); + + it('should get the subscriptions', () => { + const id = 'test-id'; + const ePersonId = 'test-ePersonId'; + service.getSubscriptionsByPersonDSO(ePersonId, id).subscribe(() => { + expect(searchData.searchBy).toHaveBeenCalled(); + }); + }); + + }); + +}); From b4acb5cbedbfcedc2f3c806600840e43edebc747 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 8 Feb 2023 15:07:02 +0100 Subject: [PATCH 121/173] Fixed text (without HTML tags) not being passed to child components --- src/app/shared/theme-support/themed.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/theme-support/themed.component.ts b/src/app/shared/theme-support/themed.component.ts index d75568e0ba..3b69be0916 100644 --- a/src/app/shared/theme-support/themed.component.ts +++ b/src/app/shared/theme-support/themed.component.ts @@ -91,7 +91,7 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges ).subscribe((constructor: GenericConstructor) => { const factory = this.resolver.resolveComponentFactory(constructor); - this.compRef = this.vcr.createComponent(factory, undefined, undefined, [this.themedElementContent.nativeElement.children]); + this.compRef = this.vcr.createComponent(factory, undefined, undefined, [this.themedElementContent.nativeElement.childNodes]); this.connectInputsAndOutputs(); this.cdr.markForCheck(); this.themedElementContent.nativeElement.remove(); From 04d0621b4ab68e70885bba39f2b9b7d40f3ff93f Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Wed, 8 Feb 2023 15:21:07 +0100 Subject: [PATCH 122/173] Fixed default @Input() values not working for themed components --- src/app/shared/theme-support/themed.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/theme-support/themed.component.ts b/src/app/shared/theme-support/themed.component.ts index 3b69be0916..b2a5d83c99 100644 --- a/src/app/shared/theme-support/themed.component.ts +++ b/src/app/shared/theme-support/themed.component.ts @@ -110,7 +110,7 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges protected connectInputsAndOutputs(): void { if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.forEach((name: any) => { + this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { this.compRef.instance[name] = this[name]; }); } From c8e7ac3e8c6e1789de4a856ebe9036b6fee03202 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 8 Feb 2023 18:26:37 +0100 Subject: [PATCH 123/173] [CST-7757] subscription modal tests --- .../subscription-modal.component.spec.ts | 78 +++++++++++++++++-- .../subscription-modal.component.ts | 6 +- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts index 221f70f77d..90f2e51e63 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { CommonModule } from '@angular/common'; -import { ReactiveFormsModule } from '@angular/forms'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; import { NgbActiveModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; @@ -24,11 +24,11 @@ describe('SubscriptionModalComponent', () => { let de: DebugElement; let subscriptionServiceStub; - const notificationServiceStub = { - notificationWithAnchor() { - return true; - } - }; + + const notificationServiceStub = jasmine.createSpyObj('authService', { + notificationWithAnchor: true, + success: undefined, + }); const emptyPageInfo = Object.assign(new PageInfo(), { 'elementsPerPage': 0, @@ -101,6 +101,72 @@ describe('SubscriptionModalComponent', () => { })); + describe('when submitting subscriptions', () => { + + const testSubscriptionId = 'test-subscription-id'; + const testTypes = ['test1', 'test2']; + const testFrequencies = ['f', 'g']; + + beforeEach(() => { + fixture = TestBed.createComponent(SubscriptionModalComponent); + component = fixture.componentInstance; + component.dso = mockItem; + (component as any).subscriptionDefaultTypes = testTypes; + (component as any).frequencyDefaultValues = testFrequencies; + de = fixture.debugElement; + subscriptionServiceStub.createSubscription.calls.reset(); + subscriptionServiceStub.updateSubscription.calls.reset(); + fixture.detectChanges(); + }); + + it('should edit an existing subscription', () => { + component.subscriptionForm = new FormGroup({}); + for (let t of testTypes) { + const formGroup = new FormGroup({ + subscriptionId: new FormControl(testSubscriptionId), + frequencies: new FormGroup({ + f: new FormControl(false), + g: new FormControl(true), + }) + }); + component.subscriptionForm.addControl(t, formGroup); + component.subscriptionForm.get('test1').markAsDirty(); + component.subscriptionForm.get('test1').markAsTouched(); + } + + fixture.detectChanges(); + component.submit(); + + expect(subscriptionServiceStub.createSubscription).not.toHaveBeenCalled(); + expect(subscriptionServiceStub.updateSubscription).toHaveBeenCalled(); + expect(component.subscriptionForm.controls).toBeTruthy(); + }); + + it('should create a new subscription', () => { + component.subscriptionForm = new FormGroup({}); + for (let t of testTypes) { + const formGroup = new FormGroup({ + subscriptionId: new FormControl(undefined), + frequencies: new FormGroup({ + f: new FormControl(false), + g: new FormControl(true), + }) + }); + component.subscriptionForm.addControl(t, formGroup); + component.subscriptionForm.get('test1').markAsDirty(); + component.subscriptionForm.get('test1').markAsTouched(); + } + + fixture.detectChanges(); + component.submit(); + + expect(subscriptionServiceStub.createSubscription).toHaveBeenCalled(); + expect(subscriptionServiceStub.updateSubscription).not.toHaveBeenCalled(); + expect(component.subscriptionForm.controls).toBeTruthy(); + }); + + }); + describe('when no subscription is given', () => { beforeEach(() => { fixture = TestBed.createComponent(SubscriptionModalComponent); diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts index 3d40c6f7e0..5de0426e0b 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.ts @@ -62,12 +62,12 @@ export class SubscriptionModalComponent implements OnInit { /** * Types of subscription to be shown on select */ - private subscriptionDefaultTypes = ['content']; + subscriptionDefaultTypes = ['content']; /** * Frequencies to be shown as checkboxes */ - private frequencyDefaultValues = ['D', 'W', 'M']; + frequencyDefaultValues = ['D', 'W', 'M']; /** * True if form status has changed and at least one frequency is checked @@ -115,7 +115,7 @@ export class SubscriptionModalComponent implements OnInit { }); } - private initFormByAllSubscriptions(): void { + initFormByAllSubscriptions(): void { this.subscriptionForm = new FormGroup({}); for (let t of this.subscriptionDefaultTypes) { const formGroup = new FormGroup({}); From 325d78dded116680a3cb88e813a5770aada65428 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Wed, 8 Feb 2023 19:38:42 +0100 Subject: [PATCH 124/173] [CST-7757] modal fixes --- .../subscription-modal.component.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html index e095e094dc..fbab2513cb 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html @@ -6,7 +6,9 @@
+ +

{{dso.name}}

+
{{subscription.subscriptionType}} diff --git a/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts b/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts index 00f0b8d22c..aa40f2bf43 100644 --- a/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts +++ b/src/app/shared/subscriptions/subscription-view/subscription-view.component.spec.ts @@ -94,7 +94,7 @@ describe('SubscriptionViewComponent', () => { }); it('should have dso object info', () => { - expect(de.query(By.css('.dso-info > span'))).toBeTruthy(); + expect(de.query(By.css('.dso-info > ds-type-badge'))).toBeTruthy(); expect(de.query(By.css('.dso-info > p > a'))).toBeTruthy(); }); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index be9ffc99ed..ba0200a892 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4589,6 +4589,8 @@ "subscriptions.modal.close": "Close", + "subscriptions.modal.delete-info": "To remove this subscription, please visit the \"Subscriptions\" page under your user profile", + "subscriptions.modal.new-subscription-form.type.content": "Content", "subscriptions.modal.new-subscription-form.frequency.D": "Daily", @@ -4619,8 +4621,7 @@ "subscriptions.table.action": "Action", - "subscriptions.table.empty.message": "You have not subscribed any notification yet. To subscribe notification about an object please use the contextual menu in the object detail view", - + "subscriptions.table.empty.message": "You do not have any subscriptions at this time. To subscribe to email updates for a Community or Collection, use the subscription button on the object's page.", "thumbnail.default.alt": "Thumbnail Image", From 0755e300e803247931b9b7fc2083edd94c746677 Mon Sep 17 00:00:00 2001 From: Giuseppe Digilio Date: Thu, 9 Feb 2023 11:41:14 +0100 Subject: [PATCH 126/173] [CST-7217] Add margins to better align badge --- .../search-facet-option/search-facet-option.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html index 4e03767b9f..9a4bffadb8 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html @@ -5,7 +5,7 @@ From 516b91f520f273bcfee62be6cd095bd27c6d3060 Mon Sep 17 00:00:00 2001 From: Davide Negretti Date: Thu, 9 Feb 2023 12:06:15 +0100 Subject: [PATCH 127/173] [CST-7757] Hide delete info for new subscriptions and fix issue with unavailable items --- .../subscription-modal.component.html | 2 +- .../subscription-modal.component.ts | 4 ++ .../subscription-view.component.html | 42 ++++++++++++------- src/assets/i18n/en.json5 | 8 ++++ 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html index 5cbfb138d4..0e723a8140 100644 --- a/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html +++ b/src/app/shared/subscriptions/subscription-modal/subscription-modal.component.html @@ -27,7 +27,7 @@ -

{{'subscriptions.modal.delete-info' | translate}}

+

{{'subscriptions.modal.delete-info' | translate}}

- {{subscription.subscriptionType}} + + +

{{dso.name}}

+
+ +

{{ 'subscriptions.table.not-available' | translate }}

+

{{ 'subscriptions.table.not-available-message' | translate }}

+
+
+ {{subscription.subscriptionType}} - + - {{ 'subscriptions.frequency.'+ parameterList.value | translate}}, + {{ 'subscriptions.frequency.' + parameterList.value | translate}}, - + -
- - -
+
+ + +
- - {{schema?.prefix}}.{{field.element}}{{field.qualifier}}{{schema?.prefix}}.{{field.element}}{{field.qualifier}} {{field.scopeNote}}
{{epersonDto.eperson.email}}
- diff --git a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts index c0d70fd0b2..4a09913862 100644 --- a/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts +++ b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts @@ -1,7 +1,7 @@ import { Router } from '@angular/router'; import { Observable, of as observableOf } from 'rxjs'; import { CommonModule } from '@angular/common'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BrowserModule, By } from '@angular/platform-browser'; @@ -42,6 +42,7 @@ describe('EPeopleRegistryComponent', () => { let paginationService; beforeEach(waitForAsync(() => { + jasmine.getEnv().allowRespy(true); mockEPeople = [EPersonMock, EPersonMock2]; ePersonDataServiceStub = { activeEPerson: null, @@ -98,7 +99,7 @@ describe('EPeopleRegistryComponent', () => { deleteEPerson(ePerson: EPerson): Observable { this.allEpeople = this.allEpeople.filter((ePerson2: EPerson) => { return (ePerson2.uuid !== ePerson.uuid); - }); + }); return observableOf(true); }, editEPerson(ePerson: EPerson) { @@ -260,17 +261,16 @@ describe('EPeopleRegistryComponent', () => { describe('delete EPerson button when the isAuthorized returns false', () => { let ePeopleDeleteButton; beforeEach(() => { - authorizationService = jasmine.createSpyObj('authorizationService', { - isAuthorized: observableOf(false) - }); + spyOn(authorizationService, 'isAuthorized').and.returnValue(observableOf(false)); + component.initialisePage(); + fixture.detectChanges(); }); it('should be disabled', () => { ePeopleDeleteButton = fixture.debugElement.queryAll(By.css('#epeople tr td div button.delete-button')); - ePeopleDeleteButton.forEach((deleteButton) => { + ePeopleDeleteButton.forEach((deleteButton: DebugElement) => { expect(deleteButton.nativeElement.disabled).toBe(true); }); - }); }); }); diff --git a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html index 3be45c4452..d1574b0dba 100644 --- a/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html +++ b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html @@ -65,7 +65,7 @@ -

{{ messagePrefix + '.table.edit.currentGroup' | translate }}

+ {{ messagePrefix + '.table.edit.currentGroup' | translate }}
@@ -17,7 +17,7 @@
-