Bug-Fix Last instance does not disappear when deleted
[portal/nonrtric-controlpanel.git] / webapp-frontend / src / app / ei-coordinator / jobs-list / jobs-list.component.ts
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2021 Nordix Foundation
6  * %%
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================LICENSE_END===================================
19  */
20 import { Component, OnInit, ViewChild } from "@angular/core";
21 import { FormControl, FormGroup } from "@angular/forms";
22 import { MatPaginator } from "@angular/material/paginator";
23 import { Sort } from "@angular/material/sort";
24 import { MatTableDataSource } from "@angular/material/table";
25 import { EMPTY, forkJoin, of, pipe, Subscription, concat, Observable } from "rxjs";
26 import { BehaviorSubject } from "rxjs/BehaviorSubject";
27 import { mergeMap, finalize, map, tap, concatMap, delay, skip, catchError } from "rxjs/operators";
28 import { ConsumerService } from "@services/ei/consumer.service";
29 import { UiService } from "@services/ui/ui.service";
30
31 export interface Job {
32   jobId: string;
33   typeId: string;
34   targetUri: string;
35   owner: string;
36   prodIds: string[];
37 }
38
39 @Component({
40   selector: "nrcp-jobs-list",
41   templateUrl: "./jobs-list.component.html",
42   styleUrls: ["./jobs-list.component.scss"],
43 })
44 export class JobsListComponent implements OnInit {
45   @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
46   jobsDataSource: MatTableDataSource<Job>;
47   jobForm: FormGroup;
48   darkMode: boolean;
49
50   private jobsSubject$ = new BehaviorSubject<Job[]>([]);
51   private refresh$ = new BehaviorSubject("");
52   private loadingSubject$ = new BehaviorSubject<boolean>(false);
53   private polling$ = new BehaviorSubject(0);
54   public loading$ = this.loadingSubject$.asObservable();
55   subscription: Subscription;
56   checked: boolean = false;
57   firstTime: boolean = true;
58   jobList: Job[] = [];
59
60   constructor(private consumerService: ConsumerService, private ui: UiService) {
61     this.jobForm = new FormGroup({
62       jobId: new FormControl(""),
63       typeId: new FormControl(""),
64       owner: new FormControl(""),
65       targetUri: new FormControl(""),
66       prodIds: new FormControl(""),
67     });
68   }
69
70   ngOnInit(): void {
71     this.subscription = this.dataSubscription();
72
73     this.jobsSubject$.subscribe((data) => {
74       this.jobsDataSource = new MatTableDataSource<Job>(data);
75       this.jobsDataSource.paginator = this.paginator;
76
77       this.jobsDataSource.filterPredicate = ((data: Job, filter) => {
78         let searchTerms = JSON.parse(filter);
79         return (
80           this.isDataIncluding(data.targetUri, searchTerms.targetUri) &&
81           this.isDataIncluding(data.jobId, searchTerms.jobId) &&
82           this.isDataIncluding(data.owner, searchTerms.owner) &&
83           this.isDataIncluding(data.typeId, searchTerms.typeId) &&
84           this.isArrayIncluding(data.prodIds, searchTerms.prodIds)
85         );
86       }) as (data: Job, filter: any) => boolean;
87     });
88
89     this.jobForm.valueChanges.subscribe((value) => {
90       this.jobsDataSource.filter = JSON.stringify(value);
91     });
92
93     this.ui.darkModeState.subscribe((isDark) => {
94       this.darkMode = isDark;
95     });
96   }
97
98   dataSubscription(): Subscription {
99     const jobsInfo$ = this.consumerService.getJobIds().pipe(
100       tap((_) => {
101         this.jobList = [] as Job[];
102       }),
103       mergeMap((jobIds) =>
104         forkJoin(jobIds.map((jobId) => {
105           return forkJoin([
106             of(jobId),
107             this.consumerService.getJobInfo(jobId).pipe(
108               catchError(err => {
109                 return of([-1]);
110               })),
111             this.consumerService.getConsumerStatus(jobId).pipe(
112               catchError(err => {
113                 return of([-1]);
114               })),
115           ])
116         }))
117       ),
118       finalize(() => {
119         this.loadingSubject$.next(false);
120         this.jobsSubject$.next(this.jobList);
121       })
122
123     );
124
125     const whenToRefresh$ = of('').pipe(
126       delay(10000),
127       tap((_) => this.refresh$.next('')),
128       skip(1),
129     );
130
131     const poll$ = concat(jobsInfo$, whenToRefresh$);
132
133     const refreshedJobs$ = this.refresh$.pipe(
134       tap((_) => {
135         this.loadingSubject$.next(true);
136       }),
137       concatMap((_) => this.checked ? poll$ : jobsInfo$),
138       map((response) => this.extractJobs(response))
139     );
140
141     return this.polling$
142       .pipe(
143         concatMap((value) => {
144           let pollCondition = value == 0 || this.checked;
145           return pollCondition ? refreshedJobs$ : EMPTY;
146         })
147       )
148       .subscribe();
149   }
150
151   ngOnDestroy() {
152     this.subscription.unsubscribe();
153   }
154
155   clearFilter() {
156     this.jobForm.get("jobId").setValue("");
157     this.jobForm.get("typeId").setValue("");
158     this.jobForm.get("owner").setValue("");
159     this.jobForm.get("targetUri").setValue("");
160     this.jobForm.get("prodIds").setValue("");
161   }
162
163   sortJobs(sort: Sort) {
164     const data = this.jobsDataSource.data;
165     data.sort((a: Job, b: Job) => {
166       const isAsc = sort.direction === "asc";
167       switch (sort.active) {
168         case "jobId":
169           return this.compare(a.jobId, b.jobId, isAsc);
170         case "typeId":
171           return this.compare(a.typeId, b.typeId, isAsc);
172         case "owner":
173           return this.compare(a.owner, b.owner, isAsc);
174         case "targetUri":
175           return this.compare(a.targetUri, b.targetUri, isAsc);
176         case "prodIds":
177           return this.compare(a.prodIds, b.prodIds, isAsc);
178         default:
179           return 0;
180       }
181     });
182     this.jobsDataSource.data = data;
183   }
184
185   stopPolling(checked) {
186     this.checked = checked;
187     this.polling$.next(this.jobs().length);
188     this.refreshDataClick();
189   }
190
191   compare(a: any, b: any, isAsc: boolean) {
192     return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
193   }
194
195   stopSort(event: any) {
196     event.stopPropagation();
197   }
198
199   isDataIncluding(data: string, filter: string): boolean {
200     const transformedFilter = filter.trim().toLowerCase();
201     return data.toLowerCase().includes(transformedFilter);
202   }
203
204   isArrayIncluding(data: string[], filter: string): boolean {
205     if (!data)
206       return true;
207     for (let i = 0; i < data.length; i++) {
208       return this.isDataIncluding(data[i], filter);
209     }
210   }
211
212   getJobTypeId(job: Job): string {
213     if (job.typeId) {
214       return job.typeId;
215     }
216     return "< No type >";
217   }
218
219   getJobOwner(job: Job): string {
220     if (job.owner) {
221       return job.owner;
222     }
223     return "< No owner >";
224   }
225
226   public jobs(): Job[] {
227     return this.jobsSubject$.value;
228   }
229
230   private extractJobs(res: any) {
231     this.clearFilter();
232     res.forEach(element => {
233       if(element[1] != -1 && element[2] != -1){
234         let jobObj = <Job>{};
235         jobObj.jobId = element[0];
236         jobObj.owner = element[1].job_owner;
237         jobObj.targetUri = element[1].job_result_uri;
238         jobObj.typeId = element[1].info_type_id;
239         jobObj.prodIds = (element[2].producers) ? element[2].producers : ["No Producers"];
240         this.jobList = this.jobList.concat(jobObj);
241       }
242     });
243
244     if (this.firstTime && this.jobList.length > 0) {
245       this.polling$.next(this.jobList.length);
246       this.firstTime = false;
247     }
248     return this.jobList;
249   }
250
251   refreshDataClick() {
252     this.refresh$.next("");
253   }
254
255   hasJobs(): boolean {
256     return this.jobs().length > 0;
257   }
258
259 }