diff --git a/src/app/notifications/qa/source/quality-assurance-source.component.html b/src/app/notifications/qa/source/quality-assurance-source.component.html index 6155b7901f..54906a0adb 100644 --- a/src/app/notifications/qa/source/quality-assurance-source.component.html +++ b/src/app/notifications/qa/source/quality-assurance-source.component.html @@ -5,65 +5,12 @@ -
-
-

{{'quality-assurance.source'| translate}}

- - @if ((isSourceLoading() | async)) { - - } - @if ((isSourceLoading() | async) !== true) { - - @if ((isSourceProcessing() | async)) { - - } - @if ((isSourceProcessing() | async) !== true) { - @if ((sources$ | async)?.length === 0) { - - } - @if ((sources$ | async)?.length !== 0) { -
- - - - - - - - - - @for (sourceElement of (sources$ | async); track sourceElement; let i = $index) { - - - - - - } - -
{{'quality-assurance.table.source' | translate}}{{'quality-assurance.table.last-event' | translate}}{{'quality-assurance.table.actions' | translate}}
{{sourceElement.id}}{{sourceElement.lastEvent | date: 'dd/MM/yyyy hh:mm' }} -
- -
-
-
- } - } -
- } -
-
+ diff --git a/src/app/notifications/qa/source/quality-assurance-source.component.spec.ts b/src/app/notifications/qa/source/quality-assurance-source.component.spec.ts index e9c0133725..226630c0f1 100644 --- a/src/app/notifications/qa/source/quality-assurance-source.component.spec.ts +++ b/src/app/notifications/qa/source/quality-assurance-source.component.spec.ts @@ -16,16 +16,18 @@ import { of as observableOf } from 'rxjs'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { AlertComponent } from '../../../shared/alert/alert.component'; -import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component'; import { getMockNotificationsStateService, qualityAssuranceSourceObjectMoreAbstract, qualityAssuranceSourceObjectMorePid, } from '../../../shared/mocks/notifications.mock'; -import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; import { createTestComponent } from '../../../shared/testing/utils.test'; import { NotificationsStateService } from '../../notifications-state.service'; +import { + SourceListComponent, + SourceObject, +} from '../../shared/source-list.component'; import { QualityAssuranceSourceComponent } from './quality-assurance-source.component'; describe('QualityAssuranceSourceComponent test suite', () => { @@ -61,8 +63,7 @@ describe('QualityAssuranceSourceComponent test suite', () => { remove: { imports: [ AlertComponent, - ThemedLoadingComponent, - PaginationComponent, + SourceListComponent, ], }, }) @@ -119,12 +120,19 @@ describe('QualityAssuranceSourceComponent test suite', () => { it(('Should init component properly'), () => { comp.ngOnInit(); fixture.detectChanges(); + const expected: SourceObject[] = [ + { id: qualityAssuranceSourceObjectMorePid.id, + lastEvent: qualityAssuranceSourceObjectMorePid.lastEvent, + total: qualityAssuranceSourceObjectMorePid.totalEvents, + }, + { id: qualityAssuranceSourceObjectMoreAbstract.id, + lastEvent: qualityAssuranceSourceObjectMoreAbstract.lastEvent, + total: qualityAssuranceSourceObjectMoreAbstract.totalEvents, + }, + ]; expect(comp.sources$).toBeObservable(cold('(a|)', { - a: [ - qualityAssuranceSourceObjectMorePid, - qualityAssuranceSourceObjectMoreAbstract, - ], + a: expected, })); expect(comp.totalElements$).toBeObservable(cold('(a|)', { a: 2, diff --git a/src/app/notifications/qa/source/quality-assurance-source.component.ts b/src/app/notifications/qa/source/quality-assurance-source.component.ts index 575394fd2f..0a5cf37c8d 100644 --- a/src/app/notifications/qa/source/quality-assurance-source.component.ts +++ b/src/app/notifications/qa/source/quality-assurance-source.component.ts @@ -8,7 +8,11 @@ import { OnDestroy, OnInit, } from '@angular/core'; -import { RouterLink } from '@angular/router'; +import { + ActivatedRoute, + Router, + RouterLink, +} from '@angular/router'; import { TranslateModule } from '@ngx-translate/core'; import { Observable, @@ -16,6 +20,7 @@ import { } from 'rxjs'; import { distinctUntilChanged, + map, take, } from 'rxjs/operators'; @@ -29,6 +34,10 @@ import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.c import { PaginationComponent } from '../../../shared/pagination/pagination.component'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { NotificationsStateService } from '../../notifications-state.service'; +import { + SourceListComponent, + SourceObject, +} from '../../shared/source-list.component'; /** * Component to display the Quality Assurance source list. @@ -38,7 +47,7 @@ import { NotificationsStateService } from '../../notifications-state.service'; templateUrl: './quality-assurance-source.component.html', styleUrls: ['./quality-assurance-source.component.scss'], standalone: true, - imports: [AlertComponent, ThemedLoadingComponent, PaginationComponent, RouterLink, AsyncPipe, TranslateModule, DatePipe], + imports: [AlertComponent, ThemedLoadingComponent, PaginationComponent, RouterLink, AsyncPipe, TranslateModule, DatePipe, SourceListComponent], }) export class QualityAssuranceSourceComponent implements OnDestroy, OnInit, AfterViewInit { @@ -59,7 +68,7 @@ export class QualityAssuranceSourceComponent implements OnDestroy, OnInit, After /** * The Quality Assurance source list. */ - public sources$: Observable; + public sources$: Observable; /** * The total number of Quality Assurance sources. */ @@ -74,10 +83,14 @@ export class QualityAssuranceSourceComponent implements OnDestroy, OnInit, After * Initialize the component variables. * @param {PaginationService} paginationService * @param {NotificationsStateService} notificationsStateService + * @param {Router} router + * @param {ActivatedRoute} route */ constructor( private paginationService: PaginationService, private notificationsStateService: NotificationsStateService, + private router: Router, + private route: ActivatedRoute, ) { } @@ -85,7 +98,15 @@ export class QualityAssuranceSourceComponent implements OnDestroy, OnInit, After * Component initialization. */ ngOnInit(): void { - this.sources$ = this.notificationsStateService.getQualityAssuranceSource(); + this.sources$ = this.notificationsStateService.getQualityAssuranceSource().pipe( + map((sources: QualityAssuranceSourceObject[])=> { + return sources.map((source: QualityAssuranceSourceObject) => ({ + id: source.id, + lastEvent: source.lastEvent, + total: source.totalEvents, + })); + }), + ); this.totalElements$ = this.notificationsStateService.getQualityAssuranceSourceTotals(); } @@ -102,6 +123,14 @@ export class QualityAssuranceSourceComponent implements OnDestroy, OnInit, After ); } + /** + * Navigate to the specified source + * @param sourceId + */ + onSelect(sourceId: string) { + this.router.navigate([sourceId], { relativeTo: this.route }); + } + /** * Returns the information about the loading status of the Quality Assurance source (if it's running or not). * diff --git a/src/app/notifications/shared/source-list.component.html b/src/app/notifications/shared/source-list.component.html new file mode 100644 index 0000000000..5759ec5f1e --- /dev/null +++ b/src/app/notifications/shared/source-list.component.html @@ -0,0 +1,59 @@ +
+

{{'quality-assurance.source'| translate}}

+ + @if (loading()) { + + } @else { + + @if (sources()?.length === 0) { + + } + @if (sources()?.length !== 0) { +
+ + + + + @if (showLastEvent()) { + + } + + + + + @for (sourceElement of sources(); track sourceElement; let i = $index) { + + + @if (showLastEvent()) { + + } + + + } + +
{{'quality-assurance.table.source' | translate}}{{'quality-assurance.table.last-event' | translate}}{{'quality-assurance.table.actions' | translate}}
{{sourceElement.id}}{{sourceElement.lastEvent | date: 'dd/MM/yyyy hh:mm' }} +
+ +
+
+
+ } + +
+ } + +
+ diff --git a/src/app/notifications/shared/source-list.component.scss b/src/app/notifications/shared/source-list.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/notifications/shared/source-list.component.spec.ts b/src/app/notifications/shared/source-list.component.spec.ts new file mode 100644 index 0000000000..f3a8f68c09 --- /dev/null +++ b/src/app/notifications/shared/source-list.component.spec.ts @@ -0,0 +1,114 @@ +import { + AsyncPipe, + DatePipe, +} from '@angular/common'; +import { + ComponentFixture, + TestBed, +} from '@angular/core/testing'; +import { provideRouter } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; +import { MockComponent } from 'ng-mocks'; + +import { AlertComponent } from '../../shared/alert/alert.component'; +import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; +import { PaginationComponent } from '../../shared/pagination/pagination.component'; +import { + SourceListComponent, + SourceObject, +} from './source-list.component'; + +describe('SourceListComponent', () => { + let component: SourceListComponent; + let fixture: ComponentFixture; + const paginationConfig = { + currentPage: 1, + pageSize: 10, + }; + const sources: SourceObject[] = [ + { id: 'source1', lastEvent: '2025-03-12T12:00:00', total: 5 }, + { id: 'source1', lastEvent: '2025-03-13T12:00:00', total: 10 }, + ]; + + const sourcesWithoutEvent: SourceObject[] = [ + { id: 'source1', total: 5 }, + { id: 'source1', total: 10 }, + ]; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [SourceListComponent], + imports: [ + TranslateModule.forRoot(), + AlertComponent, + AsyncPipe, + DatePipe, + MockComponent(PaginationComponent), + MockComponent(ThemedLoadingComponent), + ], + providers: [ + provideRouter([]), + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SourceListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should display loading message when loading is true', () => { + fixture.componentRef.setInput('loading', true); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('ds-loading')).toBeTruthy(); + }); + + it('should display sources when loading is false and sources are available', () => { + fixture.componentRef.setInput('loading', false); + fixture.componentRef.setInput('showLastEvent', true); + fixture.componentRef.setInput('sources', sources); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('table')).toBeTruthy(); + expect(fixture.nativeElement.querySelector('thead tr').children.length).toBe(3); + expect(fixture.nativeElement.querySelector('tbody').children.length).toBe(2); + expect(fixture.nativeElement.querySelector('tbody tr td').textContent).toContain('source1'); + }); + + it('should not display last event column', () => { + fixture.componentRef.setInput('loading', false); + fixture.componentRef.setInput('showLastEvent', false); + fixture.componentRef.setInput('sources', sourcesWithoutEvent); + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('table')).toBeTruthy(); + expect(fixture.nativeElement.querySelector('thead tr').children.length).toBe(2); + expect(fixture.nativeElement.querySelector('tbody').children.length).toBe(2); + expect(fixture.nativeElement.querySelector('table tbody tr td').textContent).toContain('source1'); + }); + + it('should emit sourceSelected event when a source is clicked', () => { + spyOn(component.sourceSelected, 'emit'); + fixture.componentRef.setInput('loading', false); + fixture.componentRef.setInput('paginationConfig', paginationConfig); + fixture.componentRef.setInput('sources', sources); + fixture.detectChanges(); + const button = fixture.nativeElement.querySelector('.btn-outline-primary'); + button.click(); + expect(component.sourceSelected.emit).toHaveBeenCalledWith('source1'); + }); + + it('should emit paginationChange event when pagination changes', () => { + spyOn(component.paginationChange, 'emit'); + fixture.componentRef.setInput('loading', false); + fixture.componentRef.setInput('paginationConfig', paginationConfig); + + fixture.detectChanges(); + const paginationComponent = fixture.nativeElement.querySelector('ds-pagination'); + paginationComponent.dispatchEvent(new Event('paginationChange')); + expect(component.paginationChange.emit).toHaveBeenCalled(); + }); +}); diff --git a/src/app/notifications/shared/source-list.component.ts b/src/app/notifications/shared/source-list.component.ts new file mode 100644 index 0000000000..d49d7977fe --- /dev/null +++ b/src/app/notifications/shared/source-list.component.ts @@ -0,0 +1,75 @@ +import { + AsyncPipe, + DatePipe, +} from '@angular/common'; +import { + Component, + input, + InputSignal, + output, +} from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { TranslateModule } from '@ngx-translate/core'; + +import { AlertComponent } from '../../shared/alert/alert.component'; +import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component'; +import { PaginationComponent } from '../../shared/pagination/pagination.component'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; + +export interface SourceObject { + id: string; + lastEvent?: string; + total: number; +} + +/** + * Component to display the Quality Assurance source list. + */ +@Component({ + selector: 'ds-source-list', + templateUrl: './source-list.component.html', + styleUrls: ['./source-list.component.scss'], + standalone: true, + imports: [AlertComponent, ThemedLoadingComponent, PaginationComponent, RouterLink, AsyncPipe, TranslateModule, DatePipe], +}) +export class SourceListComponent { + + /** + * A boolean indicating whether the sources are in a loading state. + */ + loading: InputSignal = input(false); + + /** + * The pagination system configuration for HTML listing. + * @type {PaginationComponentOptions} + */ + paginationConfig: InputSignal = input(); + + /** + * A boolean indicating whether to show the last event column. + */ + showLastEvent: InputSignal = input(); + + /** + * The source list. + */ + sources: InputSignal = input(); + + /** + * The total number of Quality Assurance sources. + */ + totalElements: InputSignal = input(); + + /** + * Event emitter for when a source is selected. + * Emits the ID of the selected source. + */ + sourceSelected = output(); + + /** + * Event emitter for when the pagination changes. + * Emits a string representation of the pagination change. + */ + paginationChange = output(); + +} diff --git a/src/app/quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-data.resolver.ts b/src/app/quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-data.resolver.ts index 42ff19299e..dd9ae36476 100644 --- a/src/app/quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-data.resolver.ts +++ b/src/app/quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-data.resolver.ts @@ -34,7 +34,7 @@ export const qualityAssuranceSourceDataResolver: ResolveFn => { const pageSize = appConfig.qualityAssuranceConfig.pageSize; - return qualityAssuranceSourceService.getSources(pageSize, 0).pipe( + return qualityAssuranceSourceService.getSources(pageSize, 1).pipe( map((sources: PaginatedList) => { if (sources.page.length === 1) { router.navigate([getResolvedUrl(route) + '/' + sources.page[0].id]);