import { HttpClient } from '@angular/common/http'; import { Component, NgZone, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { BehaviorSubject, Observable } from 'rxjs'; import { finalize, map, 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.model'; 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 { getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; import { URLCombiner } from '../../core/url-combiner/url-combiner'; import { AlertType } from '../../shared/alert/aletr-type'; import { hasValue } from '../../shared/empty.util'; import { ProcessStatus } from '../processes/process-status.model'; import { Process } from '../processes/process.model'; import { redirectOn4xx } from '../../core/shared/authorized.operators'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { getProcessListRoute } from '../process-page-routing.paths'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'ds-process-detail', templateUrl: './process-detail.component.html', }) /** * A component displaying detailed information about a DSpace Process */ export class ProcessDetailComponent implements OnInit { /** * The AlertType enumeration * @type {AlertType} */ public AlertTypeEnum = AlertType; /** * The Process's Remote Data */ processRD$: Observable>; /** * The Process's Output Files */ filesRD$: Observable>>; /** * File link that contain the output logs with auth token */ outputLogFileUrl$: Observable; /** * The Process's Output logs */ outputLogs$: BehaviorSubject = new BehaviorSubject(undefined); /** * Boolean on whether or not to show the output logs */ showOutputLogs; /** * When it's retrieving the output logs from backend, to show loading component */ retrievingOutputLogs$: BehaviorSubject; /** * Date format to use for start and end time of processes */ dateFormat = 'yyyy-MM-dd HH:mm:ss ZZZZ'; /** * Reference to NgbModal */ protected modalRef: NgbModalRef; constructor(protected route: ActivatedRoute, protected router: Router, protected processService: ProcessDataService, protected bitstreamDataService: BitstreamDataService, protected nameService: DSONameService, private zone: NgZone, protected authService: AuthService, protected http: HttpClient, protected modalService: NgbModal, protected notificationsService: NotificationsService, protected translateService: TranslateService ) { } /** * Initialize component properties * 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) => { return data.process as RemoteData; }), redirectOn4xx(this.router, this.authService) ); this.filesRD$ = this.processRD$.pipe( getFirstSucceededRemoteDataPayload(), switchMap((process: Process) => this.processService.getFiles(process.processId)) ); } /** * Get the name of a bitstream * @param bitstream */ getFileName(bitstream: Bitstream) { 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() { console.log('showProcessOutputLogs'); this.retrievingOutputLogs$.next(true); this.zone.runOutsideAngular(() => { const processOutputRD$: Observable> = this.processRD$.pipe( getFirstSucceededRemoteDataPayload(), switchMap((process: Process) => { return this.bitstreamDataService.findByHref(process._links.output.href, false); }) ); this.outputLogFileUrl$ = processOutputRD$.pipe( getFirstSucceededRemoteData(), tap((processOutputFileRD: RemoteData) => { if (processOutputFileRD.statusCode === 204) { this.zone.run(() => this.retrievingOutputLogs$.next(false)); this.showOutputLogs = true; } }), switchMap((processOutput: RemoteData) => { const url = processOutput.payload._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.outputLogFileUrl$.pipe(take(1), switchMap((url: string) => { return this.getTextFile(url); }), finalize(() => this.zone.run(() => this.retrievingOutputLogs$.next(false))) ).subscribe((logs: string) => { this.outputLogs$.next(logs); }); } 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())); } /** * Delete the current process * @param process */ deleteProcess(process: Process) { this.processService.delete(process.processId).pipe( getFirstCompletedRemoteData() ).subscribe((rd) => { if (rd.hasSucceeded) { this.notificationsService.success(this.translateService.get('process.detail.delete.success')); this.closeModal(); this.router.navigateByUrl(getProcessListRoute()); } else { this.notificationsService.error(this.translateService.get('process.detail.delete.error')); } }); } /** * Open a given modal. * @param content - the modal content. */ openDeleteModal(content) { this.modalRef = this.modalService.open(content); } /** * Close the modal. */ closeModal() { this.modalRef.close(); } }