Include datasource of jobs and producers into component 54/5754/2
authorychacon <yennifer.chacon@est.tech>
Wed, 3 Mar 2021 12:21:26 +0000 (13:21 +0100)
committerychacon <yennifer.chacon@est.tech>
Mon, 15 Mar 2021 14:45:29 +0000 (15:45 +0100)
Issue-ID: NONRTRIC-453
Signed-off-by: ychacon <yennifer.chacon@est.tech>
Change-Id: I3b743e87149c80115571e92e0bfd9696d8fef659

13 files changed:
webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.spec.ts
webapp-frontend/src/app/ei-coordinator/ei-coordinator.component.ts
webapp-frontend/src/app/ei-coordinator/ei-coordinator.module.ts
webapp-frontend/src/app/ei-coordinator/ei-job.datasource.spec.ts [deleted file]
webapp-frontend/src/app/ei-coordinator/ei-job.datasource.ts [deleted file]
webapp-frontend/src/app/ei-coordinator/ei-producer.datasource.spec.ts [deleted file]
webapp-frontend/src/app/ei-coordinator/ei-producer.datasource.ts [deleted file]
webapp-frontend/src/app/ei-coordinator/jobs-list/jobs-list.component.html
webapp-frontend/src/app/ei-coordinator/jobs-list/jobs-list.component.spec.ts
webapp-frontend/src/app/ei-coordinator/jobs-list/jobs-list.component.ts
webapp-frontend/src/app/ei-coordinator/producers-list/producers-list.component.html
webapp-frontend/src/app/ei-coordinator/producers-list/producers-list.component.spec.ts
webapp-frontend/src/app/ei-coordinator/producers-list/producers-list.component.ts

index 7c50ca0..cdc1b84 100644 (file)
@@ -100,12 +100,12 @@ describe('EICoordinatorComponent', () => {
 
     it('should refresh tables', async () => {
       let refreshButton = await loader.getHarness(MatButtonHarness.with({ selector: '#refreshButton' }));
-      spyOn(component.producersList, 'refresh');
-      spyOn(component.jobComponent, 'refresh');
+      spyOn(component.producersList, 'loadProducers');
+      spyOn(component.jobComponent, 'loadJobs');
       await refreshButton.click();
 
-      expect(component.jobComponent.refresh).toHaveBeenCalled();
-      expect(component.producersList.refresh).toHaveBeenCalled();
+      expect(component.jobComponent.loadJobs).toHaveBeenCalled();
+      expect(component.producersList.loadProducers).toHaveBeenCalled();
     });
   });
 
@@ -120,7 +120,7 @@ describe('EICoordinatorComponent', () => {
     ]
   })
   class JobsListStubComponent {
-    refresh() { }
+    loadJobs() { }
   }
 
   @Component({
@@ -134,7 +134,7 @@ describe('EICoordinatorComponent', () => {
     ]
   })
   class ProducerListStubComponent {
-    refresh() { }
+    loadProducers() { }
   }
 
 });
index d531948..1bb082c 100644 (file)
@@ -50,7 +50,7 @@ export class EICoordinatorComponent implements OnInit {
     }
 
     refreshTables() {
-        this.jobComponent.refresh();
-        this.producersList.refresh();
+        this.jobComponent.loadJobs();
+        this.producersList.loadProducers();
     }
 }
index cda22ce..8da3d03 100644 (file)
@@ -33,6 +33,8 @@ import { MatSortModule } from '@angular/material/sort';
 import { MatButtonModule } from '@angular/material/button';
 import { FlexLayoutModule } from '@angular/flex-layout';
 import { EiCardComponent } from './ei-card/ei-card.component';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
 
 
 const routes: Routes = [
@@ -55,7 +57,9 @@ const routes: Routes = [
     ReactiveFormsModule,
     FormsModule,
     MatSortModule,
+    MatPaginatorModule,
     MatButtonModule,
+    MatProgressSpinnerModule,
     RouterModule.forChild(routes)
   ],
   exports: [
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-job.datasource.spec.ts b/webapp-frontend/src/app/ei-coordinator/ei-job.datasource.spec.ts
deleted file mode 100644 (file)
index 2487088..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*-
- * ========================LICENSE_START=================================
- * O-RAN-SC
- * %%
- * Copyright (C) 2021 Nordix Foundation
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================LICENSE_END===================================
- */
-import { TestBed } from '@angular/core/testing';
-import { of } from 'rxjs';
-
-import { EIJobDataSource } from './ei-job.datasource';
-import { EIService } from '../services/ei/ei.service';
-import { ToastrModule } from 'ngx-toastr';
-import { EIJob } from '../interfaces/ei.types';
-
-describe('EIJobDataSource', () => {
-    let dataSource: EIJobDataSource;
-    let eiServiceSpy: any;
-
-    const job = { ei_job_identity: '1', ei_job_data: 'data', ei_type_identity: 'Type ID 1', target_uri: 'hhtp://url', owner: 'owner' };
-
-    beforeEach(() => {
-        eiServiceSpy = jasmine.createSpyObj('EIService', ['getProducerIds', 'getJobsForProducer']);
-
-        eiServiceSpy.getProducerIds.and.returnValue(of(['producer1', 'producer2']));
-        eiServiceSpy.getJobsForProducer.and.returnValue(of([job]));
-        TestBed.configureTestingModule({
-            imports: [ToastrModule.forRoot()],
-            providers: [
-                { provide: EIService, useValue: eiServiceSpy }
-            ]
-        });
-    });
-
-    it('should create', () => {
-        dataSource = TestBed.inject(EIJobDataSource);
-        expect(dataSource).toBeTruthy();
-    });
-
-    it('#getJobs', () => {
-        dataSource.loadJobs();
-        const actualJobs: EIJob[] = dataSource.eiJobs();
-        expect(actualJobs).toEqual([job, job]);
-        expect(dataSource.rowCount).toEqual(2);
-    });
-});
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-job.datasource.ts b/webapp-frontend/src/app/ei-coordinator/ei-job.datasource.ts
deleted file mode 100644 (file)
index b008f80..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-/*-
- * ========================LICENSE_START=================================
- * O-RAN-SC
- * %%
- * Copyright (C) 2019 Nordix Foundation
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================LICENSE_END===================================
- */
-
-import { Injectable } from '@angular/core';
-
-import { BehaviorSubject } from 'rxjs/BehaviorSubject';
-import { mergeMap, finalize } from 'rxjs/operators';
-import { Observable, forkJoin } from 'rxjs';
-
-import { EIJob } from '../interfaces/ei.types';
-import { EIService } from '../services/ei/ei.service';
-
-
-@Injectable({
-    providedIn: 'root'
-})
-
-export class EIJobDataSource {
-
-    private jobs: Array<EIJob> = [];
-
-    public eiJobs(): EIJob[] {
-        return this.jobs;
-    }
-
-    public eiJobsSubject(): Observable<EIJob[]> {
-        return this.jobsSubject.asObservable() as Observable<EIJob[]>;
-    }
-
-    private loadingSubject = new BehaviorSubject<boolean>(false);
-    private jobsSubject = new BehaviorSubject<EIJob[]>([]);
-
-    public loading$ = this.loadingSubject.asObservable();
-
-    public rowCount = 1; // hide footer during intial load
-
-    constructor(
-        private eiSvc: EIService) {
-    }
-
-    loadJobs() {
-        this.loadingSubject.next(true);
-        this.jobs = [];
-        this.eiSvc.getProducerIds().pipe(
-            mergeMap(prodIds =>
-                forkJoin(prodIds.map(id => this.eiSvc.getJobsForProducer(id)))),
-            mergeMap(result => result),
-            finalize(() => this.loadingSubject.next(false))
-        ).subscribe(result => {
-            this.jobs = this.jobs.concat(result);
-            this.jobsSubject.next(this.jobs);
-        });
-        this.rowCount = this.jobs.length;
-    }
-
-
-}
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-producer.datasource.spec.ts b/webapp-frontend/src/app/ei-coordinator/ei-producer.datasource.spec.ts
deleted file mode 100644 (file)
index d1f22e2..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*-
- * ========================LICENSE_START=================================
- * O-RAN-SC
- * %%
- * Copyright (C) 2021 Nordix Foundation
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================LICENSE_END===================================
- */
-import { TestBed } from '@angular/core/testing';
-import { of } from 'rxjs';
-
-import { EIService } from '../services/ei/ei.service';
-import { ToastrModule } from 'ngx-toastr';
-import { EIProducer, OperationalState, ProducerRegistrationInfo, ProducerStatus } from '../interfaces/ei.types';
-import { EIProducerDataSource } from './ei-producer.datasource';
-
-describe('EIProducerDataSource', () => {
-    let dataSource: EIProducerDataSource;
-    let eiServiceSpy: any;
-
-    let producer1 = {
-        supported_ei_types: ['type1', 'type2']
-    } as ProducerRegistrationInfo;
-    let producer2 = {
-        supported_ei_types: ['type3', 'type4']
-    } as ProducerRegistrationInfo;
-    let producerStatus1 = {
-        operational_state: OperationalState.ENABLED
-    } as ProducerStatus;
-    let producerStatus2 = {
-        operational_state: OperationalState.DISABLED
-    } as ProducerStatus;
-
-    let expectedProducer1 = {
-        ei_producer_id: 'producer1',
-        ei_producer_types: ['type1', 'type2'],
-        status: 'ENABLED'
-    } as EIProducer;
-    let expectedProducer2 = {
-        ei_producer_id: 'producer2',
-        ei_producer_types: ['type3', 'type4'],
-        status: 'DISABLED'
-    } as EIProducer;
-
-    beforeEach(() => {
-        eiServiceSpy = jasmine.createSpyObj('EIService', ['getProducerIds', 'getProducer', 'getProducerStatus']);
-
-        eiServiceSpy.getProducerIds.and.returnValue(of(['producer1', 'producer2']));
-        eiServiceSpy.getProducer.and.returnValues(of(producer1), of(producer2));
-        eiServiceSpy.getProducerStatus.and.returnValues(of(producerStatus1), of(producerStatus2));
-        TestBed.configureTestingModule({
-            imports: [ToastrModule.forRoot()],
-            providers: [
-                { provide: EIService, useValue: eiServiceSpy }
-            ]
-        });
-    });
-
-    it('should create', () => {
-        dataSource = TestBed.inject(EIProducerDataSource);
-        expect(dataSource).toBeTruthy();
-    });
-
-    it('#loadProducers', () => {
-        dataSource.loadProducers();
-        const actualProducers: EIProducer[] = dataSource.eiProducers();
-        expect(actualProducers).toEqual([expectedProducer1, expectedProducer2]);
-        expect(dataSource.rowCount).toEqual(2);
-    });
-});
diff --git a/webapp-frontend/src/app/ei-coordinator/ei-producer.datasource.ts b/webapp-frontend/src/app/ei-coordinator/ei-producer.datasource.ts
deleted file mode 100644 (file)
index cb4809c..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-/*-
- * ========================LICENSE_START=================================
- * O-RAN-SC
- * %%
- * Copyright (C) 2019 Nordix Foundation
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================LICENSE_END===================================
- */
-
-import { Injectable } from '@angular/core';
-
-import { BehaviorSubject } from 'rxjs/BehaviorSubject';
-import { mergeMap, finalize } from 'rxjs/operators';
-import { Observable, forkJoin, of } from 'rxjs';
-
-import { EIProducer } from '../interfaces/ei.types';
-import { EIService } from '../services/ei/ei.service';
-
-@Injectable({
-    providedIn: 'root'
-})
-
-export class EIProducerDataSource {
-
-    private producers: Array<EIProducer> = [];
-
-    public eiProducers(): EIProducer[] {
-        return this.producers;
-    }
-
-    public eiProducersSubject(): Observable<EIProducer[]> {
-        return this.producersSubject.asObservable() as Observable<EIProducer[]>;
-    }
-
-    private loadingSubject = new BehaviorSubject<boolean>(false);
-    private producersSubject = new BehaviorSubject<EIProducer[]>([]);
-
-    public loading$ = this.loadingSubject.asObservable();
-
-    public rowCount = 1; // hide footer during intial load
-
-    constructor(
-        private eiSvc: EIService) {
-    }
-
-    loadProducers() {
-        this.loadingSubject.next(true);
-        this.producers = [];
-
-        this.eiSvc.getProducerIds().pipe(
-            mergeMap(prodIds =>
-                forkJoin(prodIds.map(id => {
-                    return forkJoin([
-                        of(id),
-                        this.eiSvc.getProducer(id),
-                        this.eiSvc.getProducerStatus(id)
-                    ])
-                })
-                )),
-            finalize(() => this.loadingSubject.next(false))
-        ).subscribe(result => {
-            this.producers = result.map(producer => {
-                let eiProducer = <EIProducer>{};
-                eiProducer.ei_producer_id = producer[0];
-                eiProducer.ei_producer_types = producer[1].supported_ei_types;
-                eiProducer.status = producer[2].operational_state.toString();
-                return eiProducer;
-            });
-            this.producersSubject.next(this.producers);
-        });
-        this.rowCount = this.producers.length;
-    }
-}
\ No newline at end of file
index 840cc95..c7f3a20 100644 (file)
@@ -18,8 +18,12 @@ limitations under the License.
 ========================LICENSE_END===================================
 -->
 <div class="table-container">
+    <div class="spinner-container" style="display: flex; justify-content: center; align-items: center;"
+        *ngIf="loading$ | async">
+        <mat-spinner></mat-spinner>
+    </div>
     <mat-table id="jobsTable" [dataSource]="jobsDataSource" fixedLayout matSort (matSortChange)="sortJobs($event)"
-        class="ei-coordinator-table mat-elevation-z8">
+        matSortDisableClear matSortDirection="asc" class="ei-coordinator-table mat-elevation-z8">
         <ng-container matColumnDef="id">
             <mat-header-cell *matHeaderCellDef mat-sort-header>
                 <div (click)="stopSort($event)">
@@ -75,4 +79,6 @@ limitations under the License.
         <mat-header-row *matHeaderRowDef="['id', 'typeId', 'owner', 'targetUri']"></mat-header-row>
         <mat-row *matRowDef="let row; columns: ['id', 'typeId', 'owner', 'targetUri'];"></mat-row>
     </mat-table>
+    <mat-paginator [length]="jobs()?.length" [pageSize]="10" [pageSizeOptions]="[5, 10, 25, 100]" showFirstLastButtons
+        class="ei-coordinator-table mat-elevation-z8"></mat-paginator>
 </div>
\ No newline at end of file
index 24750eb..0e1f9d3 100644 (file)
@@ -21,141 +21,244 @@ import { HarnessLoader } from '@angular/cdk/testing';
 import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
 import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { ReactiveFormsModule, FormBuilder } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
 import { MatInputHarness } from '@angular/material/input/testing';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatSortModule } from '@angular/material/sort';
+import { MatSortHarness } from '@angular/material/sort/testing';
+import { MatPaginatorHarness } from '@angular/material/paginator/testing';
 import { MatTableModule } from '@angular/material/table';
 import { MatTableHarness } from '@angular/material/table/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
 import { of } from 'rxjs/observable/of';
-import { EIJob } from 'src/app/interfaces/ei.types';
-import { UiService } from 'src/app/services/ui/ui.service';
-import { EIJobDataSource } from '../ei-job.datasource';
+import { EIJob } from '../../interfaces/ei.types';
+import { EIService } from '../../services/ei/ei.service';
+import { UiService } from '../../services/ui/ui.service';
 
 import { JobsListComponent } from './jobs-list.component';
 
+let component: JobsListComponent;
+let fixture: ComponentFixture<JobsListComponent>;
+
+const job1 = {
+  ei_job_identity: 'job1',
+  ei_type_identity: 'type1',
+  owner: 'owner1',
+  target_uri: 'http://one'
+} as EIJob;
+const job2 = {
+  ei_job_identity: 'job2',
+  ei_type_identity: 'type2',
+  owner: 'owner2',
+  target_uri: 'http://two'
+} as EIJob;
+
 describe('JobsListComponent', () => {
-  let component: JobsListComponent;
-  let fixture: ComponentFixture<JobsListComponent>;
   let loader: HarnessLoader;
-  let eiJobComponent: jasmine.SpyObj<JobsListComponent>;
-  let jobDataSourceSpy: jasmine.SpyObj<EIJobDataSource>;
-
-  const job1 = {
-    ei_job_identity: 'job1',
-    ei_type_identity: 'type1',
-    owner: 'owner1',
-    target_uri: 'http://one'
-  } as EIJob;
-  const job2 = {
-    ei_job_identity: 'job2',
-    ei_type_identity: 'type2',
-    owner: 'owner2',
-    target_uri: 'http://two'
-  } as EIJob;
 
   beforeEach(async(() => {
-    eiJobComponent = jasmine.createSpyObj('producersListSpy', ['refresh']);
-    jobDataSourceSpy = jasmine.createSpyObj('EIJobDataSource', ['loadJobs', 'eiJobs', 'eiJobsSubject']);
-
-    jobDataSourceSpy.eiJobsSubject.and.returnValue(of([job1, job2]));
+    const spy = jasmine.createSpyObj('EIService', ['getProducerIds', 'getJobsForProducer']);
 
     TestBed.configureTestingModule({
       imports: [
         MatTableModule,
-        ReactiveFormsModule
-      ],
-      schemas: [
-        CUSTOM_ELEMENTS_SCHEMA
+        MatPaginatorModule,
+        FormsModule,
+        MatSortModule,
+        ReactiveFormsModule,
+        BrowserAnimationsModule,
+        MatFormFieldModule,
+        MatInputModule
       ],
+      schemas: [CUSTOM_ELEMENTS_SCHEMA],
       declarations: [JobsListComponent],
       providers: [
-        { provide: EIJobDataSource, useValue: jobDataSourceSpy },
-        UiService,
-        FormBuilder,
+        { provide: EIService, useValue: spy },
+        UiService
       ]
     })
-      .compileComponents();
+      .compileComponents()
+      .then(() => {
+        fixture = TestBed.createComponent(JobsListComponent);
+        component = fixture.componentInstance;
+        loader = TestbedHarnessEnvironment.loader(fixture);
+      });
   }));
 
   const expectedJob1Row = { id: 'job1', typeId: 'type1', owner: 'owner1', targetUri: 'http://one' };
 
-  beforeEach(() => {
-    fixture = TestBed.createComponent(JobsListComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-    loader = TestbedHarnessEnvironment.loader(fixture);
-  });
-
   it('should create', () => {
     expect(component).toBeTruthy();
   });
 
-  it('should contain job table with correct columns', async () => {
-    let producersTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
-    let headerRow = (await producersTable.getHeaderRows())[0];
-    let headers = await headerRow.getCellTextByColumnName();
+  describe('#content', () => {
+
+    it('should loadJobs', () => {
+      setServiceSpy();
+      component.loadJobs();
+      const actualJobs: EIJob[] = component.jobs();
+      expect(actualJobs.length).toEqual(4);
+      expect(actualJobs).toEqual([job1, job2, job1, job2]);
+    });
+
+    it('should contain job table with correct columns', async () => {
+      setServiceSpy();
+      let jobsTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
+      let headerRow = (await jobsTable.getHeaderRows())[0];
+      let headers = await headerRow.getCellTextByColumnName();
+
+      expect(headers).toEqual({ id: 'Job ID', typeId: 'Type ID', owner: 'Owner', targetUri: 'Target URI' });
+    });
+
+    it('should set correct dark mode from UIService', () => {
+      setServiceSpy();
+      component.ngOnInit();
+      const uiService: UiService = TestBed.inject(UiService);
+      expect(component.darkMode).toBeTruthy();
 
-    expect(headers).toEqual({ id: 'Job ID', typeId: 'Type ID', owner: 'Owner', targetUri: 'Target URI' });
+      uiService.darkModeState.next(false);
+      fixture.detectChanges();
+      expect(component.darkMode).toBeFalsy();
+    });
   });
 
-  it('should contain data after initialization', async () => {
-    component.ngOnInit();
-    const expectedJobRows = [
-      expectedJob1Row,
-      { id: 'job2', typeId: 'type2', owner: 'owner2', targetUri: 'http://two' }
-    ];
-    let jobsTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
-    let jobRows = await jobsTable.getRows();
-    expect(jobRows.length).toEqual(2);
-    jobRows.forEach(row => {
-      row.getCellTextByColumnName().then(values => {
-        expect(expectedJobRows).toContain(jasmine.objectContaining(values));
+  describe('#jobsTable', () => {
+
+    it('should contain data after initialization', async () => {
+      setServiceSpy();
+      component.ngOnInit();
+      const expectedJobRows = [
+        expectedJob1Row,
+        { id: 'job2', typeId: 'type2', owner: 'owner2', targetUri: 'http://two' }
+      ];
+      let jobsTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
+      let jobRows = await jobsTable.getRows();
+      expect(jobRows.length).toEqual(4);
+      jobRows.forEach(row => {
+        row.getCellTextByColumnName().then(values => {
+          expect(expectedJobRows).toContain(jasmine.objectContaining(values));
+        });
       });
     });
-  });
 
-  it('should display default values for non required properties ', async () => {
-    const jobMissingProperties = {
-      ei_job_identity: 'job1',
-      target_uri: 'http://one'
-    } as EIJob;
-    const jobs: EIJob[] = [jobMissingProperties];
-    jobDataSourceSpy.eiJobsSubject.and.returnValue(of(jobs));
-    component.ngOnInit();
-
-    const expectedJobRow = { id: 'job1', typeId: '< No type >', owner: '< No owner >', targetUri: 'http://one' };
-    let jobsTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
-    let jobRows = await jobsTable.getRows();
-    expect(await jobRows[0].getCellTextByColumnName()).toEqual(expectedJobRow);
-  });
+    it('should display default values for non required properties ', async () => {
+
+      const jobMissingProperties = {
+        "ei_job_identity": "job1",
+        "ei_job_data": {
+          "jobparam2": "value2_job2",
+          "jobparam3": "value3_job2",
+          "jobparam1": "value1_job2"
+        },
+        "target_uri": "http://one"
+      } as EIJob;
+
+      let eiServiceSpy = TestBed.inject(EIService) as jasmine.SpyObj<EIService>;
+      eiServiceSpy.getProducerIds.and.returnValue(of(['producer1']));
+      eiServiceSpy.getJobsForProducer.and.returnValue(of([jobMissingProperties]));
+
+      component.ngOnInit();
+      const expectedJobRow = { id: 'job1', typeId: '< No type >', owner: '< No owner >', targetUri: 'http://one' };
+      let jobsTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
+      let jobRows = await jobsTable.getRows();
+      expect(await jobRows[0].getCellTextByColumnName()).toEqual(expectedJobRow);
+    });
+
+    it('filtering', async () => {
+      setServiceSpy();
+      component.ngOnInit();
+      let jobsTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
 
-  it('filtering', async () => {
-    component.ngOnInit();
-    let jobsTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
-
-    let idFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#jobIdFilter' }));
-    await idFilterInput.setValue("1");
-    let jobRows = await jobsTable.getRows();
-    expect(jobRows.length).toEqual(1);
-    expect(await jobRows[0].getCellTextByColumnName()).toEqual(expectedJob1Row);
-
-    idFilterInput.setValue('');
-    let typeIdFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#jobTypeIdFilter' }));
-    await typeIdFilterInput.setValue("1");
-    jobRows = await jobsTable.getRows();
-    expect(jobRows.length).toEqual(1);
-
-    typeIdFilterInput.setValue('');
-    let ownerFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#jobOwnerFilter' }));
-    await ownerFilterInput.setValue("1");
-    jobRows = await jobsTable.getRows();
-    expect(jobRows.length).toEqual(1);
-    expect(await jobRows[0].getCellTextByColumnName()).toEqual(expectedJob1Row);
-
-    ownerFilterInput.setValue('');
-    let targetUriFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#jobTargetUriFilter' }));
-    await targetUriFilterInput.setValue("one");
-    jobRows = await jobsTable.getRows();
-    expect(jobRows.length).toEqual(1);
-    expect(await jobRows[0].getCellTextByColumnName()).toEqual(expectedJob1Row);
+      let idFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#jobIdFilter' }));
+      await idFilterInput.setValue("1");
+      let jobRows = await jobsTable.getRows();
+      expect(jobRows.length).toEqual(2);
+      expect(await jobRows[0].getCellTextByColumnName()).toEqual(expectedJob1Row);
+
+      idFilterInput.setValue('');
+      let typeIdFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#jobTypeIdFilter' }));
+      await typeIdFilterInput.setValue("1");
+      jobRows = await jobsTable.getRows();
+      expect(jobRows.length).toEqual(2);
+
+      typeIdFilterInput.setValue('');
+      let ownerFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#jobOwnerFilter' }));
+      await ownerFilterInput.setValue("1");
+      jobRows = await jobsTable.getRows();
+      expect(jobRows.length).toEqual(2);
+      expect(await jobRows[0].getCellTextByColumnName()).toEqual(expectedJob1Row);
+
+      ownerFilterInput.setValue('');
+      let targetUriFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#jobTargetUriFilter' }));
+      await targetUriFilterInput.setValue("one");
+      jobRows = await jobsTable.getRows();
+      expect(jobRows.length).toEqual(2);
+      expect(await jobRows[0].getCellTextByColumnName()).toEqual(expectedJob1Row);
+    });
+
+    describe('#sorting', () => {
+
+      it('should verify sort functionality on the table', async () => {
+        setServiceSpy();
+        const sort = await loader.getHarness(MatSortHarness);
+        let headers = await sort.getSortHeaders({ sortDirection: '' });
+        expect(headers.length).toBe(4);
+
+        await headers[0].click();
+        expect(await headers[0].isActive()).toBe(true);
+        expect(await headers[0].getSortDirection()).toBe('asc');
+
+        await headers[0].click();
+        expect(await headers[0].getSortDirection()).toBe('desc');
+
+      });
+
+      it('should sort table asc and desc by first header', async () => {
+        setServiceSpy();
+        const sort = await loader.getHarness(MatSortHarness);
+        let jobsTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
+        const firstHeader = (await sort.getSortHeaders())[0];
+        expect(await firstHeader.getSortDirection()).toBe('');
+
+        await firstHeader.click();
+        expect(await firstHeader.getSortDirection()).toBe('asc');
+        let jobRows = await jobsTable.getRows();
+        jobRows = await jobsTable.getRows();
+        expect(await jobRows[0].getCellTextByColumnName()).toEqual(expectedJob1Row);
+
+        await firstHeader.click();
+        expect(await firstHeader.getSortDirection()).toBe('desc');
+        jobRows = await jobsTable.getRows();
+        expect(await jobRows[jobRows.length - 1].getCellTextByColumnName()).toEqual(expectedJob1Row);
+      });
+    });
+
+    describe('#paging', () => {
+      it('should work properly on the table', async () => {
+        let eiServiceSpy = TestBed.inject(EIService) as jasmine.SpyObj<EIService>;
+        eiServiceSpy.getProducerIds.and.returnValue(of(['producer1', 'producer2']));
+        eiServiceSpy.getJobsForProducer.and.returnValue(of([job1, job2, job1]));
+
+        const paging = await loader.getHarness(MatPaginatorHarness);
+        await paging.setPageSize(5);
+
+        let jobsTable = await loader.getHarness(MatTableHarness.with({ selector: '#jobsTable' }));
+        let jobRows = await jobsTable.getRows();
+        expect(jobRows.length).toEqual(5);
+
+        await paging.goToNextPage();
+        jobRows = await jobsTable.getRows();
+        expect(jobRows.length).toEqual(1);
+        expect(await jobRows[jobRows.length - 1].getCellTextByColumnName()).toEqual(expectedJob1Row);
+      });
+    });
   });
 });
+
+function setServiceSpy() {
+  let eiServiceSpy = TestBed.inject(EIService) as jasmine.SpyObj<EIService>;
+  eiServiceSpy.getProducerIds.and.returnValue(of(['producer1', 'producer2']));
+  eiServiceSpy.getJobsForProducer.and.returnValue(of([job1, job2]));
+}
index 5208112..2991c28 100644 (file)
  * limitations under the License.
  * ========================LICENSE_END===================================
  */
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, ViewChild } from '@angular/core';
 import { FormControl, FormGroup } from '@angular/forms';
+import { MatPaginator } from '@angular/material/paginator';
 import { Sort } from '@angular/material/sort';
 import { MatTableDataSource } from '@angular/material/table';
-import { EIJob } from 'src/app/interfaces/ei.types';
-import { UiService } from 'src/app/services/ui/ui.service';
-import { EIJobDataSource } from '../ei-job.datasource';
+import { forkJoin } from 'rxjs';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { mergeMap, finalize } from 'rxjs/operators';
+import { EIJob } from '../../interfaces/ei.types';
+import { EIService } from '../../services/ei/ei.service';
+import { UiService } from '../../services/ui/ui.service';
 
 @Component({
   selector: 'nrcp-jobs-list',
@@ -31,13 +35,18 @@ import { EIJobDataSource } from '../ei-job.datasource';
   styleUrls: ['./jobs-list.component.scss']
 })
 export class JobsListComponent implements OnInit {
+
+  @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
+
   darkMode: boolean;
   jobsDataSource: MatTableDataSource<EIJob> = new MatTableDataSource<EIJob>();
-
   jobForm: FormGroup;
+  private loadingSubject = new BehaviorSubject<boolean>(false);
+  private jobsSubject = new BehaviorSubject<EIJob[]>([]);
+  public loading$ = this.loadingSubject.asObservable();
 
   constructor(
-    private eiJobsDataSource: EIJobDataSource,
+    private eiSvc: EIService,
     private ui: UiService
   ) {
     this.jobForm = new FormGroup({
@@ -46,11 +55,15 @@ export class JobsListComponent implements OnInit {
       owner: new FormControl(''),
       targetUri: new FormControl('')
     });
-
   }
 
   ngOnInit(): void {
-    this.refresh();
+    this.loadJobs();
+    this.jobsSubject.subscribe((data) => {
+      this.jobsDataSource = new MatTableDataSource<EIJob>(data);
+      //this.jobsDataSource.data = data;
+      this.jobsDataSource.paginator = this.paginator;
+    });
 
     this.jobForm.valueChanges.subscribe(value => {
       const filter = { ...value, id: value.id.trim().toLowerCase() } as string;
@@ -69,6 +82,12 @@ export class JobsListComponent implements OnInit {
     });
   }
 
+  ngOnDestroy() {
+    if (!this.jobsSubject) this.jobsSubject.unsubscribe();
+    if (!this.loadingSubject) this.loadingSubject.unsubscribe();
+    if (!this.ui.darkModeState) this.ui.darkModeState.unsubscribe();
+  }
+
   sortJobs(sort: Sort) {
     const data = this.jobsDataSource.data
     data.sort((a: EIJob, b: EIJob) => {
@@ -110,11 +129,21 @@ export class JobsListComponent implements OnInit {
     return '< No owner >';
   }
 
-  refresh() {
-    this.eiJobsDataSource.loadJobs();
+  public jobs(): EIJob[] {
+    return this.jobsSubject.value;
+  }
 
-    this.eiJobsDataSource.eiJobsSubject().subscribe((data) => {
-      this.jobsDataSource.data = data;
+  loadJobs() {
+    this.loadingSubject.next(true);
+    let jobs = [];
+    this.eiSvc.getProducerIds().pipe(
+      mergeMap(prodIds =>
+        forkJoin(prodIds.map(id => this.eiSvc.getJobsForProducer(id)))),
+      mergeMap(result => result),
+      finalize(() => this.loadingSubject.next(false))
+    ).subscribe(result => {
+      jobs = jobs.concat(result);
+      this.jobsSubject.next(jobs);
     });
   }
 
index 7e84ea4..8982c05 100644 (file)
@@ -18,8 +18,9 @@ limitations under the License.
 ========================LICENSE_END===================================
 -->
 <div class="table-container">
-  <mat-table id="producersTable" [dataSource]="producersDataSource" fixedLayout matSort
-    (matSortChange)="sortProducers($event)" class="ei-coordinator-table mat-elevation-z8">
+  <mat-table id="producersTable" [dataSource]="producersDataSource" fixedLayout
+    class="ei-coordinator-table mat-elevation-z8" matSort matSortDisableClear matSortDirection="asc"
+    (matSortChange)="sortProducers($event)">
     <ng-container matColumnDef="id">
       <mat-header-cell *matHeaderCellDef mat-sort-header>
         <div (click)="stopSort($event)">
index 9e4860d..ce54d18 100644 (file)
@@ -1,63 +1,38 @@
-/*-
- * ========================LICENSE_START=================================
- * O-RAN-SC
- * %%
- * Copyright (C) 2021 Nordix Foundation
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================LICENSE_END===================================
- */
-import { HarnessLoader } from '@angular/cdk/testing';
+
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
+import { HarnessLoader } from '@angular/cdk/testing';
+import { MatTableHarness } from '@angular/material/table/testing';
+import { MatSortHarness } from '@angular/material/sort/testing';
+import { ProducersListComponent } from "./producers-list.component";
+import { EIService } from 'src/app/services/ei/ei.service';
+import { EIProducer, OperationalState, ProducerRegistrationInfo, ProducerStatus } from 'src/app/interfaces/ei.types';
 import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
-import { MatInputHarness } from '@angular/material/input/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { MatIconModule } from '@angular/material/icon';
 import { MatTableModule } from '@angular/material/table';
-import { MatTableHarness } from '@angular/material/table/testing';
-import { of } from 'rxjs/observable/of';
-import { EIProducer } from '../../interfaces/ei.types';
-import { UiService } from '../../services/ui/ui.service';
-import { EIProducerDataSource } from '../ei-producer.datasource';
+import { MatInputHarness } from '@angular/material/input/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { UiService } from 'src/app/services/ui/ui.service';
+import { of } from 'rxjs';
+import { MatSortModule } from '@angular/material/sort';
 
-import { ProducersListComponent } from './producers-list.component';
+let component: ProducersListComponent;
+let fixture: ComponentFixture<ProducersListComponent>;
 
 describe('ProducersListComponent', () => {
-  let component: ProducersListComponent;
-  let fixture: ComponentFixture<ProducersListComponent>;
+
   let loader: HarnessLoader;
-  let producerDataSourceSpy: jasmine.SpyObj<EIProducerDataSource>;
-
-  const producer1 = {
-    ei_producer_id: 'producer1',
-    ei_producer_types: ['type1', 'type2'],
-    status: 'ENABLED'
-  } as EIProducer;
-  const producer2 = {
-    ei_producer_id: 'producer2',
-    ei_producer_types: ['type2', 'type3'],
-    status: 'DISABLED'
-  } as EIProducer;
 
   beforeEach(async(() => {
-    producerDataSourceSpy = jasmine.createSpyObj('EIProducerDataSource', ['loadProducers', 'eiProducers', 'eiProducersSubject']);
-
-    const producers: EIProducer[] = [producer1, producer2];
-    producerDataSourceSpy.eiProducersSubject.and.returnValue(of(producers));
+    const spy = jasmine.createSpyObj('EIService', ['getProducerIds', 'getProducer', 'getProducerStatus']);
 
     TestBed.configureTestingModule({
       imports: [
+        MatIconModule,
         MatTableModule,
+        MatSortModule,
+        BrowserAnimationsModule,
         ReactiveFormsModule
       ],
       schemas: [
@@ -67,93 +42,185 @@ describe('ProducersListComponent', () => {
         ProducersListComponent
       ],
       providers: [
-        { provide: EIProducerDataSource, useValue: producerDataSourceSpy },
+        { provide: EIService, useValue: spy },
         UiService,
-        FormBuilder,
       ]
     })
-      .compileComponents();
+      .compileComponents()
+      .then(() => {
+        fixture = TestBed.createComponent(ProducersListComponent);
+        component = fixture.componentInstance;
+        loader = TestbedHarnessEnvironment.loader(fixture);
+      });
   }));
 
-  const expectedProducer1Row = { id: 'producer1', types: 'type1,type2', status: 'ENABLED' };
-  beforeEach(() => {
-    fixture = TestBed.createComponent(ProducersListComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-    loader = TestbedHarnessEnvironment.loader(fixture);
-  });
-
   it('should create', () => {
     expect(component).toBeTruthy();
   });
 
-  it('should contain producers table with correct columns', async () => {
-    let producersTable = await loader.getHarness(MatTableHarness.with({ selector: '#producersTable' }));
-    let headerRow = (await producersTable.getHeaderRows())[0];
-    let headers = await headerRow.getCellTextByColumnName();
+  describe('#content', () => {
 
-    expect(headers).toEqual({ id: 'Producer ID', types: 'Producer types', status: 'Producer status' });
-  });
+    it('should loadProducers', () => {
+      const producer1 = {
+        ei_producer_id: 'producer1',
+        ei_producer_types: ['type1', 'type2'],
+        status: 'ENABLED'
+      } as EIProducer;
+      const producer2 = {
+        ei_producer_id: 'producer2',
+        ei_producer_types: ['type2', 'type3'],
+        status: 'DISABLED'
+      } as EIProducer;
 
+      setServiceSpy();
+      component.loadProducers();
+      const actualProducers: EIProducer[] = component.eiProducers();
+      expect(actualProducers).toEqual([producer1, producer2]);
+    });
 
-  it('should contain data after initialization', async () => {
-    component.ngOnInit();
-    const expectedProducerRows = [
-      expectedProducer1Row,
-      { id: 'producer2', types: 'type2,type3', status: 'DISABLED' }
-    ];
-    let producersTable = await loader.getHarness(MatTableHarness.with({ selector: '#producersTable' }));
-    let producerRows = await producersTable.getRows();
-    expect(producerRows.length).toEqual(2);
-    producerRows.forEach(row => {
-      row.getCellTextByColumnName().then(values => {
-        expect(expectedProducerRows).toContain(jasmine.objectContaining(values));
-      });
+    it('should contain producers table with correct columns', async () => {
+      setServiceSpy();
+
+      let producersTable = await loader.getHarness(MatTableHarness.with({ selector: '#producersTable' }));
+      let headerRow = (await producersTable.getHeaderRows())[0];
+      let headers = await headerRow.getCellTextByColumnName();
+      expect(headers).toEqual({ id: 'Producer ID', types: 'Producer types', status: 'Producer status' });
+    });
+
+    it('should set correct dark mode from UIService', () => {
+      setServiceSpy();
+      component.ngOnInit();
+      expect(component.darkMode).toBeTruthy();
+
+      const uiService: UiService = TestBed.inject(UiService);
+      uiService.darkModeState.next(false);
+      fixture.detectChanges();
+      expect(component.darkMode).toBeFalsy();
     });
   });
 
-  describe('should display default values for non required properties', () => {
-    it('producer defaults', async () => {
-      const producerMissingProperties = {
-        ei_producer_id: 'producer1'
-      } as EIProducer;
-      const producers: EIProducer[] = [producerMissingProperties];
-      producerDataSourceSpy.eiProducersSubject.and.returnValue(of(producers));
+  describe('#producersTable', () => {
+
+    const expectedProducer1Row = { id: 'producer1', types: 'type1,type2', status: 'ENABLED' };
+
+    it('should contain data after initialization', async () => {
+      setServiceSpy();
       component.ngOnInit();
+      const expectedProducerRows = [
+        expectedProducer1Row,
+        { id: 'producer2', types: 'type2,type3', status: 'DISABLED' }
+      ];
+      let producersTable = await loader.getHarness(MatTableHarness.with({ selector: '#producersTable' }));
+      let producerRows = await producersTable.getRows();
+      expect(producerRows.length).toEqual(2);
+      producerRows.forEach(row => {
+        row.getCellTextByColumnName().then(values => {
+          expect(expectedProducerRows).toContain(jasmine.objectContaining(values));
+        });
+      });
+    });
 
+    it('should display defaults values for non required properties', async () => {
+      let eiServiceSpy = TestBed.inject(EIService) as jasmine.SpyObj<EIService>;
+
+      eiServiceSpy.getProducerIds.and.returnValue(of(['producer1']));
+      eiServiceSpy.getProducer.and.returnValues(of({} as ProducerRegistrationInfo));
+      eiServiceSpy.getProducerStatus.and.returnValues(of({} as ProducerStatus));
+
+      component.ngOnInit();
       const expectedProducerRow = { id: 'producer1', types: '< No types >', status: '< No status >' };
       let producersTable = await loader.getHarness(MatTableHarness.with({ selector: '#producersTable' }));
       let producerRows = await producersTable.getRows();
       expect(await producerRows[0].getCellTextByColumnName()).toEqual(expectedProducerRow);
     });
-  });
 
-  it('filtering', async () => {
-    const expectedProducer1Row = { id: 'producer1', types: 'type1,type2', status: 'ENABLED' };
-    component.ngOnInit();
-    let producersTable = await loader.getHarness(MatTableHarness.with({ selector: '#producersTable' }));
-
-    let idFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#producerIdFilter' }));
-    await idFilterInput.setValue("1");
-    let producerRows = await producersTable.getRows();
-    expect(producerRows.length).toEqual(1);
-    expect(await producerRows[0].getCellTextByColumnName()).toEqual(expectedProducer1Row);
-
-    idFilterInput.setValue('');
-    let typesFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#producerTypesFilter' }));
-    await typesFilterInput.setValue("1");
-    producerRows = await producersTable.getRows();
-    expect(producerRows.length).toEqual(1);
-    expect(await producerRows[0].getCellTextByColumnName()).toEqual(expectedProducer1Row);
-    await typesFilterInput.setValue("2");
-    producerRows = await producersTable.getRows();
-    expect(producerRows.length).toEqual(2);
-
-    typesFilterInput.setValue('');
-    let statusFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#producerStatusFilter' }));
-    await statusFilterInput.setValue("enabled");
-    producerRows = await producersTable.getRows();
-    expect(producerRows.length).toEqual(1);
-    expect(await producerRows[0].getCellTextByColumnName()).toEqual(expectedProducer1Row);
+    it('filtering', async () => {
+      setServiceSpy();
+      component.ngOnInit();
+      let producersTable = await loader.getHarness(MatTableHarness.with({ selector: '#producersTable' }));
+
+      let idFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#producerIdFilter' }));
+      await idFilterInput.setValue("1");
+      let producerRows = await producersTable.getRows();
+      expect(producerRows.length).toEqual(1);
+      expect(await producerRows[0].getCellTextByColumnName()).toEqual(expectedProducer1Row);
+
+      idFilterInput.setValue('');
+      let typesFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#producerTypesFilter' }));
+      await typesFilterInput.setValue("1");
+      producerRows = await producersTable.getRows();
+      expect(producerRows.length).toEqual(1);
+      expect(await producerRows[0].getCellTextByColumnName()).toEqual(expectedProducer1Row);
+      await typesFilterInput.setValue("2");
+      producerRows = await producersTable.getRows();
+      expect(producerRows.length).toEqual(2);
+
+      typesFilterInput.setValue('');
+      let statusFilterInput = await loader.getHarness(MatInputHarness.with({ selector: '#producerStatusFilter' }));
+      await statusFilterInput.setValue("enabled");
+      producerRows = await producersTable.getRows();
+      expect(producerRows.length).toEqual(1);
+      expect(await producerRows[0].getCellTextByColumnName()).toEqual(expectedProducer1Row);
+    });
+
+    describe('#sorting', () => {
+
+      it('should verify sort functionality on the table', async () => {
+        setServiceSpy();
+        const sort = await loader.getHarness(MatSortHarness);
+        let headers = await sort.getSortHeaders({ sortDirection: '' });
+        expect(headers.length).toBe(3);
+
+        await headers[0].click();
+        expect(await headers[0].isActive()).toBe(true);
+        expect(await headers[0].getSortDirection()).toBe('asc');
+
+        await headers[0].click();
+        expect(await headers[0].getSortDirection()).toBe('desc');
+
+      });
+
+      it('should sort table asc and desc by first header', async () => {
+        setServiceSpy();
+        const sort = await loader.getHarness(MatSortHarness);
+        let producersTable = await loader.getHarness(MatTableHarness.with({ selector: '#producersTable' }));
+        const firstHeader = (await sort.getSortHeaders())[0];
+        expect(await firstHeader.getSortDirection()).toBe('');
+
+        await firstHeader.click();
+        expect(await firstHeader.getSortDirection()).toBe('asc');
+        let prodRows = await producersTable.getRows();
+        prodRows = await producersTable.getRows();
+        expect(await prodRows[0].getCellTextByColumnName()).toEqual(expectedProducer1Row);
+
+        await firstHeader.click();
+        expect(await firstHeader.getSortDirection()).toBe('desc');
+        prodRows = await producersTable.getRows();
+        expect(await prodRows[prodRows.length - 1].getCellTextByColumnName()).toEqual(expectedProducer1Row);
+      });
+    });
   });
 });
+
+function setServiceSpy() {
+  let producerRegInfo1 = {
+    supported_ei_types: ['type1', 'type2']
+  } as ProducerRegistrationInfo;
+  let producerRegInfo2 = {
+    supported_ei_types: ['type2', 'type3']
+  } as ProducerRegistrationInfo;
+  let producerStatus1 = {
+    operational_state: OperationalState.ENABLED
+  } as ProducerStatus;
+  let producerStatus2 = {
+    operational_state: OperationalState.DISABLED
+  } as ProducerStatus;
+
+  let eiServiceSpy = TestBed.inject(EIService) as jasmine.SpyObj<EIService>;
+
+  eiServiceSpy.getProducerIds.and.returnValue(of(['producer1', 'producer2']));
+  eiServiceSpy.getProducer.and.returnValues(of(producerRegInfo1), of(producerRegInfo2));
+  eiServiceSpy.getProducerStatus.and.returnValues(of(producerStatus1), of(producerStatus2));
+}
+
+
index df1d0be..68f4f3f 100644 (file)
  * limitations under the License.
  * ========================LICENSE_END===================================
  */
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, ViewChild } from '@angular/core';
 import { FormControl, FormGroup } from '@angular/forms';
-import { Sort } from '@angular/material/sort';
+import { MatSort, Sort } from '@angular/material/sort';
 import { MatTableDataSource } from '@angular/material/table';
+import { forkJoin, of } from 'rxjs';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { mergeMap, finalize, catchError } from 'rxjs/operators';
+import { EIService } from 'src/app/services/ei/ei.service';
 import { EIProducer } from '../../interfaces/ei.types';
 import { UiService } from '../../services/ui/ui.service';
-import { EIProducerDataSource } from '../ei-producer.datasource';
 
 @Component({
   selector: 'nrcp-producers-list',
@@ -31,12 +34,19 @@ import { EIProducerDataSource } from '../ei-producer.datasource';
   styleUrls: ['./producers-list.component.scss']
 })
 export class ProducersListComponent implements OnInit {
-  darkMode: boolean;
+
+  @ViewChild(MatSort) sort: MatSort;
+
   producersDataSource: MatTableDataSource<EIProducer> = new MatTableDataSource<EIProducer>();
   producerForm: FormGroup;
+  darkMode: boolean;
+
+  private loadingSubject = new BehaviorSubject<boolean>(false);
+  private producerSubject = new BehaviorSubject<EIProducer[]>([]);
+  public loading$ = this.loadingSubject.asObservable();
 
   constructor(
-    private eiProducersDataSource: EIProducerDataSource,
+    private eiSvc: EIService,
     private ui: UiService) {
 
     this.producerForm = new FormGroup({
@@ -47,7 +57,11 @@ export class ProducersListComponent implements OnInit {
   }
 
   ngOnInit(): void {
-    this.refresh();
+    this.loadProducers();
+    this.producerSubject.subscribe((data) => {
+      this.producersDataSource = new MatTableDataSource<EIProducer>(data);
+      //this.producersDataSource.data = data;
+    });
 
     this.producerForm.valueChanges.subscribe(value => {
       const filter = { ...value, ei_producer_id: value.ei_producer_id.trim().toLowerCase() } as string;
@@ -65,6 +79,12 @@ export class ProducersListComponent implements OnInit {
     });
   }
 
+  ngOnDestroy() {
+    if (!this.producerSubject) this.producerSubject.unsubscribe();
+    if (!this.loadingSubject) this.loadingSubject.unsubscribe();
+    if (!this.ui.darkModeState) this.ui.darkModeState.unsubscribe();
+  }
+
   isDataIncluding(data: string, filter: string): boolean {
     return !filter || data.toLowerCase().includes(filter);
   }
@@ -105,10 +125,40 @@ export class ProducersListComponent implements OnInit {
     return '< No status >';
   }
 
-  refresh() {
-    this.eiProducersDataSource.loadProducers();
-    this.eiProducersDataSource.eiProducersSubject().subscribe((data) => {
-      this.producersDataSource.data = data;
+  public eiProducers(): EIProducer[] {
+    return this.producerSubject.value;
+  }
+
+  loadProducers() {
+    this.loadingSubject.next(true);
+    let producers = [];
+
+    this.eiSvc.getProducerIds().pipe(
+      mergeMap(prodIds =>
+        forkJoin(prodIds.map(id => {
+          return forkJoin([
+            of(id),
+            this.eiSvc.getProducer(id),
+            this.eiSvc.getProducerStatus(id)
+          ])
+        })
+        )),
+      finalize(() => this.loadingSubject.next(false)),
+    ).subscribe(result => {
+      producers = result.map(producer => {
+        let eiProducer = <EIProducer>{};
+        eiProducer.ei_producer_id = producer[0];
+        if (producer[1].supported_ei_types) {
+          eiProducer.ei_producer_types = producer[1].supported_ei_types;
+        }
+        if (producer[2].operational_state) {
+          eiProducer.status = producer[2].operational_state.toString();
+        }
+        return eiProducer;
+      });
+      this.producerSubject.next(producers);
+    }, err => {
+      console.error("Subscribe function error:" + err);
     });
   }
 }