Skip to content

Commit 117514b

Browse files
committed
[CST-18016] Create a new component to list the qa sources
1 parent 20df163 commit 117514b

8 files changed

Lines changed: 305 additions & 73 deletions

File tree

src/app/notifications/qa/source/quality-assurance-source.component.html

Lines changed: 7 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,65 +5,12 @@ <h1 class="border-bottom pb-2">{{'quality-assurance.title'| translate}}</h1>
55
<ds-alert [type]="'alert-info'" [content]="'quality-assurance.source.description'"></ds-alert>
66
</div>
77
</div>
8-
<div class="row">
9-
<div class="col-12">
10-
<h2 class="h4 border-bottom pb-2">{{'quality-assurance.source'| translate}}</h2>
11-
12-
@if ((isSourceLoading() | async)) {
13-
<ds-loading class="container" message="{{'quality-assurance.loading' | translate}}"></ds-loading>
14-
}
15-
@if ((isSourceLoading() | async) !== true) {
16-
<ds-pagination
17-
[paginationOptions]="paginationConfig"
18-
[collectionSize]="(totalElements$ | async)"
19-
[hideGear]="false"
20-
[hideSortOptions]="true"
21-
(paginationChange)="getQualityAssuranceSource()">
22-
@if ((isSourceProcessing() | async)) {
23-
<ds-loading class="container" message="'quality-assurance.loading' | translate"></ds-loading>
24-
}
25-
@if ((isSourceProcessing() | async) !== true) {
26-
@if ((sources$ | async)?.length === 0) {
27-
<div class="alert alert-info w-100 mb-2 mt-2" role="alert">
28-
{{'quality-assurance.noSource' | translate}}
29-
</div>
30-
}
31-
@if ((sources$ | async)?.length !== 0) {
32-
<div class="table-responsive mt-2">
33-
<table id="epeople" class="table table-striped table-hover table-bordered">
34-
<thead>
35-
<tr>
36-
<th scope="col">{{'quality-assurance.table.source' | translate}}</th>
37-
<th scope="col">{{'quality-assurance.table.last-event' | translate}}</th>
38-
<th scope="col">{{'quality-assurance.table.actions' | translate}}</th>
39-
</tr>
40-
</thead>
41-
<tbody>
42-
@for (sourceElement of (sources$ | async); track sourceElement; let i = $index) {
43-
<tr>
44-
<td>{{sourceElement.id}}</td>
45-
<td>{{sourceElement.lastEvent | date: 'dd/MM/yyyy hh:mm' }}</td>
46-
<td>
47-
<div class="btn-group edit-field">
48-
<button
49-
class="btn btn-outline-primary btn-sm"
50-
title="{{'quality-assurance.source-list.button.detail' | translate : { param: sourceElement.id } }}"
51-
[routerLink]="[sourceElement.id]">
52-
<span class="badge bg-info">{{sourceElement.totalEvents}}</span>
53-
<i class="fas fa-info fa-fw"></i>
54-
</button>
55-
</div>
56-
</td>
57-
</tr>
58-
}
59-
</tbody>
60-
</table>
61-
</div>
62-
}
63-
}
64-
</ds-pagination>
65-
}
66-
</div>
67-
</div>
8+
<ds-source-list [loading]="(isSourceLoading() | async) === true || (isSourceProcessing() | async) === true"
9+
[paginationConfig]="paginationConfig"
10+
[showLastEvent]="true"
11+
[sources]="sources$ | async"
12+
[totalElements]="totalElements$ | async"
13+
(paginationChange)="getQualityAssuranceSource()"
14+
(sourceSelected)="onSelect($event)"></ds-source-list>
6815
</div>
6916

src/app/notifications/qa/source/quality-assurance-source.component.spec.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,18 @@ import { of as observableOf } from 'rxjs';
1616

1717
import { PaginationService } from '../../../core/pagination/pagination.service';
1818
import { AlertComponent } from '../../../shared/alert/alert.component';
19-
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
2019
import {
2120
getMockNotificationsStateService,
2221
qualityAssuranceSourceObjectMoreAbstract,
2322
qualityAssuranceSourceObjectMorePid,
2423
} from '../../../shared/mocks/notifications.mock';
25-
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
2624
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
2725
import { createTestComponent } from '../../../shared/testing/utils.test';
2826
import { NotificationsStateService } from '../../notifications-state.service';
27+
import {
28+
SourceListComponent,
29+
SourceObject,
30+
} from '../../shared/source-list.component';
2931
import { QualityAssuranceSourceComponent } from './quality-assurance-source.component';
3032

3133
describe('QualityAssuranceSourceComponent test suite', () => {
@@ -61,8 +63,7 @@ describe('QualityAssuranceSourceComponent test suite', () => {
6163
remove: {
6264
imports: [
6365
AlertComponent,
64-
ThemedLoadingComponent,
65-
PaginationComponent,
66+
SourceListComponent,
6667
],
6768
},
6869
})
@@ -119,12 +120,19 @@ describe('QualityAssuranceSourceComponent test suite', () => {
119120
it(('Should init component properly'), () => {
120121
comp.ngOnInit();
121122
fixture.detectChanges();
123+
const expected: SourceObject[] = [
124+
{ id: qualityAssuranceSourceObjectMorePid.id,
125+
lastEvent: qualityAssuranceSourceObjectMorePid.lastEvent,
126+
total: qualityAssuranceSourceObjectMorePid.totalEvents,
127+
},
128+
{ id: qualityAssuranceSourceObjectMoreAbstract.id,
129+
lastEvent: qualityAssuranceSourceObjectMoreAbstract.lastEvent,
130+
total: qualityAssuranceSourceObjectMoreAbstract.totalEvents,
131+
},
132+
];
122133

123134
expect(comp.sources$).toBeObservable(cold('(a|)', {
124-
a: [
125-
qualityAssuranceSourceObjectMorePid,
126-
qualityAssuranceSourceObjectMoreAbstract,
127-
],
135+
a: expected,
128136
}));
129137
expect(comp.totalElements$).toBeObservable(cold('(a|)', {
130138
a: 2,

src/app/notifications/qa/source/quality-assurance-source.component.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@ import {
88
OnDestroy,
99
OnInit,
1010
} from '@angular/core';
11-
import { RouterLink } from '@angular/router';
11+
import {
12+
ActivatedRoute,
13+
Router,
14+
RouterLink,
15+
} from '@angular/router';
1216
import { TranslateModule } from '@ngx-translate/core';
1317
import {
1418
Observable,
1519
Subscription,
1620
} from 'rxjs';
1721
import {
1822
distinctUntilChanged,
23+
map,
1924
take,
2025
} from 'rxjs/operators';
2126

@@ -29,6 +34,10 @@ import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.c
2934
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
3035
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
3136
import { NotificationsStateService } from '../../notifications-state.service';
37+
import {
38+
SourceListComponent,
39+
SourceObject,
40+
} from '../../shared/source-list.component';
3241

3342
/**
3443
* Component to display the Quality Assurance source list.
@@ -38,7 +47,7 @@ import { NotificationsStateService } from '../../notifications-state.service';
3847
templateUrl: './quality-assurance-source.component.html',
3948
styleUrls: ['./quality-assurance-source.component.scss'],
4049
standalone: true,
41-
imports: [AlertComponent, ThemedLoadingComponent, PaginationComponent, RouterLink, AsyncPipe, TranslateModule, DatePipe],
50+
imports: [AlertComponent, ThemedLoadingComponent, PaginationComponent, RouterLink, AsyncPipe, TranslateModule, DatePipe, SourceListComponent],
4251
})
4352
export class QualityAssuranceSourceComponent implements OnDestroy, OnInit, AfterViewInit {
4453

@@ -59,7 +68,7 @@ export class QualityAssuranceSourceComponent implements OnDestroy, OnInit, After
5968
/**
6069
* The Quality Assurance source list.
6170
*/
62-
public sources$: Observable<QualityAssuranceSourceObject[]>;
71+
public sources$: Observable<SourceObject[]>;
6372
/**
6473
* The total number of Quality Assurance sources.
6574
*/
@@ -74,18 +83,30 @@ export class QualityAssuranceSourceComponent implements OnDestroy, OnInit, After
7483
* Initialize the component variables.
7584
* @param {PaginationService} paginationService
7685
* @param {NotificationsStateService} notificationsStateService
86+
* @param {Router} router
87+
* @param {ActivatedRoute} route
7788
*/
7889
constructor(
7990
private paginationService: PaginationService,
8091
private notificationsStateService: NotificationsStateService,
92+
private router: Router,
93+
private route: ActivatedRoute,
8194
) {
8295
}
8396

8497
/**
8598
* Component initialization.
8699
*/
87100
ngOnInit(): void {
88-
this.sources$ = this.notificationsStateService.getQualityAssuranceSource();
101+
this.sources$ = this.notificationsStateService.getQualityAssuranceSource().pipe(
102+
map((sources: QualityAssuranceSourceObject[])=> {
103+
return sources.map((source: QualityAssuranceSourceObject) => ({
104+
id: source.id,
105+
lastEvent: source.lastEvent,
106+
total: source.totalEvents,
107+
}));
108+
}),
109+
);
89110
this.totalElements$ = this.notificationsStateService.getQualityAssuranceSourceTotals();
90111
}
91112

@@ -102,6 +123,14 @@ export class QualityAssuranceSourceComponent implements OnDestroy, OnInit, After
102123
);
103124
}
104125

126+
/**
127+
* Navigate to the specified source
128+
* @param sourceId
129+
*/
130+
onSelect(sourceId: string) {
131+
this.router.navigate([sourceId], { relativeTo: this.route });
132+
}
133+
105134
/**
106135
* Returns the information about the loading status of the Quality Assurance source (if it's running or not).
107136
*
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<div class="col-12">
2+
<h2 class="h4 border-bottom pb-2">{{'quality-assurance.source'| translate}}</h2>
3+
4+
@if (loading()) {
5+
<ds-loading class="container" message="{{'quality-assurance.loading' | translate}}"></ds-loading>
6+
} @else {
7+
<ds-pagination
8+
[paginationOptions]="paginationConfig()"
9+
[collectionSize]="totalElements()"
10+
[hideGear]="false"
11+
[hideSortOptions]="true"
12+
(paginationChange)="paginationChange.emit($event)">
13+
@if (sources()?.length === 0) {
14+
<div class="alert alert-info w-100 mb-2 mt-2" role="alert">
15+
{{'quality-assurance.noSource' | translate}}
16+
</div>
17+
}
18+
@if (sources()?.length !== 0) {
19+
<div class="table-responsive mt-2">
20+
<table id="epeople" class="table table-striped table-hover table-bordered">
21+
<thead>
22+
<tr>
23+
<th scope="col">{{'quality-assurance.table.source' | translate}}</th>
24+
@if (showLastEvent()) {
25+
<th scope="col">{{'quality-assurance.table.last-event' | translate}}</th>
26+
}
27+
<th scope="col">{{'quality-assurance.table.actions' | translate}}</th>
28+
</tr>
29+
</thead>
30+
<tbody>
31+
@for (sourceElement of sources(); track sourceElement; let i = $index) {
32+
<tr>
33+
<td>{{sourceElement.id}}</td>
34+
@if (showLastEvent()) {
35+
<td>{{sourceElement.lastEvent | date: 'dd/MM/yyyy hh:mm' }}</td>
36+
}
37+
<td>
38+
<div class="btn-group edit-field">
39+
<button
40+
class="btn btn-outline-primary btn-sm"
41+
title="{{'quality-assurance.source-list.button.detail' | translate : { param: sourceElement.id } }}"
42+
(click)="sourceSelected.emit(sourceElement.id)">
43+
<span class="badge bg-info">{{sourceElement.total}}</span>
44+
<i class="fas fa-info fa-fw"></i>
45+
</button>
46+
</div>
47+
</td>
48+
</tr>
49+
}
50+
</tbody>
51+
</table>
52+
</div>
53+
}
54+
55+
</ds-pagination>
56+
}
57+
58+
</div>
59+

src/app/notifications/shared/source-list.component.scss

Whitespace-only changes.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import {
2+
AsyncPipe,
3+
DatePipe,
4+
} from '@angular/common';
5+
import {
6+
ComponentFixture,
7+
TestBed,
8+
} from '@angular/core/testing';
9+
import { provideRouter } from '@angular/router';
10+
import { TranslateModule } from '@ngx-translate/core';
11+
import { MockComponent } from 'ng-mocks';
12+
13+
import { AlertComponent } from '../../shared/alert/alert.component';
14+
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
15+
import { PaginationComponent } from '../../shared/pagination/pagination.component';
16+
import {
17+
SourceListComponent,
18+
SourceObject,
19+
} from './source-list.component';
20+
21+
describe('SourceListComponent', () => {
22+
let component: SourceListComponent;
23+
let fixture: ComponentFixture<SourceListComponent>;
24+
const paginationConfig = {
25+
currentPage: 1,
26+
pageSize: 10,
27+
};
28+
const sources: SourceObject[] = [
29+
{ id: 'source1', lastEvent: '2025-03-12T12:00:00', total: 5 },
30+
{ id: 'source1', lastEvent: '2025-03-13T12:00:00', total: 10 },
31+
];
32+
33+
const sourcesWithoutEvent: SourceObject[] = [
34+
{ id: 'source1', total: 5 },
35+
{ id: 'source1', total: 10 },
36+
];
37+
38+
beforeEach(async () => {
39+
await TestBed.configureTestingModule({
40+
declarations: [SourceListComponent],
41+
imports: [
42+
TranslateModule.forRoot(),
43+
AlertComponent,
44+
AsyncPipe,
45+
DatePipe,
46+
MockComponent(PaginationComponent),
47+
MockComponent(ThemedLoadingComponent),
48+
],
49+
providers: [
50+
provideRouter([]),
51+
],
52+
}).compileComponents();
53+
});
54+
55+
beforeEach(() => {
56+
fixture = TestBed.createComponent(SourceListComponent);
57+
component = fixture.componentInstance;
58+
fixture.detectChanges();
59+
});
60+
61+
it('should create', () => {
62+
expect(component).toBeTruthy();
63+
});
64+
65+
it('should display loading message when loading is true', () => {
66+
fixture.componentRef.setInput('loading', true);
67+
fixture.detectChanges();
68+
expect(fixture.nativeElement.querySelector('ds-loading')).toBeTruthy();
69+
});
70+
71+
it('should display sources when loading is false and sources are available', () => {
72+
fixture.componentRef.setInput('loading', false);
73+
fixture.componentRef.setInput('showLastEvent', true);
74+
fixture.componentRef.setInput('sources', sources);
75+
fixture.detectChanges();
76+
expect(fixture.nativeElement.querySelector('table')).toBeTruthy();
77+
expect(fixture.nativeElement.querySelector('thead tr').children.length).toBe(3);
78+
expect(fixture.nativeElement.querySelector('tbody').children.length).toBe(2);
79+
expect(fixture.nativeElement.querySelector('tbody tr td').textContent).toContain('source1');
80+
});
81+
82+
it('should not display last event column', () => {
83+
fixture.componentRef.setInput('loading', false);
84+
fixture.componentRef.setInput('showLastEvent', false);
85+
fixture.componentRef.setInput('sources', sourcesWithoutEvent);
86+
fixture.detectChanges();
87+
expect(fixture.nativeElement.querySelector('table')).toBeTruthy();
88+
expect(fixture.nativeElement.querySelector('thead tr').children.length).toBe(2);
89+
expect(fixture.nativeElement.querySelector('tbody').children.length).toBe(2);
90+
expect(fixture.nativeElement.querySelector('table tbody tr td').textContent).toContain('source1');
91+
});
92+
93+
it('should emit sourceSelected event when a source is clicked', () => {
94+
spyOn(component.sourceSelected, 'emit');
95+
fixture.componentRef.setInput('loading', false);
96+
fixture.componentRef.setInput('paginationConfig', paginationConfig);
97+
fixture.componentRef.setInput('sources', sources);
98+
fixture.detectChanges();
99+
const button = fixture.nativeElement.querySelector('.btn-outline-primary');
100+
button.click();
101+
expect(component.sourceSelected.emit).toHaveBeenCalledWith('source1');
102+
});
103+
104+
it('should emit paginationChange event when pagination changes', () => {
105+
spyOn(component.paginationChange, 'emit');
106+
fixture.componentRef.setInput('loading', false);
107+
fixture.componentRef.setInput('paginationConfig', paginationConfig);
108+
109+
fixture.detectChanges();
110+
const paginationComponent = fixture.nativeElement.querySelector('ds-pagination');
111+
paginationComponent.dispatchEvent(new Event('paginationChange'));
112+
expect(component.paginationChange.emit).toHaveBeenCalled();
113+
});
114+
});

0 commit comments

Comments
 (0)