GUI stoppig when plenty of deletions
[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 import { EmptyObservable } from 'rxjs/observable/EmptyObservable';
31
32 export interface Job {
33   jobId: string;
34   typeId: string;
35   targetUri: string;
36   owner: string;
37   prodIds: string[];
38 }
39
40 @Component({
41   selector: "nrcp-jobs-list",
42   templateUrl: "./jobs-list.component.html",
43   styleUrls: ["./jobs-list.component.scss"],
44 })
45 export class JobsListComponent implements OnInit {
46   @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
47   jobsDataSource: MatTableDataSource<Job>;
48   jobForm: FormGroup;
49   darkMode: boolean;
50
51   private jobsSubject$ = new BehaviorSubject<Job[]>([]);
52   private refresh$ = new BehaviorSubject("");
53   private loadingSubject$ = new BehaviorSubject<boolean>(false);
54   private polling$ = new BehaviorSubject(0);
55   public loading$ = this.loadingSubject$.asObservable();
56   subscription: Subscription;
57   checked: boolean = false;
58   firstTime: boolean = true;
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       mergeMap((jobIds) =>
101         forkJoin(jobIds.map((jobId) => {
102           return forkJoin([
103             of(jobId),
104             this.consumerService.getJobInfo(jobId).pipe(
105               catchError(err => {
106                 return of([-1]);
107               })),
108             this.consumerService.getConsumerStatus(jobId).pipe(
109               catchError(err => {
110                 return of([-1]);
111               })),
112           ])
113         }))
114       ),
115       finalize(() => {
116         this.loadingSubject$.next(false)
117       })
118
119     );
120
121     const whenToRefresh$ = of('').pipe(
122       delay(10000),
123       tap((_) => this.refresh$.next('')),
124       skip(1),
125     );
126
127     const poll$ = concat(jobsInfo$, whenToRefresh$);
128
129     const refreshedJobs$ = this.refresh$.pipe(
130       tap((_) => {
131         this.loadingSubject$.next(true);
132       }),
133       concatMap((_) => this.checked ? poll$ : jobsInfo$),
134       map((response) => this.extractJobs(response))
135     );
136
137     return this.polling$
138       .pipe(
139         concatMap((value) => {
140           let pollCondition = value == 0 || this.checked;
141           return pollCondition ? refreshedJobs$ : EMPTY;
142         }),
143       )
144       .subscribe();
145   }
146
147   ngOnDestroy() {
148     this.subscription.unsubscribe();
149   }
150
151   clearFilter() {
152     this.jobForm.get("jobId").setValue("");
153     this.jobForm.get("typeId").setValue("");
154     this.jobForm.get("owner").setValue("");
155     this.jobForm.get("targetUri").setValue("");
156     this.jobForm.get("prodIds").setValue("");
157   }
158
159   sortJobs(sort: Sort) {
160     const data = this.jobsDataSource.data;
161     data.sort((a: Job, b: Job) => {
162       const isAsc = sort.direction === "asc";
163       switch (sort.active) {
164         case "jobId":
165           return this.compare(a.jobId, b.jobId, isAsc);
166         case "typeId":
167           return this.compare(a.typeId, b.typeId, isAsc);
168         case "owner":
169           return this.compare(a.owner, b.owner, isAsc);
170         case "targetUri":
171           return this.compare(a.targetUri, b.targetUri, isAsc);
172         case "prodIds":
173           return this.compare(a.prodIds, b.prodIds, isAsc);
174         default:
175           return 0;
176       }
177     });
178     this.jobsDataSource.data = data;
179   }
180
181   stopPolling(checked) {
182     this.checked = checked;
183     this.polling$.next(this.jobs().length);
184     this.refreshDataClick();
185   }
186
187   compare(a: any, b: any, isAsc: boolean) {
188     return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
189   }
190
191   stopSort(event: any) {
192     event.stopPropagation();
193   }
194
195   isDataIncluding(data: string, filter: string): boolean {
196     const transformedFilter = filter.trim().toLowerCase();
197     return data.toLowerCase().includes(transformedFilter);
198   }
199
200   isArrayIncluding(data: string[], filter: string): boolean {
201     if (!data)
202       return true;
203     for (let i = 0; i < data.length; i++) {
204       return this.isDataIncluding(data[i], filter);
205     }
206   }
207
208   getJobTypeId(job: Job): string {
209     if (job.typeId) {
210       return job.typeId;
211     }
212     return "< No type >";
213   }
214
215   getJobOwner(job: Job): string {
216     if (job.owner) {
217       return job.owner;
218     }
219     return "< No owner >";
220   }
221
222   public jobs(): Job[] {
223     return this.jobsSubject$.value;
224   }
225
226   private extractJobs(res: any) {
227     this.clearFilter();
228     let jobList = [];
229     res.forEach(element => {
230       if(element[1] != -1 && element[2] != -1){
231         let jobObj = <Job>{};
232         jobObj.jobId = element[0];
233         jobObj.owner = element[1].job_owner;
234         jobObj.targetUri = element[1].job_result_uri;
235         jobObj.typeId = element[1].info_type_id;
236         jobObj.prodIds = (element[2].producers) ? element[2].producers : ["No Producers"];
237         jobList = jobList.concat(jobObj);
238       }
239     });
240
241     this.jobsSubject$.next(jobList);
242     if (this.firstTime && jobList.length > 0) {
243       this.polling$.next(jobList.length);
244       this.firstTime = false;
245     }
246     return jobList;
247   }
248
249   refreshDataClick() {
250     this.refresh$.next("");
251   }
252 }