From 8325a34910aea6be0de0e4b61ba8a6df3a5fe17c Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 4 Aug 2020 14:15:25 +0200 Subject: [PATCH 1/5] 72403: Support for process output on detail page --- src/app/core/core.module.ts | 4 + .../core/data/process-output-data.service.ts | 73 +++++++++++++++++++ .../shared/process-output.resource-type.ts | 9 +++ .../detail/process-detail.component.html | 13 +++- .../detail/process-detail.component.spec.ts | 53 +++++++++++++- .../detail/process-detail.component.ts | 24 +++++- src/app/process-page/process-page.resolver.ts | 2 +- .../processes/process-output.model.ts | 36 +++++++++ .../process-page/processes/process.model.ts | 9 +++ src/assets/i18n/en.json5 | 2 +- 10 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 src/app/core/data/process-output-data.service.ts create mode 100644 src/app/core/shared/process-output.resource-type.ts create mode 100644 src/app/process-page/processes/process-output.model.ts diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 5aa462d5e0e..d262bfd0d67 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -6,6 +6,7 @@ import { EffectsModule } from '@ngrx/effects'; import { Action, StoreConfig, StoreModule } from '@ngrx/store'; import { MyDSpaceGuard } from '../+my-dspace-page/my-dspace.guard'; +import { ProcessOutput } from '../process-page/processes/process-output.model'; import { isNotEmpty } from '../shared/empty.util'; import { FormBuilderService } from '../shared/form/builder/form-builder.service'; @@ -70,6 +71,7 @@ import { LookupRelationService } from './data/lookup-relation.service'; import { MappedCollectionsReponseParsingService } from './data/mapped-collections-reponse-parsing.service'; import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing.service'; import { ObjectUpdatesService } from './data/object-updates/object-updates.service'; +import { ProcessOutputDataService } from './data/process-output-data.service'; import { RelationshipTypeService } from './data/relationship-type.service'; import { RelationshipService } from './data/relationship.service'; import { ResourcePolicyService } from './resource-policy/resource-policy.service'; @@ -281,6 +283,7 @@ const PROVIDERS = [ ItemTypeDataService, WorkflowActionDataService, ProcessDataService, + ProcessOutputDataService, ScriptDataService, ProcessFilesResponseParsingService, FeatureDataService, @@ -347,6 +350,7 @@ export const models = ExternalSourceEntry, Script, Process, + ProcessOutput, Version, VersionHistory, WorkflowAction, diff --git a/src/app/core/data/process-output-data.service.ts b/src/app/core/data/process-output-data.service.ts new file mode 100644 index 00000000000..28adbcd6cab --- /dev/null +++ b/src/app/core/data/process-output-data.service.ts @@ -0,0 +1,73 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { ProcessOutput } from '../../process-page/processes/process-output.model'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; +import { dataService } from '../cache/builders/build-decorators'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { CoreState } from '../core.reducers'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { PROCESS_OUTPUT_TYPE } from '../shared/process-output.resource-type'; +import { DataService } from './data.service'; +import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; +import { RemoteData } from './remote-data'; +import { RequestService } from './request.service'; + +/* tslint:disable:max-classes-per-file */ +/** + * A private DataService implementation to delegate specific methods to. + */ +class DataServiceImpl extends DataService { + protected linkPath = 'processes'; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer) { + super(); + } +} + +// @ts-ignore +/** + * A service to retrieve output from processes from the REST API. + */ +@Injectable() +@dataService(PROCESS_OUTPUT_TYPE) +export class ProcessOutputDataService { + /** + * A private DataService instance to delegate specific methods to. + */ + private dataService: DataServiceImpl; + + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer) { + this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator); + } + + /** + * Returns an observable of {@link RemoteData} of a {@link ProcessOutput}, based on an href, with a list of {@link FollowLinkConfig}, + * to automatically resolve {@link HALLink}s of the {@link ProcessOutput} + * @param href The url of {@link ProcessOutput} we want to retrieve + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved + */ + findByHref(href: string, ...linksToFollow: Array>): Observable> { + return this.dataService.findByHref(href, ...linksToFollow); + } +} +/* tslint:enable:max-classes-per-file */ diff --git a/src/app/core/shared/process-output.resource-type.ts b/src/app/core/shared/process-output.resource-type.ts new file mode 100644 index 00000000000..2e707d0bdad --- /dev/null +++ b/src/app/core/shared/process-output.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from './resource-type'; + +/** + * The resource type for ProcessOutput + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const PROCESS_OUTPUT_TYPE = new ResourceType('processOutput'); diff --git a/src/app/process-page/detail/process-detail.component.html b/src/app/process-page/detail/process-detail.component.html index 9cb1f1e6afa..078c79a0d5b 100644 --- a/src/app/process-page/detail/process-detail.component.html +++ b/src/app/process-page/detail/process-detail.component.html @@ -34,9 +34,16 @@

{{'process.detail.title' | translate:{id: process?.proce
{{ process.processStatus }}
- - - + +
+      
+        {{ (outputLogs$ | async)?.join('\n\t') }}
+      
+    
+

+ {{ 'process.detail.logs.none' | translate }} +

+
{{'process.detail.back' | translate}} diff --git a/src/app/process-page/detail/process-detail.component.spec.ts b/src/app/process-page/detail/process-detail.component.spec.ts index dff481fdc61..c1efd233e83 100644 --- a/src/app/process-page/detail/process-detail.component.spec.ts +++ b/src/app/process-page/detail/process-detail.component.spec.ts @@ -1,5 +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, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { VarDirective } from '../../shared/utils/var.directive'; import { TranslateModule } from '@ngx-translate/core'; import { RouterTestingModule } from '@angular/router/testing'; @@ -21,13 +23,20 @@ describe('ProcessDetailComponent', () => { let fixture: ComponentFixture; let processService: ProcessDataService; + let processOutputService: ProcessOutputDataService; let nameService: DSONameService; let process: Process; let fileName: string; let files: Bitstream[]; + let processOutput; + function init() { + processOutput = Object.assign(new ProcessOutput(), { + logs: ['Process started', 'Process completed'] + } + ); process = Object.assign(new Process(), { processId: 1, scriptName: 'script-name', @@ -40,7 +49,15 @@ describe('ProcessDetailComponent', () => { name: '-i', value: 'identifier' } - ] + ], + _links: { + self: { + href: 'https://rest.api/processes/1' + }, + output: { + href: 'https://rest.api/processes/1/output' + } + } }); fileName = 'fake-file-name'; files = [ @@ -62,6 +79,9 @@ describe('ProcessDetailComponent', () => { processService = jasmine.createSpyObj('processService', { getFiles: createSuccessfulRemoteDataObject$(createPaginatedList(files)) }); + processOutputService = jasmine.createSpyObj('processOutputService', { + findByHref: createSuccessfulRemoteDataObject$(processOutput) + }); nameService = jasmine.createSpyObj('nameService', { getName: fileName }); @@ -75,6 +95,7 @@ describe('ProcessDetailComponent', () => { providers: [ { provide: ActivatedRoute, useValue: { data: observableOf({ process: createSuccessfulRemoteDataObject(process) }) } }, { provide: ProcessDataService, useValue: processService }, + { provide: ProcessOutputDataService, useValue: processOutputService }, { provide: DSONameService, useValue: nameService } ], schemas: [NO_ERRORS_SCHEMA] @@ -104,4 +125,32 @@ describe('ProcessDetailComponent', () => { 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 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(); + }) + ); + it('should not display the process\'s output logs', () => { + const outputProcess = fixture.debugElement.query(By.css('#process-output pre')); + expect(outputProcess).toBeNull(); + }); + it('should display message saying there are no output logs', () => { + const noOutputProcess = fixture.debugElement.query(By.css('#no-output-logs-message')).nativeElement; + expect(noOutputProcess).toBeDefined(); + }); + } + ) + }); diff --git a/src/app/process-page/detail/process-detail.component.ts b/src/app/process-page/detail/process-detail.component.ts index b0e2c7e378c..74027a4e72a 100644 --- a/src/app/process-page/detail/process-detail.component.ts +++ b/src/app/process-page/detail/process-detail.component.ts @@ -1,9 +1,12 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; 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 { map, switchMap, tap } 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'; @@ -36,9 +39,15 @@ export class ProcessDetailComponent implements OnInit { */ filesRD$: Observable>>; + /** + * The Process's Output logs + */ + outputLogs$: Observable; + constructor(protected route: ActivatedRoute, protected router: Router, protected processService: ProcessDataService, + protected processOutputService: ProcessOutputDataService, protected nameService: DSONameService) { } @@ -56,6 +65,17 @@ export class ProcessDetailComponent implements OnInit { getFirstSucceededRemoteDataPayload(), switchMap((process: Process) => this.processService.getFiles(process.processId)) ); + + const processOutputRD$: Observable> = this.processRD$.pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((process: Process) => this.processOutputService.findByHref(process._links.output.href)) + ); + this.outputLogs$ = processOutputRD$.pipe( + getFirstSucceededRemoteDataPayload(), + switchMap((processOutput: ProcessOutput) => { + return [processOutput.logs]; + }) + ) } /** @@ -63,7 +83,7 @@ export class ProcessDetailComponent implements OnInit { * @param bitstream */ getFileName(bitstream: Bitstream) { - return this.nameService.getName(bitstream); + return bitstream instanceof DSpaceObject ? this.nameService.getName(bitstream) : 'unknown'; } } diff --git a/src/app/process-page/process-page.resolver.ts b/src/app/process-page/process-page.resolver.ts index 84821a2574c..57c749e1cbb 100644 --- a/src/app/process-page/process-page.resolver.ts +++ b/src/app/process-page/process-page.resolver.ts @@ -24,7 +24,7 @@ export class ProcessPageResolver implements Resolve> { * or an error if something went wrong */ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { - return this.processService.findById(route.params.id, followLink('script')).pipe( + return this.processService.findById(route.params.id, followLink('script'), followLink('output') ).pipe( find((RD) => hasValue(RD.error) || RD.hasSucceeded), ); } diff --git a/src/app/process-page/processes/process-output.model.ts b/src/app/process-page/processes/process-output.model.ts new file mode 100644 index 00000000000..4ae1731d26a --- /dev/null +++ b/src/app/process-page/processes/process-output.model.ts @@ -0,0 +1,36 @@ +import { PROCESS_OUTPUT_TYPE } from '../../core/shared/process-output.resource-type'; +import { CacheableObject } from '../../core/cache/object-cache.reducer'; +import { HALLink } from '../../core/shared/hal-link.model'; +import { autoserialize, deserialize } from 'cerialize'; +import { excludeFromEquals } from '../../core/utilities/equals.decorators'; +import { ResourceType } from '../../core/shared/resource-type'; +import { typedObject } from '../../core/cache/builders/build-decorators'; + +/** + * Object representing a process output object + */ +@typedObject +export class ProcessOutput implements CacheableObject { + static type = PROCESS_OUTPUT_TYPE; + + /** + * The object type + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The output strings for this ProcessOutput + */ + @autoserialize + logs: string[]; + + /** + * The {@link HALLink}s for this ProcessOutput + */ + @deserialize + _links: { + self: HALLink, + }; +} diff --git a/src/app/process-page/processes/process.model.ts b/src/app/process-page/processes/process.model.ts index 85de5337e78..891acb626dd 100644 --- a/src/app/process-page/processes/process.model.ts +++ b/src/app/process-page/processes/process.model.ts @@ -1,3 +1,5 @@ +import { PROCESS_OUTPUT_TYPE } from '../../core/shared/process-output.resource-type'; +import { ProcessOutput } from './process-output.model'; import { ProcessStatus } from './process-status.model'; import { ProcessParameter } from './process-parameter.model'; import { CacheableObject } from '../../core/cache/object-cache.reducer'; @@ -85,4 +87,11 @@ export class Process implements CacheableObject { */ @link(SCRIPT) script?: Observable>; + + /** + * The output logs created by this Process + * Will be undefined unless the output {@link HALLink} has been resolved. + */ + @link(PROCESS_OUTPUT_TYPE) + output?: Observable>; } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index bfa1c81aa69..69e7df151ea 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2085,7 +2085,7 @@ "process.detail.output" : "Process Output", - "process.detail.output.alert" : "Work in progress - Process output is not available yet", + "process.detail.logs.none": "This process has no output logs (yet)", "process.detail.output-files" : "Output Files", From f2a381643028d4027b03df6f9123eda50aa2a321 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 4 Aug 2020 14:57:57 +0200 Subject: [PATCH 2/5] 72403: tab removed process output, message change & redundant switchmap removed --- src/app/process-page/detail/process-detail.component.html | 7 ++----- src/app/process-page/detail/process-detail.component.ts | 6 +++--- src/assets/i18n/en.json5 | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/app/process-page/detail/process-detail.component.html b/src/app/process-page/detail/process-detail.component.html index 078c79a0d5b..e76d24d17aa 100644 --- a/src/app/process-page/detail/process-detail.component.html +++ b/src/app/process-page/detail/process-detail.component.html @@ -35,11 +35,8 @@

{{'process.detail.title' | translate:{id: process?.proce -
-      
-        {{ (outputLogs$ | async)?.join('\n\t') }}
-      
-    
+
{{ (outputLogs$ | async)?.join('\n') }}

{{ 'process.detail.logs.none' | translate }}

diff --git a/src/app/process-page/detail/process-detail.component.ts b/src/app/process-page/detail/process-detail.component.ts index 74027a4e72a..af6e3238a62 100644 --- a/src/app/process-page/detail/process-detail.component.ts +++ b/src/app/process-page/detail/process-detail.component.ts @@ -6,7 +6,7 @@ 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, tap } from 'rxjs/operators'; +import { map, switchMap } 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'; @@ -72,8 +72,8 @@ export class ProcessDetailComponent implements OnInit { ); this.outputLogs$ = processOutputRD$.pipe( getFirstSucceededRemoteDataPayload(), - switchMap((processOutput: ProcessOutput) => { - return [processOutput.logs]; + map((processOutput: ProcessOutput) => { + return processOutput.logs; }) ) } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 69e7df151ea..75db3eceb18 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2085,7 +2085,7 @@ "process.detail.output" : "Process Output", - "process.detail.logs.none": "This process has no output logs (yet)", + "process.detail.logs.none": "This process has no output yet", "process.detail.output-files" : "Output Files", From 02c693363b6f5ed28202898a2cc9c5c21c269f51 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 13 Aug 2020 12:31:42 +0200 Subject: [PATCH 3/5] Unneeded ts-ignore removed --- src/app/core/data/process-output-data.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/core/data/process-output-data.service.ts b/src/app/core/data/process-output-data.service.ts index 28adbcd6cab..24f33a85b79 100644 --- a/src/app/core/data/process-output-data.service.ts +++ b/src/app/core/data/process-output-data.service.ts @@ -36,7 +36,6 @@ class DataServiceImpl extends DataService { } } -// @ts-ignore /** * A service to retrieve output from processes from the REST API. */ From f82e6fa48bad7214b53f424bbf8b313152ae5ee9 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 2 Sep 2020 19:35:35 +0200 Subject: [PATCH 4/5] 72403: Process output logs only retrieved at button press + tests --- .../detail/process-detail.component.html | 22 ++++--- .../detail/process-detail.component.spec.ts | 66 +++++++++++++------ .../detail/process-detail.component.ts | 51 ++++++++++---- src/app/process-page/process-page.resolver.ts | 2 +- src/assets/i18n/en.json5 | 4 ++ 5 files changed, 103 insertions(+), 42 deletions(-) diff --git a/src/app/process-page/detail/process-detail.component.html b/src/app/process-page/detail/process-detail.component.html index e76d24d17aa..e13770a0a38 100644 --- a/src/app/process-page/detail/process-detail.component.html +++ b/src/app/process-page/detail/process-detail.component.html @@ -34,13 +34,19 @@

{{'process.detail.title' | translate:{id: process?.proce
{{ process.processStatus }}
- -
{{ (outputLogs$ | async)?.join('\n') }}
-

- {{ 'process.detail.logs.none' | translate }} -

-
+ + + +
{{ (outputLogs$ | async)?.join('\n') }}
+

+ {{ 'process.detail.logs.none' | translate }} +

+
- {{'process.detail.back' | translate}} + diff --git a/src/app/process-page/detail/process-detail.component.spec.ts b/src/app/process-page/detail/process-detail.component.spec.ts index c1efd233e83..8d4a3f0e621 100644 --- a/src/app/process-page/detail/process-detail.component.spec.ts +++ b/src/app/process-page/detail/process-detail.component.spec.ts @@ -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(); }); - } - ) + }); }); diff --git a/src/app/process-page/detail/process-detail.component.ts b/src/app/process-page/detail/process-detail.component.ts index af6e3238a62..f6b628f0f80 100644 --- a/src/app/process-page/detail/process-detail.component.ts +++ b/src/app/process-page/detail/process-detail.component.ts @@ -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; + /** + * 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(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> = 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> = 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(); + } + } diff --git a/src/app/process-page/process-page.resolver.ts b/src/app/process-page/process-page.resolver.ts index 57c749e1cbb..84821a2574c 100644 --- a/src/app/process-page/process-page.resolver.ts +++ b/src/app/process-page/process-page.resolver.ts @@ -24,7 +24,7 @@ export class ProcessPageResolver implements Resolve> { * or an error if something went wrong */ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { - 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), ); } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 75db3eceb18..036fd87ec8d 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -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", From df5570c7b0b99a5ba637e9e1cd9257d8a549cb3f Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 12 Nov 2020 17:35:10 +0100 Subject: [PATCH 5/5] Refactored to use process log bitstream --- src/app/core/core.module.ts | 4 - .../core/data/process-output-data.service.ts | 72 -------------- .../detail/process-detail.component.html | 11 ++- .../detail/process-detail.component.spec.ts | 68 +++++++++---- .../detail/process-detail.component.ts | 97 ++++++++++++++----- .../processes/process-output.model.ts | 36 ------- .../process-page/processes/process.model.ts | 4 +- src/app/shared/mocks/auth.service.mock.ts | 6 ++ src/assets/i18n/en.json5 | 2 +- 9 files changed, 137 insertions(+), 163 deletions(-) delete mode 100644 src/app/core/data/process-output-data.service.ts delete mode 100644 src/app/process-page/processes/process-output.model.ts diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 95cd89e87d5..2203377603c 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -7,7 +7,6 @@ import { EffectsModule } from '@ngrx/effects'; import { Action, StoreConfig, StoreModule } from '@ngrx/store'; import { MyDSpaceGuard } from '../+my-dspace-page/my-dspace.guard'; -import { ProcessOutput } from '../process-page/processes/process-output.model'; import { isNotEmpty } from '../shared/empty.util'; import { FormBuilderService } from '../shared/form/builder/form-builder.service'; @@ -72,7 +71,6 @@ import { LookupRelationService } from './data/lookup-relation.service'; import { MappedCollectionsReponseParsingService } from './data/mapped-collections-reponse-parsing.service'; import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing.service'; import { ObjectUpdatesService } from './data/object-updates/object-updates.service'; -import { ProcessOutputDataService } from './data/process-output-data.service'; import { RelationshipTypeService } from './data/relationship-type.service'; import { RelationshipService } from './data/relationship.service'; import { ResourcePolicyService } from './resource-policy/resource-policy.service'; @@ -291,7 +289,6 @@ const PROVIDERS = [ ItemTypeDataService, WorkflowActionDataService, ProcessDataService, - ProcessOutputDataService, ScriptDataService, ProcessFilesResponseParsingService, FeatureDataService, @@ -365,7 +362,6 @@ export const models = ExternalSourceEntry, Script, Process, - ProcessOutput, Version, VersionHistory, WorkflowAction, diff --git a/src/app/core/data/process-output-data.service.ts b/src/app/core/data/process-output-data.service.ts deleted file mode 100644 index 24f33a85b79..00000000000 --- a/src/app/core/data/process-output-data.service.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; -import { Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; -import { ProcessOutput } from '../../process-page/processes/process-output.model'; -import { NotificationsService } from '../../shared/notifications/notifications.service'; -import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; -import { dataService } from '../cache/builders/build-decorators'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; -import { ObjectCacheService } from '../cache/object-cache.service'; -import { CoreState } from '../core.reducers'; -import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { PROCESS_OUTPUT_TYPE } from '../shared/process-output.resource-type'; -import { DataService } from './data.service'; -import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; -import { RemoteData } from './remote-data'; -import { RequestService } from './request.service'; - -/* tslint:disable:max-classes-per-file */ -/** - * A private DataService implementation to delegate specific methods to. - */ -class DataServiceImpl extends DataService { - protected linkPath = 'processes'; - - constructor( - protected requestService: RequestService, - protected rdbService: RemoteDataBuildService, - protected store: Store, - protected objectCache: ObjectCacheService, - protected halService: HALEndpointService, - protected notificationsService: NotificationsService, - protected http: HttpClient, - protected comparator: DefaultChangeAnalyzer) { - super(); - } -} - -/** - * A service to retrieve output from processes from the REST API. - */ -@Injectable() -@dataService(PROCESS_OUTPUT_TYPE) -export class ProcessOutputDataService { - /** - * A private DataService instance to delegate specific methods to. - */ - private dataService: DataServiceImpl; - - constructor( - protected requestService: RequestService, - protected rdbService: RemoteDataBuildService, - protected store: Store, - protected objectCache: ObjectCacheService, - protected halService: HALEndpointService, - protected notificationsService: NotificationsService, - protected http: HttpClient, - protected comparator: DefaultChangeAnalyzer) { - this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator); - } - - /** - * Returns an observable of {@link RemoteData} of a {@link ProcessOutput}, based on an href, with a list of {@link FollowLinkConfig}, - * to automatically resolve {@link HALLink}s of the {@link ProcessOutput} - * @param href The url of {@link ProcessOutput} we want to retrieve - * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved - */ - findByHref(href: string, ...linksToFollow: Array>): Observable> { - return this.dataService.findByHref(href, ...linksToFollow); - } -} -/* tslint:enable:max-classes-per-file */ diff --git a/src/app/process-page/detail/process-detail.component.html b/src/app/process-page/detail/process-detail.component.html index e13770a0a38..d59f93254e3 100644 --- a/src/app/process-page/detail/process-detail.component.html +++ b/src/app/process-page/detail/process-detail.component.html @@ -17,7 +17,7 @@

{{'process.detail.title' | translate:{id: process?.proce {{getFileName(file)}} - ({{(file?.sizeBytes) | dsFileSize }}) + ({{(file?.sizeBytes) | dsFileSize }}) @@ -34,14 +34,15 @@

{{'process.detail.title' | translate:{id: process?.proce
{{ process.processStatus }}
- -
{{ (outputLogs$ | async)?.join('\n') }}
-

+ *ngIf="showOutputLogs && (outputLogs$ | async)?.length > 0">{{ (outputLogs$ | async) }} +

{{ 'process.detail.logs.none' | translate }}

diff --git a/src/app/process-page/detail/process-detail.component.spec.ts b/src/app/process-page/detail/process-detail.component.spec.ts index 8d4a3f0e621..b81eedabad3 100644 --- a/src/app/process-page/detail/process-detail.component.spec.ts +++ b/src/app/process-page/detail/process-detail.component.spec.ts @@ -1,11 +1,22 @@ -import { ProcessOutputDataService } from '../../core/data/process-output-data.service'; -import { ProcessOutput } from '../processes/process-output.model'; +import { HttpClient } from '@angular/common/http'; +import { AuthService } from '../../core/auth/auth.service'; +import { BitstreamDataService } from '../../core/data/bitstream-data.service'; +import { AuthServiceMock } from '../../shared/mocks/auth.service.mock'; import { ProcessDetailComponent } from './process-detail.component'; -import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { + async, + ComponentFixture, + discardPeriodicTasks, + fakeAsync, + flush, + flushMicrotasks, + 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'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { ProcessDetailFieldComponent } from './process-detail-field/process-detail-field.component'; import { Process } from '../processes/process.model'; import { ActivatedRoute } from '@angular/router'; @@ -23,8 +34,9 @@ describe('ProcessDetailComponent', () => { let fixture: ComponentFixture; let processService: ProcessDataService; - let processOutputService: ProcessOutputDataService; let nameService: DSONameService; + let bitstreamDataService: BitstreamDataService; + let httpClient: HttpClient; let process: Process; let fileName: string; @@ -33,13 +45,11 @@ describe('ProcessDetailComponent', () => { let processOutput; function init() { - processOutput = Object.assign(new ProcessOutput(), { - logs: ['Process started', 'Process completed'] - } - ); + processOutput = 'Process Started' process = Object.assign(new Process(), { processId: 1, scriptName: 'script-name', + processStatus: 'COMPLETED', parameters: [ { name: '-f', @@ -76,15 +86,24 @@ describe('ProcessDetailComponent', () => { } }) ]; + const logBitstream = Object.assign(new Bitstream(), { + id: 'output.log', + _links: { + content: { href: 'log-selflink' } + } + }); processService = jasmine.createSpyObj('processService', { getFiles: createSuccessfulRemoteDataObject$(createPaginatedList(files)) }); - processOutputService = jasmine.createSpyObj('processOutputService', { - findByHref: createSuccessfulRemoteDataObject$(processOutput) + bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', { + findByHref: createSuccessfulRemoteDataObject$(logBitstream) }); nameService = jasmine.createSpyObj('nameService', { getName: fileName }); + httpClient = jasmine.createSpyObj('httpClient', { + get: observableOf(processOutput) + }); } beforeEach(async(() => { @@ -98,10 +117,12 @@ describe('ProcessDetailComponent', () => { useValue: { data: observableOf({ process: createSuccessfulRemoteDataObject(process) }) } }, { provide: ProcessDataService, useValue: processService }, - { provide: ProcessOutputDataService, useValue: processOutputService }, - { provide: DSONameService, useValue: nameService } + { provide: BitstreamDataService, useValue: bitstreamDataService }, + { provide: DSONameService, useValue: nameService }, + { provide: AuthService, useValue: new AuthServiceMock() }, + { provide: HttpClient, useValue: httpClient }, ], - schemas: [NO_ERRORS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA] }).compileComponents(); })); @@ -109,6 +130,14 @@ describe('ProcessDetailComponent', () => { fixture = TestBed.createComponent(ProcessDetailComponent); component = fixture.componentInstance; }); + afterEach(fakeAsync(() => { + TestBed.resetTestingModule(); + fixture.destroy(); + flush(); + flushMicrotasks(); + discardPeriodicTasks(); + component = null; + })); it('should display the script\'s name', () => { fixture.detectChanges(); @@ -134,6 +163,7 @@ describe('ProcessDetailComponent', () => { beforeEach(fakeAsync(() => { spyOn(component, 'showProcessOutputLogs').and.callThrough(); fixture.detectChanges(); + const showOutputButton = fixture.debugElement.query(By.css('#showOutputButton')); showOutputButton.triggerEventHandler('click', { preventDefault: () => {/**/ @@ -147,20 +177,16 @@ describe('ProcessDetailComponent', () => { 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'); + expect(outputProcess.nativeElement.textContent).toContain(processOutput); }); }); - describe('if press show output logs and process has no output logs (yet)', () => { + describe('if press show output logs and process has no output logs', () => { beforeEach(fakeAsync(() => { jasmine.getEnv().allowRespy(true); - const emptyProcessOutput = Object.assign(new ProcessOutput(), { - logs: [] - }); - spyOn(processOutputService, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(emptyProcessOutput)); + spyOn(httpClient, 'get').and.returnValue(observableOf(null)); fixture = TestBed.createComponent(ProcessDetailComponent); component = fixture.componentInstance; - fixture.detectChanges(); spyOn(component, 'showProcessOutputLogs').and.callThrough(); fixture.detectChanges(); const showOutputButton = fixture.debugElement.query(By.css('#showOutputButton')); diff --git a/src/app/process-page/detail/process-detail.component.ts b/src/app/process-page/detail/process-detail.component.ts index 13f98149232..c4b94aa7292 100644 --- a/src/app/process-page/detail/process-detail.component.ts +++ b/src/app/process-page/detail/process-detail.component.ts @@ -1,19 +1,23 @@ +import { HttpClient } from '@angular/common/http'; 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 { finalize, map, mergeMap, switchMap, take, tap } from 'rxjs/operators'; +import { AuthService } from '../../core/auth/auth.service'; +import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { BitstreamDataService } from '../../core/data/bitstream-data.service'; +import { PaginatedList } from '../../core/data/paginated-list'; +import { ProcessDataService } from '../../core/data/processes/process-data.service'; import { RemoteData } from '../../core/data/remote-data'; +import { Bitstream } from '../../core/shared/bitstream.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { ProcessOutput } from '../processes/process-output.model'; -import { Process } from '../processes/process.model'; -import { finalize, map, switchMap, take } from 'rxjs/operators'; import { getFirstSucceededRemoteDataPayload, redirectOn404Or401 } from '../../core/shared/operators'; +import { URLCombiner } from '../../core/url-combiner/url-combiner'; import { AlertType } from '../../shared/alert/aletr-type'; -import { ProcessDataService } from '../../core/data/processes/process-data.service'; -import { PaginatedList } from '../../core/data/paginated-list'; -import { Bitstream } from '../../core/shared/bitstream.model'; -import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { hasValue } from '../../shared/empty.util'; +import { ProcessStatus } from '../processes/process-status.model'; +import { Process } from '../processes/process.model'; @Component({ selector: 'ds-process-detail', @@ -40,26 +44,33 @@ export class ProcessDetailComponent implements OnInit { */ filesRD$: Observable>>; + /** + * File link that contain the output logs with auth token + */ + outputLogFileUrl$: Observable; + /** * The Process's Output logs */ - outputLogs$: Observable; + outputLogs$: Observable; /** * Boolean on whether or not to show the output logs */ - showOutputLogs = false; + showOutputLogs; /** * When it's retrieving the output logs from backend, to show loading component */ - retrievingOutputLogs$ = new BehaviorSubject(false); + retrievingOutputLogs$: BehaviorSubject; constructor(protected route: ActivatedRoute, protected router: Router, protected processService: ProcessDataService, - protected processOutputService: ProcessOutputDataService, + protected bitstreamDataService: BitstreamDataService, protected nameService: DSONameService, - private zone: NgZone) { + private zone: NgZone, + protected authService: AuthService, + protected http: HttpClient) { } /** @@ -67,8 +78,12 @@ export class ProcessDetailComponent implements OnInit { * Display a 404 if the process doesn't exist */ ngOnInit(): void { + this.showOutputLogs = false; + this.retrievingOutputLogs$ = new BehaviorSubject(false); this.processRD$ = this.route.data.pipe( - map((data) => data.process as RemoteData), + map((data) => { + return data.process as RemoteData + }), redirectOn404Or401(this.router) ); @@ -93,20 +108,58 @@ export class ProcessDetailComponent implements OnInit { showProcessOutputLogs() { this.retrievingOutputLogs$.next(true); this.zone.runOutsideAngular(() => { - const processOutputRD$: Observable> = this.processRD$.pipe( + const processOutputRD$: Observable> = this.processRD$.pipe( getFirstSucceededRemoteDataPayload(), - switchMap((process: Process) => this.processOutputService.findByHref(process._links.output.href)) + switchMap((process: Process) => { + return this.bitstreamDataService.findByHref(process._links.output.href); + }) ); - this.outputLogs$ = processOutputRD$.pipe( - getFirstSucceededRemoteDataPayload(), - map((processOutput: ProcessOutput) => { - this.showOutputLogs = true; - return processOutput.logs; + this.outputLogFileUrl$ = processOutputRD$.pipe( + tap((processOutputFileRD: RemoteData) => { + if (processOutputFileRD.statusCode === 204) { + this.zone.run(() => this.retrievingOutputLogs$.next(false)); + this.showOutputLogs = true; + } }), - finalize(() => this.zone.run(() => this.retrievingOutputLogs$.next(false))), + getFirstSucceededRemoteDataPayload(), + mergeMap((processOutput: Bitstream) => { + const url = processOutput._links.content.href; + return this.authService.getShortlivedToken().pipe(take(1), + map((token: string) => { + return hasValue(token) ? new URLCombiner(url, `?authentication-token=${token}`).toString() : url; + })); + }) ) }); + this.outputLogs$ = this.outputLogFileUrl$.pipe(take(1), + mergeMap((url: string) => { + return this.getTextFile(url); + }), + finalize(() => this.zone.run(() => this.retrievingOutputLogs$.next(false))), + ); this.outputLogs$.pipe(take(1)).subscribe(); } + getTextFile(filename: string): Observable { + // The Observable returned by get() is of type Observable + // because a text response was specified. + // There's no need to pass a type parameter to get(). + return this.http.get(filename, { responseType: 'text' }) + .pipe( + finalize(() => { + this.showOutputLogs = true; + }), + ); + } + + /** + * Whether or not the given process has Completed or Failed status + * @param process Process to check if completed or failed + */ + isProcessFinished(process: Process): boolean { + return (hasValue(process) && hasValue(process.processStatus) && + (process.processStatus.toString() === ProcessStatus[ProcessStatus.COMPLETED].toString() + || process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString())); + } + } diff --git a/src/app/process-page/processes/process-output.model.ts b/src/app/process-page/processes/process-output.model.ts deleted file mode 100644 index 4ae1731d26a..00000000000 --- a/src/app/process-page/processes/process-output.model.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { PROCESS_OUTPUT_TYPE } from '../../core/shared/process-output.resource-type'; -import { CacheableObject } from '../../core/cache/object-cache.reducer'; -import { HALLink } from '../../core/shared/hal-link.model'; -import { autoserialize, deserialize } from 'cerialize'; -import { excludeFromEquals } from '../../core/utilities/equals.decorators'; -import { ResourceType } from '../../core/shared/resource-type'; -import { typedObject } from '../../core/cache/builders/build-decorators'; - -/** - * Object representing a process output object - */ -@typedObject -export class ProcessOutput implements CacheableObject { - static type = PROCESS_OUTPUT_TYPE; - - /** - * The object type - */ - @excludeFromEquals - @autoserialize - type: ResourceType; - - /** - * The output strings for this ProcessOutput - */ - @autoserialize - logs: string[]; - - /** - * The {@link HALLink}s for this ProcessOutput - */ - @deserialize - _links: { - self: HALLink, - }; -} diff --git a/src/app/process-page/processes/process.model.ts b/src/app/process-page/processes/process.model.ts index 891acb626dd..74bb82b890b 100644 --- a/src/app/process-page/processes/process.model.ts +++ b/src/app/process-page/processes/process.model.ts @@ -1,5 +1,5 @@ +import { Bitstream } from '../../core/shared/bitstream.model'; import { PROCESS_OUTPUT_TYPE } from '../../core/shared/process-output.resource-type'; -import { ProcessOutput } from './process-output.model'; import { ProcessStatus } from './process-status.model'; import { ProcessParameter } from './process-parameter.model'; import { CacheableObject } from '../../core/cache/object-cache.reducer'; @@ -93,5 +93,5 @@ export class Process implements CacheableObject { * Will be undefined unless the output {@link HALLink} has been resolved. */ @link(PROCESS_OUTPUT_TYPE) - output?: Observable>; + output?: Observable>; } diff --git a/src/app/shared/mocks/auth.service.mock.ts b/src/app/shared/mocks/auth.service.mock.ts index a168ffd8e52..0f3a7d13d1e 100644 --- a/src/app/shared/mocks/auth.service.mock.ts +++ b/src/app/shared/mocks/auth.service.mock.ts @@ -1,4 +1,6 @@ /* tslint:disable:no-empty */ +import { Observable, of as observableOf } from 'rxjs'; + export class AuthServiceMock { public checksAuthenticationToken() { return @@ -6,4 +8,8 @@ export class AuthServiceMock { public buildAuthHeader() { return 'auth-header'; } + + public getShortlivedToken(): Observable { + return observableOf('token'); + } } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 930c59cee59..dc0d2ec2fb1 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2270,7 +2270,7 @@ "process.detail.logs.loading": "Retrieving", - "process.detail.logs.none": "This process has no output yet", + "process.detail.logs.none": "This process has no output", "process.detail.output-files" : "Output Files",