javascript – Angular: Job Run Status Fails to Update Automatically on HTML Template

Peace be upon you all! I hope you are doing well!

I am tackling a project that is built upon Angular, and I would like to ask for any feedback regarding the issue I have encountered.

Query

Currently, I wish to display a list of jobs from an HTTP GET request on an HTML template, where the execution – or run- status is displayed, alongside an icon that changes based on the three statuses available: COMPLETED, RUNNING or FAILED. After executing the job script then navigating to the history route, the status and status icon indicate that the job is successfully running. However, once the script is completed, the icon and the status itself do not update; the new status is reflected once I navigated to another route before returning to that history route once more.

Attempts

I resorted to the use of Observables and Subjects, but alas, I was unsuccessful. The failed attempts are currently absent from the provided source code, as I feel that I have been using them incorrectly, considering that I am but a beginner in Angular, and the project was built by another.

Code
Below is the code related to this issue:

TypeScript

import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import {
  faCaretDown,
  faCaretUp,
  faCheckCircle,
  faChevronLeft,
  faChevronRight,
  faCircleNotch,
  faExclamationCircle,
  faPause,
  faTerminal,
} from "@fortawesome/free-solid-svg-icons";
import { humanizer } from "src/globals";
import { jobsData, STATUS } from "../workloads.component";
import { Job } from "../../model/job";
import { JobServiceService } from "../../service/job-service/job-service.service";

@Component({
  selector: "app-history",
  templateUrl: "./history.component.html",
  styleUrls: ["./history.component.css"],
})
export class HistoryComponent implements OnInit {
  jobContainer: any;
  faCaretUp = faCaretUp;
  faCaretDown = faCaretDown;
  faPause = faPause;
  faSpinner = faCircleNotch;
  faExclamationCircle = faExclamationCircle;
  faCheckCircle = faCheckCircle;
  faTerminal = faTerminal;
  faChevronLeft = faChevronLeft;
  faChevronRight = faChevronRight;

  STATUS = STATUS;

  humanizer = humanizer;

  headers = [
    {
      key: "status",
      title: "Status",
    },
    {
      key: "date",
      title: "Date",
    },
    {
      key: "type",
      title: "Type",
    },
    {
      key: "duration",
      title: "Duration",
    },
    {
      key: "id",
      title: "Run ID",
    },
    {
      key: "runStatus",
      title: "Run Status",
    },
    {
      key: "consoleOutput",
      title: "Console Output",
    },
  ];

  pages = {
    current: 1,
    per: 3,
  };

  currentSort = {
    key: "",
    ascending: false,
  };

  response;
  historyArray;
  consoleResponse;

  constructor(
    private route: ActivatedRoute,
    private jobService: JobServiceService
  ) {}

  ngOnInit() {
    // grab url parameter and find specific job
    const jobId = Number(this.route.snapshot.paramMap.get("jobId"));
    this.jobContainer = jobsData.find(
      (jobContainer) => jobId === jobContainer.job.id
    );
    // default to sort by date
    this.onSort("id");
    this.onHistoryDisplay(this.jobContainer.job);
  }

  onHistoryDisplay(job: Job) {
    this.jobService.getJobRuns(job).subscribe((data) => {
      this.response = data;
      console.log(this.response);
    });
  }

  onConsoleClick(job: Job) {
    this.jobService.getJobRuns(job).subscribe((data) => {
      this.consoleResponse = data;
      console.log(this.response);
    });
  }

  onPaginate(amt) {
    const next = this.pages.current + amt;

    // only paginate if valid page
    if (
      next <= Math.ceil(this.jobContainer.history.length / this.pages.per) &&
      next >= 1
    ) {
      this.pages.current = next;
    }
  }

  runHistoryArray(job: Job) {
    return this.jobService.getJobRuns(job);
  }

  onSort(key) {
    if (key !== this.currentSort.key) {
      // default to descending if different key
      this.currentSort.ascending = false;
      this.currentSort.key = key;
    } else {
      // toggle ascending/descending if same key
      this.currentSort.ascending = !this.currentSort.ascending;
    }
    this.sort(key);
  }

  private sort(key) {
    this.historyArray = this.runHistoryArray(this.jobContainer.job);
    this.historyArray.subscribe((data) => {
      this.response = data;
      this.response = this.response.sort((a, b) => {
        if (a[key] > b[key]) return this.currentSort.ascending ? -1 : 1;
        if (a[key] < b[key]) return this.currentSort.ascending ? 1 : -1;
        return 0;
      });
    });
  }
}

HTML

<div class="container">
  <app-breadcrumb [jobName]="jobContainer.job.name"></app-breadcrumb>
  <table>
    <thead>
      <th *ngFor="let header of headers" (click)="onSort(header.key)">
        {{ header.title }}
        <fa-icon
          *ngIf="currentSort.key === header.key"
          [icon]="currentSort.ascending ? faCaretUp : faCaretDown"
        ></fa-icon>
      </th>
      <th></th>
    </thead>
    <tbody *ngFor="let data of response">
      <tr
        *ngFor="
          let jobRun of jobContainer.history.slice(
            (pages.current - 1) * pages.per,
            pages.current * pages.per
          )
        "
      >
        <div [ngSwitch]="data.status">
          <td>
            <fa-icon
              *ngSwitchCase="'RUNNING'"
              title="Running"
              [style.color]="'blue'"
              [icon]="faSpinner"
              [spin]="true"
            ></fa-icon>
            <fa-icon
              *ngSwitchCase="'FAILED'"
              title="Failed"
              [style.color]="'red'"
              [icon]="faExclamationCircle"
            ></fa-icon>
            <fa-icon
              *ngSwitchCase="'COMPLETED'"
              title="Completed"
              [style.color]="'green'"
              [icon]="faCheckCircle"
            ></fa-icon>
          </td>
        </div>
        <td>{{ data.datetime | date: "medium" }}</td>
        <td>{{ jobContainer.job.type.name }}</td>
        <td>
          {{ humanizer(data.duration, { delimiter: " ", largest: 2 }) }}
        </td>
        <td>
          {{ data.id }}
        </td>
        <td>
          {{ data.status }}
        </td>
        <td>
          <div class="tools">
            <fa-icon
              [routerLink]="[
                '/workloads',
                jobContainer.job.id,
                'console',
                data.id
              ]"
              (click)="onConsoleClick(jobContainer.job)"
              [icon]="faTerminal"
              title="Console"
              class="button"
            ></fa-icon>
          </div>
        </td>
        <td>
          <fa-icon
            *ngIf="jobRun.status == STATUS.RUNNING"
            [icon]="faPause"
            class="button"
          ></fa-icon>
        </td>
      </tr>
    </tbody>
  </table>
  <div class="table-footer">
    <span>
      <fa-icon
        (click)="onPaginate(-1)"
        class="button"
        [icon]="faChevronLeft"
        [style.marginRight]="'10px'"
      ></fa-icon>
      <fa-icon
        (click)="onPaginate(1)"
        class="button"
        [icon]="faChevronRight"
      ></fa-icon>
      Page {{ pages.current }} of
      {{ jobContainer.history.length / pages.per | round: "ceil" }}
    </span>
  </div>
</div>

I attached images to highlight the portions that may be the cause for convenience, as well as images for the output:

[TypeScript File][1] [HTML Template][2] [Running Status][3]
[Completed Status (Only After Re-routing)][4]

[1]: https://i.stack.imgur.com/9CrQ1.png [2]: https://i.stack.imgur.com/myZX0.png [3]: https://i.stack.imgur.com/CsujV.png [4]: https://i.stack.imgur.com/truFt.png

Thank you very much in advance for your time!

Be safe and take care!

Edit: I resorted to blockquoting my post to avoid the error with respect to code formatting

Leave a Comment