Skip to content
Snippets Groups Projects
execution-detail.component.ts 8.42 KiB
Newer Older
import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
import {Job, JobExecution} from "../../../model/job";
import {Subject, Observable, of} 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 {auditTime, takeUntil} from "rxjs/operators";

@Component({
  selector: 'app-execution-detail',
  templateUrl: './execution-detail.component.html',
  styleUrls: ['./execution-detail.component.scss']
})
export class ExecutionDetailComponent implements OnInit, OnDestroy {
  noteFormGroup = new FormGroup({
    notes: new FormControl(),
    id: new FormControl()
  });
  planesFormGroup = new FormGroup({
    planes: new FormArray([])
  });
  tilesFormGroup = new FormGroup({
    tiles: new FormArray([])
  });
  private ngUnsubscribe = new Subject<void>();
  @Input() job: Job;
  public jobDetail: JobExecution;

  public faSave = faSave;
  public faList = faList;
  public faCheckCircle = faCheckCircle;
  public faStickyNote = faStickyNote;
  public faCopy = faCopy;

  public weblogUrl: string;
  planeKeys: string[] = [];
  tiles: string[] = [];
  haveRequestedTiles = false;  // Avoid requesting them repeatedly
  haveRequestedPlanes = false;  // Avoid requesting them repeatedly
  constructor(
    private configService: ConfigurationService,
    private jobService: JobsService,
    private alertService: AlertService
  ) {
  }

  ngOnInit() {
    this.jobService.getJobExecution(this.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);

    // autosave the form on changes
    this.noteFormGroup.valueChanges.pipe(auditTime(2000),takeUntil(this.ngUnsubscribe)).subscribe(() => this.updateNotes());
  }

  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;
  }

  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;

    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)
        if (response !== null && response.body !== null && response.body !== '') {
          weblogUrl = weblogUrl + '/' + response.body;
        }
      },
      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();
    }
    this.updateNotes(); // make sure notes are saved before submitting
    this.alertService.info('Accepting ' + this.job.job_id);
    this.performQa(this.job.job_id, 'accept');
  }

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

  performQa(id: number, status: string) {
    //console.log(status + 'ing QA');

    let newStatus;

    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).subscribe(response => {
        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' || true;
  canSelectTiles(): boolean {
    return this.jobDetail.queueName === 'calibration' || true;
  }

  // Reads plane names from a JSON file and returns them in a list
  getPlaneKeys(): string[] {
    // Return the plane names if we already fetched them
    if (this.haveRequestedPlanes) {
      return this.planeKeys;
    }

    // Get the plane names from the spectral window numbers in the planes.json file
    this.haveRequestedPlanes = true;
    this.jobService.getPlanes(this.job.job_id).subscribe((response: string[]) => {
      this.planeKeys = response;
      this.planesFormArray.clear();
      this.planeKeys.forEach(planeKey => this.planesFormArray.push(new FormControl(planeKey)));
    },
      error => {
      this.alertService.error('Could not retrieve planes from planes.json. ' + error);
    });

    return this.planeKeys;
  }

  // Reads tile names from a JSON file and returns them in a list
  getTiles(): string[] {
    // Return the plane names if we already fetched them
    if (this.haveRequestedTiles) {
      return this.tiles;
    }

    // Get the plane names from the spectral window numbers in the planes.json file
    this.haveRequestedTiles = true;
    this.jobService.getTiles(this.job.job_id).subscribe((response: string[]) => {
      this.tiles = response;
      this.tilesFormArray.clear();
      this.tiles.forEach(tile => this.tilesFormArray.push(new FormControl(tile)));
      this.alertService.error(`Could not retrieve tiles: ${error}`);
      this.tiles = ["blorp"];
  get tilesFormArray(): FormArray {
    return this.tilesFormGroup.controls["tiles"] as FormArray;
  }

  get planesFormArray(): FormArray {
    return this.planesFormGroup.controls["planes"] as FormArray;
  }

  // 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');
        });
    }
  }

  ngOnDestroy(): void {
    this.ngUnsubscribe.next();
    this.ngUnsubscribe.complete();