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: string) => {
this.planeKeys = response.split(/\r?\n|\r|\n/g); // Source: https://stackoverflow.com/a/21712066
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[]) {
this.tiles = new Map<string, Tile>(newTiles.map(tile => {return [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)));
95
96
97
98
99
100
101
102
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
131
132
133
134
135
136
137
}
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 {
if (status === 'QA_READY' || status === 'QA_MANUAL' || archiveStatus === 'ARCHIVE_ERROR') {
return true;
} else {
return false;
}
}
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');
let notes = this.noteFormGroup.get('notes').value;
let 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[]) {
let newStatus: string = "";
if (status == 'accept' && (this.job.job_status === 'QA_READY' || this.job.job_status === 'QA_MANUAL')) {
newStatus = 'QA_ACCEPTED';
} else if (status == 'reject' && (this.job.job_status === 'QA_READY' || this.job.job_status === 'QA_MANUAL')) {
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);
canSelectPlanes(): boolean {
return this.jobDetail.queueName === 'se_continuum_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;
}
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
// 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;
Object.keys(planes).forEach(key => {
if (planes[key] === true) {
planesText += key + '\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();