mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
72403: Process output logs only retrieved at button press + tests
This commit is contained in:
@@ -34,13 +34,19 @@
|
||||
<div>{{ process.processStatus }}</div>
|
||||
</ds-process-detail-field>
|
||||
|
||||
<ds-process-detail-field id="process-output" [title]="'process.detail.output'">
|
||||
<pre class="font-weight-bold text-secondary bg-light p-3"
|
||||
*ngIf="(outputLogs$ | async)?.length > 0">{{ (outputLogs$ | async)?.join('\n') }}</pre>
|
||||
<p id="no-output-logs-message" *ngIf="!(outputLogs$ | async) || (outputLogs$ | async).length == 0">
|
||||
{{ 'process.detail.logs.none' | translate }}
|
||||
</p>
|
||||
</ds-process-detail-field>
|
||||
<ds-process-detail-field id="process-output" [title]="'process.detail.output'">
|
||||
<button *ngIf="!showOutputLogs" id="showOutputButton" class="btn btn-light" (click)="showProcessOutputLogs()">
|
||||
{{ 'process.detail.logs.button' | translate }}
|
||||
</button>
|
||||
<ds-loading *ngIf="retrievingOutputLogs$ | async" class="ds-loading" message="{{ 'process.detail.logs.loading' | translate }}"></ds-loading>
|
||||
<pre class="font-weight-bold text-secondary bg-light p-3"
|
||||
*ngIf="showOutputLogs && (outputLogs$ | async)?.length > 0">{{ (outputLogs$ | async)?.join('\n') }}</pre>
|
||||
<p id="no-output-logs-message" *ngIf="showOutputLogs && !(outputLogs$ | async) || (outputLogs$ | async)?.length == 0">
|
||||
{{ 'process.detail.logs.none' | translate }}
|
||||
</p>
|
||||
</ds-process-detail-field>
|
||||
|
||||
<a class="btn btn-light mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
|
||||
<div>
|
||||
<a class="btn btn-light mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { ProcessOutputDataService } from '../../core/data/process-output-data.service';
|
||||
import { ProcessOutput } from '../processes/process-output.model';
|
||||
import { ProcessDetailComponent } from './process-detail.component';
|
||||
import { async, ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
|
||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
@@ -93,7 +93,10 @@ describe('ProcessDetailComponent', () => {
|
||||
declarations: [ProcessDetailComponent, ProcessDetailFieldComponent, VarDirective, FileSizePipe],
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: { data: observableOf({ process: createSuccessfulRemoteDataObject(process) }) } },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: { data: observableOf({ process: createSuccessfulRemoteDataObject(process) }) }
|
||||
},
|
||||
{ provide: ProcessDataService, useValue: processService },
|
||||
{ provide: ProcessOutputDataService, useValue: processOutputService },
|
||||
{ provide: DSONameService, useValue: nameService }
|
||||
@@ -105,15 +108,16 @@ describe('ProcessDetailComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProcessDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should display the script\'s name', () => {
|
||||
fixture.detectChanges();
|
||||
const name = fixture.debugElement.query(By.css('#process-name')).nativeElement;
|
||||
expect(name.textContent).toContain(process.scriptName);
|
||||
});
|
||||
|
||||
it('should display the process\'s parameters', () => {
|
||||
fixture.detectChanges();
|
||||
const args = fixture.debugElement.query(By.css('#process-arguments')).nativeElement;
|
||||
process.parameters.forEach((param) => {
|
||||
expect(args.textContent).toContain(`${param.name} ${param.value}`)
|
||||
@@ -121,27 +125,52 @@ describe('ProcessDetailComponent', () => {
|
||||
});
|
||||
|
||||
it('should display the process\'s output files', () => {
|
||||
fixture.detectChanges();
|
||||
const processFiles = fixture.debugElement.query(By.css('#process-files')).nativeElement;
|
||||
expect(processFiles.textContent).toContain(fileName);
|
||||
});
|
||||
|
||||
it('should display the process\'s output logs', () => {
|
||||
const outputProcess = fixture.debugElement.query(By.css('#process-output pre')).nativeElement;
|
||||
expect(outputProcess.textContent).toContain('Process started');
|
||||
describe('if press show output logs', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
spyOn(component, 'showProcessOutputLogs').and.callThrough();
|
||||
fixture.detectChanges();
|
||||
const showOutputButton = fixture.debugElement.query(By.css('#showOutputButton'));
|
||||
showOutputButton.triggerEventHandler('click', {
|
||||
preventDefault: () => {/**/
|
||||
}
|
||||
});
|
||||
tick();
|
||||
}));
|
||||
it('should trigger showProcessOutputLogs', () => {
|
||||
expect(component.showProcessOutputLogs).toHaveBeenCalled();
|
||||
});
|
||||
it('should display the process\'s output logs', () => {
|
||||
fixture.detectChanges();
|
||||
const outputProcess = fixture.debugElement.query(By.css('#process-output pre'));
|
||||
expect(outputProcess.nativeElement.textContent).toContain('Process started');
|
||||
});
|
||||
});
|
||||
|
||||
describe('if process has no output logs (yet)', () => {
|
||||
describe('if press show output logs and process has no output logs (yet)', () => {
|
||||
beforeEach(fakeAsync(() => {
|
||||
jasmine.getEnv().allowRespy(true);
|
||||
const emptyProcessOutput = Object.assign(new ProcessOutput(), {
|
||||
logs: []
|
||||
});
|
||||
spyOn(processOutputService, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(emptyProcessOutput));
|
||||
fixture = TestBed.createComponent(ProcessDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
})
|
||||
);
|
||||
jasmine.getEnv().allowRespy(true);
|
||||
const emptyProcessOutput = Object.assign(new ProcessOutput(), {
|
||||
logs: []
|
||||
});
|
||||
spyOn(processOutputService, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(emptyProcessOutput));
|
||||
fixture = TestBed.createComponent(ProcessDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
spyOn(component, 'showProcessOutputLogs').and.callThrough();
|
||||
fixture.detectChanges();
|
||||
const showOutputButton = fixture.debugElement.query(By.css('#showOutputButton'));
|
||||
showOutputButton.triggerEventHandler('click', {
|
||||
preventDefault: () => {/**/
|
||||
}
|
||||
});
|
||||
tick();
|
||||
fixture.detectChanges();
|
||||
}));
|
||||
it('should not display the process\'s output logs', () => {
|
||||
const outputProcess = fixture.debugElement.query(By.css('#process-output pre'));
|
||||
expect(outputProcess).toBeNull();
|
||||
@@ -150,7 +179,6 @@ describe('ProcessDetailComponent', () => {
|
||||
const noOutputProcess = fixture.debugElement.query(By.css('#no-output-logs-message')).nativeElement;
|
||||
expect(noOutputProcess).toBeDefined();
|
||||
});
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, NgZone, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { ProcessOutputDataService } from '../../core/data/process-output-data.service';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { ProcessOutput } from '../processes/process-output.model';
|
||||
import { Process } from '../processes/process.model';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { finalize, map, switchMap, take } from 'rxjs/operators';
|
||||
import { getFirstSucceededRemoteDataPayload, redirectToPageNotFoundOn404 } from '../../core/shared/operators';
|
||||
import { AlertType } from '../../shared/alert/aletr-type';
|
||||
import { ProcessDataService } from '../../core/data/processes/process-data.service';
|
||||
@@ -44,11 +45,21 @@ export class ProcessDetailComponent implements OnInit {
|
||||
*/
|
||||
outputLogs$: Observable<string[]>;
|
||||
|
||||
/**
|
||||
* Boolean on whether or not to show the output logs
|
||||
*/
|
||||
showOutputLogs = false;
|
||||
/**
|
||||
* When it's retrieving the output logs from backend, to show loading component
|
||||
*/
|
||||
retrievingOutputLogs$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
constructor(protected route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
protected processService: ProcessDataService,
|
||||
protected processOutputService: ProcessOutputDataService,
|
||||
protected nameService: DSONameService) {
|
||||
protected nameService: DSONameService,
|
||||
private zone: NgZone) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,17 +76,6 @@ export class ProcessDetailComponent implements OnInit {
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
switchMap((process: Process) => this.processService.getFiles(process.processId))
|
||||
);
|
||||
|
||||
const processOutputRD$: Observable<RemoteData<ProcessOutput>> = this.processRD$.pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
switchMap((process: Process) => this.processOutputService.findByHref(process._links.output.href))
|
||||
);
|
||||
this.outputLogs$ = processOutputRD$.pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
map((processOutput: ProcessOutput) => {
|
||||
return processOutput.logs;
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,4 +86,27 @@ export class ProcessDetailComponent implements OnInit {
|
||||
return bitstream instanceof DSpaceObject ? this.nameService.getName(bitstream) : 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the process logs, while setting the loading subject to true.
|
||||
* Sets the outputLogs when retrieved and sets the showOutputLogs boolean to show them and hide the button.
|
||||
*/
|
||||
showProcessOutputLogs() {
|
||||
this.retrievingOutputLogs$.next(true);
|
||||
this.zone.runOutsideAngular(() => {
|
||||
const processOutputRD$: Observable<RemoteData<ProcessOutput>> = this.processRD$.pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
switchMap((process: Process) => this.processOutputService.findByHref(process._links.output.href))
|
||||
);
|
||||
this.outputLogs$ = processOutputRD$.pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
map((processOutput: ProcessOutput) => {
|
||||
this.showOutputLogs = true;
|
||||
return processOutput.logs;
|
||||
}),
|
||||
finalize(() => this.zone.run(() => this.retrievingOutputLogs$.next(false))),
|
||||
)
|
||||
});
|
||||
this.outputLogs$.pipe(take(1)).subscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ export class ProcessPageResolver implements Resolve<RemoteData<Process>> {
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Process>> {
|
||||
return this.processService.findById(route.params.id, followLink('script'), followLink('output') ).pipe(
|
||||
return this.processService.findById(route.params.id, followLink('script')).pipe(
|
||||
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
|
||||
);
|
||||
}
|
||||
|
@@ -2085,6 +2085,10 @@
|
||||
|
||||
"process.detail.output" : "Process Output",
|
||||
|
||||
"process.detail.logs.button": "Retrieve process output",
|
||||
|
||||
"process.detail.logs.loading": "Retrieving",
|
||||
|
||||
"process.detail.logs.none": "This process has no output yet",
|
||||
|
||||
"process.detail.output-files" : "Output Files",
|
||||
|
Reference in New Issue
Block a user