Newer
Older
import {Component, EventEmitter, Input, OnDestroy, OnInit, Output, SimpleChanges, OnChanges} from '@angular/core';
import {Job, JobExecution} from "../../../model/job";
import {Subject} from "rxjs";
import {JobsService} from "../../../services/jobs.service";
import {FormControl, FormGroup, FormArray} from "@angular/forms";
import {ConfigurationService} from "../../../env/configuration.service";
import {faCheckCircle, faCopy, faList, faSave, faStickyNote} from "@fortawesome/free-solid-svg-icons";
import {AlertService} from "../../../services/alert.service";
import {takeUntil, debounceTime} from "rxjs/operators";
import { Tile } from 'src/app/model/tile';
@Component({
selector: 'app-execution-detail',
templateUrl: './execution-detail.component.html',
styleUrls: ['./execution-detail.component.scss']
})
export class ExecutionDetailComponent implements OnInit, OnDestroy, OnChanges {
noteFormGroup = new FormGroup({
notes: new FormControl(),
id: new FormControl()
});
planesFormGroup = new FormGroup({});
tilesFormGroup = new FormGroup({});

Reid Givens
committed
private ngUnsubscribe = new Subject<void>();

Reid Givens
committed
@Output() reload = new EventEmitter();
public jobDetail: JobExecution;
public faSave = faSave;
public faList = faList;
public faCheckCircle = faCheckCircle;
public faStickyNote = faStickyNote;
public faCopy = faCopy;
public weblogUrl: string;
planeKeys: string[] = [];
tiles: Map<string, Tile> = new Map<string, Tile>();

Daniel Nemergut
committed
constructor(
private configService: ConfigurationService,
private jobService: JobsService,
private alertService: AlertService
) {
}
ngOnInit() {
// autosave the form on changes
this.noteFormGroup.valueChanges.pipe(debounceTime(2000),takeUntil(this.ngUnsubscribe)).subscribe(() => this.updateNotes());
// Listen for changes to @Input properties
// Source: https://angular.io/guide/lifecycle-hooks#onchanges
ngOnChanges(changes: SimpleChanges) {
const job = changes.job.currentValue as Job;
// Only need to get these when `@Input() job` changes
this.jobService.getJobExecution(job.job_id).pipe(takeUntil(this.ngUnsubscribe)).subscribe((j: JobExecution) => {
if (j) {
this.jobDetail = j;
this.noteFormGroup.get('notes').setValue(j.notes);
this.noteFormGroup.get('id').setValue(j.id);

Daniel Nemergut
committed
// this.loadWeblogUrl, this.canSelectTiles, and this.canSelectPlanes depend on this.jobDetail,
// so need to wait for it to be set to run them

Daniel Nemergut
committed
this.loadWeblogUrl();
if (this.canSelectTiles()) {
this.jobService.getTiles(job.job_id).pipe(takeUntil(this.ngUnsubscribe)).subscribe(
(response: Tile[]) => this.refreshTiles(response),
error => {
this.alertService.error(`Could not retrieve tiles: ${error}`);
});
}
if (this.canSelectPlanes()) {
this.jobService.getPlanes(job.job_id).subscribe((response: object) => {
// Sort the returned planes before making form controls from them
this.planeKeys = Object.keys(response)
.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
for (const controlName in this.planesFormGroup.controls) {
this.planesFormGroup.removeControl(controlName);
}
this.planeKeys.forEach(key => this.planesFormGroup.addControl(key, new FormControl(true)));
},
error => {
this.alertService.error('Could not retrieve planes from planes.json. ' + error);
});
}
}
refreshTiles(newTiles: Tile[]) {
if (this.job.jobspec_name.includes('_')) {
// Sort newTiles by their order in the jobspec name before they're stored on this component
const tileOrder: string[] = this.job.jobspec_name.split('_')[1].split('.');
newTiles = newTiles.sort((a, b) => tileOrder.indexOf(a.name) - tileOrder.indexOf(b.name));
}
this.tiles = new Map<string, Tile>(newTiles.map(tile => [tile.name, tile]));
for (const controlName in this.tilesFormGroup.controls) {
this.tilesFormGroup.removeControl(controlName);
}
Array.from(this.tiles.keys()).forEach(tile => this.tilesFormGroup.addControl(tile, new FormControl(true)));
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
}
copyToClipboard(text: string): void {
const selBox = document.createElement('textarea');
selBox.style.position = 'fixed';
selBox.style.left = '0';
selBox.style.top = '0';
selBox.style.opacity = '0';
selBox.value = text;
document.body.appendChild(selBox);
selBox.focus();
selBox.select();
document.execCommand('copy');
document.body.removeChild(selBox);
}
getTaskStatusBgClass(status: string) {
switch (status) {
case 'ERROR':
return 'badge-danger';
case 'SUCCESS':
return 'badge-success';
default:
return 'badge-info';
}
}
canAcceptArchive(status: string, archiveStatus: string): boolean {

Daniel Lopez Sanders
committed
return (status === 'QA_READY' || status === 'QA_MANUAL' || archiveStatus === 'ARCHIVE_ERROR');
}
getConfigRootDataDirectory(): string {
return this.configService.config.rootDataDirectory;
}
getConfigWebLogBaseUrl(): string {
return this.configService.config.weblogbaseurl;
}

Daniel Nemergut
committed
loadWeblogUrl(): void {
// The base URL opens up to a file navigation URL, that's the default if the service doesn't find a pipeline directory
let weblogUrl = this.getConfigWebLogBaseUrl() + '/vlass/weblog/' + this.jobDetail.queueName + '/' + this.job.job_name;

Daniel Nemergut
committed
this.jobService.getWeblogLink(this.job.job_id).subscribe(response => {
// Append the pipeline directory and /html to the base weblog URL (both contained in the response)

Daniel Nemergut
committed
if (response !== null && response.body !== null && response.body !== '') {

Daniel Nemergut
committed
weblogUrl = weblogUrl + '/' + response.body;

Daniel Nemergut
committed
}
},
error => {
this.alertService.error('Could not find a pipeline dir, using the base weblog URL.');
},
() => {
this.weblogUrl = weblogUrl;
});
}
updateNotes() {
this.alertService.info('Saving Notes');

Daniel Lopez Sanders
committed
const notes = this.noteFormGroup.get('notes').value;
const id = this.noteFormGroup.get('id').value;
this.jobService.updateNotes(id, notes).subscribe(response => {
this.alertService.success('Notes Saved');
},
error => {
this.alertService.error('Notes did not save. ' + error);
});
}
acceptQa() {
if (this.canSelectPlanes()) {
this.acceptPlanes();
}

Reid Givens
committed
this.updateNotes(); // make sure notes are saved before submitting

Reid Givens
committed
this.alertService.info('Accepting ' + this.job.job_id);
this.performQa(this.job.job_id, 'accept', this.collectTileIds());
}
rejectQa() {
let yesno = confirm("Are you sure you want to reject this image?");
if (yesno) {

Reid Givens
committed
this.alertService.info('Rejecting ' + this.job.job_id);
this.performQa(this.job.job_id, 'reject');
}
}
performQa(id: number, status: string, selectedTiles?: number[]) {

Daniel Lopez Sanders
committed
// console.log(status + 'ing QA');

Daniel Lopez Sanders
committed
let newStatus = '';
const qaJobStatus = (this.job.job_status === 'QA_READY' || this.job.job_status === 'QA_MANUAL');

Daniel Lopez Sanders
committed
if (status === 'accept' && qaJobStatus) {

Daniel Lopez Sanders
committed
} else if (status === 'reject' && qaJobStatus) {
newStatus = 'QA_REJECTED';
}
if (['QA_ACCEPTED', 'QA_MANUAL_ACCEPTED', 'QA_REJECTED', 'QA_MANUAL_REJECTED'].indexOf(newStatus) > -1) {
this.jobService.performQA(this.job.job_id, newStatus, this.jobDetail.queueName, selectedTiles).subscribe(response => {

Reid Givens
committed
this.alertService.success('QA Performed for ' + id);
this.reload.emit('reload');
}, error => {
this.alertService.error('QA Failed for ' + id);

Daniel Lopez Sanders
committed
}
canSelectPlanes(): boolean {

Daniel Lopez Sanders
committed
return (this.jobDetail.queueName === 'se_coarse_cube_imaging' &&
this.canAcceptArchive(this.jobDetail.status, this.jobDetail.archiveStatus));
return this.jobDetail.queueName === 'calibration' && this.canAcceptArchive(this.jobDetail.status, this.jobDetail.archiveStatus);
get tileNames(): string[] {
return Array.from(this.tiles.keys());
collectTileIds(): number[] {
const formTiles = this.tilesFormGroup.value;
const tileIds: number[] = [];
for (const tileName in formTiles) {
if (formTiles[tileName] === true) {
tileIds.push(this.tiles.get(tileName).id);
}
}
return tileIds;
}
// Writes to a file in lustre to flag planes to be accepted
acceptPlanes(): void {
this.alertService.info('Flagging accepted planes to be cached');
// Collect the selected planes and write their names separated by a newline
let planesText = '';
const planes = this.planesFormGroup.value;
for (const planeKey in planes) {
if (planes[planeKey] === true) {

Daniel Lopez Sanders
committed
planesText += planeKey + '\n';
planesText = planesText.trim();
// Write out the planes string if any are selected
if (planesText.length > 0) {
this.jobService.writePlanes(this.job.job_id, planesText).subscribe(
result => {},
error => {
this.alertService.error('Planes did not save. ' + error);
},
() => {
this.alertService.success('Planes Saved');
});
}
}

Reid Givens
committed
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();