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) {
-
- {{'quality-assurance.noSource' | translate}}
-
- }
- @if ((sources$ | async)?.length !== 0) {
-
-
-
-
- {{'quality-assurance.table.source' | translate}} |
- {{'quality-assurance.table.last-event' | translate}} |
- {{'quality-assurance.table.actions' | translate}} |
-
-
-
- @for (sourceElement of (sources$ | async); track sourceElement; let i = $index) {
-
- {{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) {
+
+ {{'quality-assurance.noSource' | translate}}
+
+ }
+ @if (sources()?.length !== 0) {
+
+
+
+
+ {{'quality-assurance.table.source' | translate}} |
+ @if (showLastEvent()) {
+ {{'quality-assurance.table.last-event' | translate}} |
+ }
+ {{'quality-assurance.table.actions' | translate}} |
+
+
+
+ @for (sourceElement of sources(); track sourceElement; let i = $index) {
+
+ {{sourceElement.id}} |
+ @if (showLastEvent()) {
+ {{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]);