Merge pull request #3014 from atmire/w2p-113904_AddSupportForNullUsers

Handle Null Users Gracefully in Process Overview Page
This commit is contained in:
Tim Donohue
2024-05-08 14:07:15 -05:00
committed by GitHub
4 changed files with 105 additions and 14 deletions

View File

@@ -177,5 +177,27 @@ export class ProcessFormComponent implements OnInit {
}; };
void this.router.navigate([getProcessListRoute()], extras); void this.router.navigate([getProcessListRoute()], extras);
} }
}
updateScript($event: Script) {
this.selectedScript = $event;
this.parameters = undefined;
}
get generatedProcessName() {
const paramsString = this.parameters?.map((p: ProcessParameter) => {
const value = this.parseValue(p.value);
return isEmpty(value) ? p.name : `${p.name} ${value}`;
}).join(' ') || '';
return isEmpty(paramsString) ? this.selectedScript.name : `${this.selectedScript.name} ${paramsString}`;
}
private parseValue(value: any) {
if (typeof value === 'boolean') {
return undefined;
}
if (value instanceof File) {
return value.name;
}
return value?.toString();
}
}

View File

@@ -10,7 +10,10 @@ import {
NgbCollapse, NgbCollapse,
NgbModal, NgbModal,
} from '@ng-bootstrap/ng-bootstrap'; } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core'; import {
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { BehaviorSubject } from 'rxjs'; import { BehaviorSubject } from 'rxjs';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
@@ -50,6 +53,8 @@ describe('ProcessOverviewTableComponent', () => {
let processes: Process[]; let processes: Process[];
let ePerson: EPerson; let ePerson: EPerson;
let translateServiceSpy: jasmine.SpyObj<TranslateService>;
function init() { function init() {
processes = [ processes = [
Object.assign(new Process(), { Object.assign(new Process(), {
@@ -58,6 +63,7 @@ describe('ProcessOverviewTableComponent', () => {
startTime: '2020-03-19 00:30:00', startTime: '2020-03-19 00:30:00',
endTime: '2020-03-19 23:30:00', endTime: '2020-03-19 23:30:00',
processStatus: ProcessStatus.COMPLETED, processStatus: ProcessStatus.COMPLETED,
userId: 'testid',
}), }),
Object.assign(new Process(), { Object.assign(new Process(), {
processId: 2, processId: 2,
@@ -65,6 +71,7 @@ describe('ProcessOverviewTableComponent', () => {
startTime: '2020-03-20 00:30:00', startTime: '2020-03-20 00:30:00',
endTime: '2020-03-20 23:30:00', endTime: '2020-03-20 23:30:00',
processStatus: ProcessStatus.FAILED, processStatus: ProcessStatus.FAILED,
userId: 'testid',
}), }),
Object.assign(new Process(), { Object.assign(new Process(), {
processId: 3, processId: 3,
@@ -72,9 +79,12 @@ describe('ProcessOverviewTableComponent', () => {
startTime: '2020-03-21 00:30:00', startTime: '2020-03-21 00:30:00',
endTime: '2020-03-21 23:30:00', endTime: '2020-03-21 23:30:00',
processStatus: ProcessStatus.RUNNING, processStatus: ProcessStatus.RUNNING,
userId: 'testid',
}), }),
]; ];
ePerson = Object.assign(new EPerson(), { ePerson = Object.assign(new EPerson(), {
id: 'testid',
uuid: 'testid',
metadata: { metadata: {
'eperson.firstname': [ 'eperson.firstname': [
{ {
@@ -133,6 +143,8 @@ describe('ProcessOverviewTableComponent', () => {
beforeEach(waitForAsync(() => { beforeEach(waitForAsync(() => {
init(); init();
translateServiceSpy = jasmine.createSpyObj('TranslateService', ['get']);
void TestBed.configureTestingModule({ void TestBed.configureTestingModule({
declarations: [NgbCollapse], declarations: [NgbCollapse],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), VarDirective, ProcessOverviewTableComponent], imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), VarDirective, ProcessOverviewTableComponent],
@@ -217,4 +229,43 @@ describe('ProcessOverviewTableComponent', () => {
}); });
}); });
describe('getEPersonName function', () => {
it('should return unknown user when id is null', (done: DoneFn) => {
const id = null;
const expectedTranslation = 'process.overview.unknown.user';
translateServiceSpy.get(expectedTranslation);
component.getEPersonName(id).subscribe((result: string) => {
expect(result).toBe(expectedTranslation);
done();
});
expect(translateServiceSpy.get).toHaveBeenCalledWith('process.overview.unknown.user');
});
it('should return unknown user when id is invalid', (done: DoneFn) => {
const id = '';
const expectedTranslation = 'process.overview.unknown.user';
translateServiceSpy.get(expectedTranslation);
component.getEPersonName(id).subscribe((result: string) => {
expect(result).toBe(expectedTranslation);
done();
});
expect(translateServiceSpy.get).toHaveBeenCalledWith('process.overview.unknown.user');
});
it('should return EPerson name when id is correct', (done: DoneFn) => {
const id = 'testid';
const expectedName = 'John Doe';
component.getEPersonName(id).subscribe((result: string) => {
expect(result).toEqual(expectedName);
done();
});
expect(translateServiceSpy.get).not.toHaveBeenCalled();
});
});
}); });

View File

@@ -18,7 +18,10 @@ import {
RouterLink, RouterLink,
} from '@angular/router'; } from '@angular/router';
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'; import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core'; import {
TranslateModule,
TranslateService,
} from '@ngx-translate/core';
import { import {
BehaviorSubject, BehaviorSubject,
from as observableFrom, from as observableFrom,
@@ -46,9 +49,12 @@ import { RouteService } from '../../../core/services/route.service';
import { redirectOn4xx } from '../../../core/shared/authorized.operators'; import { redirectOn4xx } from '../../../core/shared/authorized.operators';
import { import {
getAllCompletedRemoteData, getAllCompletedRemoteData,
getFirstSucceededRemoteDataPayload, getFirstCompletedRemoteData,
} from '../../../core/shared/operators'; } from '../../../core/shared/operators';
import { hasValue } from '../../../shared/empty.util'; import {
hasValue,
isNotEmpty,
} from '../../../shared/empty.util';
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component'; import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { PaginationComponent } from '../../../shared/pagination/pagination.component';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
@@ -153,12 +159,13 @@ export class ProcessOverviewTableComponent implements OnInit, OnDestroy {
constructor(protected processOverviewService: ProcessOverviewService, constructor(protected processOverviewService: ProcessOverviewService,
protected processBulkDeleteService: ProcessBulkDeleteService, protected processBulkDeleteService: ProcessBulkDeleteService,
protected ePersonDataService: EPersonDataService, public ePersonDataService: EPersonDataService,
protected dsoNameService: DSONameService, public dsoNameService: DSONameService,
protected paginationService: PaginationService, protected paginationService: PaginationService,
protected routeService: RouteService, protected routeService: RouteService,
protected router: Router, protected router: Router,
protected auth: AuthService, protected auth: AuthService,
private translateService: TranslateService,
@Inject(PLATFORM_ID) protected platformId: object, @Inject(PLATFORM_ID) protected platformId: object,
) { ) {
} }
@@ -176,7 +183,7 @@ export class ProcessOverviewTableComponent implements OnInit, OnDestroy {
// Creates an ID from the first 2 characters of the process status. // Creates an ID from the first 2 characters of the process status.
// Should two process status values ever start with the same substring, // Should two process status values ever start with the same substring,
// increase the number of characters until the ids are distinct. // increase the number of characters until the ids are distinct.
this.paginationId = this.processStatus.toLowerCase().substring(0,2); this.paginationId = this.processStatus.toLowerCase().substring(0, 2);
const defaultPaginationOptions = Object.assign(new PaginationComponentOptions(), { const defaultPaginationOptions = Object.assign(new PaginationComponentOptions(), {
id: this.paginationId, id: this.paginationId,
@@ -218,7 +225,7 @@ export class ProcessOverviewTableComponent implements OnInit, OnDestroy {
// Map RemoteData<PaginatedList<Process>> to RemoteData<PaginatedList<ProcessOverviewTableEntry>> // Map RemoteData<PaginatedList<Process>> to RemoteData<PaginatedList<ProcessOverviewTableEntry>>
switchMap((processesRD: RemoteData<PaginatedList<Process>>) => { switchMap((processesRD: RemoteData<PaginatedList<Process>>) => {
// Create observable emitting all processes one by one // Create observable emitting all processes one by one
return observableFrom(processesRD.payload.page).pipe( return observableFrom(processesRD.payload.page).pipe(
// Map every Process to ProcessOverviewTableEntry // Map every Process to ProcessOverviewTableEntry
mergeMap((process: Process) => { mergeMap((process: Process) => {
return this.getEPersonName(process.userId).pipe( return this.getEPersonName(process.userId).pipe(
@@ -243,7 +250,6 @@ export class ProcessOverviewTableComponent implements OnInit, OnDestroy {
}), }),
); );
}), }),
).subscribe((next: RemoteData<PaginatedList<ProcessOverviewTableEntry>>) => { ).subscribe((next: RemoteData<PaginatedList<ProcessOverviewTableEntry>>) => {
this.processesRD$.next(next); this.processesRD$.next(next);
})); }));
@@ -267,10 +273,20 @@ export class ProcessOverviewTableComponent implements OnInit, OnDestroy {
* @param id ID of the EPerson * @param id ID of the EPerson
*/ */
getEPersonName(id: string): Observable<string> { getEPersonName(id: string): Observable<string> {
return this.ePersonDataService.findById(id).pipe( if (isNotEmpty(id)) {
getFirstSucceededRemoteDataPayload(), return this.ePersonDataService.findById(id).pipe(
map((eperson: EPerson) => this.dsoNameService.getName(eperson)), getFirstCompletedRemoteData(),
); switchMap((rd: RemoteData<EPerson>) => {
if (rd.hasSucceeded) {
return [this.dsoNameService.getName(rd.payload)];
} else {
return this.translateService.get('process.overview.unknown.user');
}
}),
);
} else {
return this.translateService.get('process.overview.unknown.user');
}
} }
/** /**

View File

@@ -3846,6 +3846,8 @@
"process.overview.delete.header": "Delete processes", "process.overview.delete.header": "Delete processes",
"process.overview.unknown.user": "Unknown",
"process.bulk.delete.error.head": "Error on deleteing process", "process.bulk.delete.error.head": "Error on deleteing process",
"process.bulk.delete.error.body": "The process with ID {{processId}} could not be deleted. The remaining processes will continue being deleted. ", "process.bulk.delete.error.body": "The process with ID {{processId}} could not be deleted. The remaining processes will continue being deleted. ",