mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-14 21:43:04 +00:00
[CST-18016] Create a new component to list the qa sources
This commit is contained in:
@@ -5,65 +5,12 @@
|
||||
<ds-alert [type]="'alert-info'" [content]="'quality-assurance.source.description'"></ds-alert>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2 class="h4 border-bottom pb-2">{{'quality-assurance.source'| translate}}</h2>
|
||||
|
||||
@if ((isSourceLoading() | async)) {
|
||||
<ds-loading class="container" message="{{'quality-assurance.loading' | translate}}"></ds-loading>
|
||||
}
|
||||
@if ((isSourceLoading() | async) !== true) {
|
||||
<ds-pagination
|
||||
[paginationOptions]="paginationConfig"
|
||||
[collectionSize]="(totalElements$ | async)"
|
||||
[hideGear]="false"
|
||||
[hideSortOptions]="true"
|
||||
(paginationChange)="getQualityAssuranceSource()">
|
||||
@if ((isSourceProcessing() | async)) {
|
||||
<ds-loading class="container" message="'quality-assurance.loading' | translate"></ds-loading>
|
||||
}
|
||||
@if ((isSourceProcessing() | async) !== true) {
|
||||
@if ((sources$ | async)?.length === 0) {
|
||||
<div class="alert alert-info w-100 mb-2 mt-2" role="alert">
|
||||
{{'quality-assurance.noSource' | translate}}
|
||||
</div>
|
||||
}
|
||||
@if ((sources$ | async)?.length !== 0) {
|
||||
<div class="table-responsive mt-2">
|
||||
<table id="epeople" class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{'quality-assurance.table.source' | translate}}</th>
|
||||
<th scope="col">{{'quality-assurance.table.last-event' | translate}}</th>
|
||||
<th scope="col">{{'quality-assurance.table.actions' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (sourceElement of (sources$ | async); track sourceElement; let i = $index) {
|
||||
<tr>
|
||||
<td>{{sourceElement.id}}</td>
|
||||
<td>{{sourceElement.lastEvent | date: 'dd/MM/yyyy hh:mm' }}</td>
|
||||
<td>
|
||||
<div class="btn-group edit-field">
|
||||
<button
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
title="{{'quality-assurance.source-list.button.detail' | translate : { param: sourceElement.id } }}"
|
||||
[routerLink]="[sourceElement.id]">
|
||||
<span class="badge bg-info">{{sourceElement.totalEvents}}</span>
|
||||
<i class="fas fa-info fa-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</ds-pagination>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<ds-source-list [loading]="(isSourceLoading() | async) === true || (isSourceProcessing() | async) === true"
|
||||
[paginationConfig]="paginationConfig"
|
||||
[showLastEvent]="true"
|
||||
[sources]="sources$ | async"
|
||||
[totalElements]="totalElements$ | async"
|
||||
(paginationChange)="getQualityAssuranceSource()"
|
||||
(sourceSelected)="onSelect($event)"></ds-source-list>
|
||||
</div>
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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<QualityAssuranceSourceObject[]>;
|
||||
public sources$: Observable<SourceObject[]>;
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
|
59
src/app/notifications/shared/source-list.component.html
Normal file
59
src/app/notifications/shared/source-list.component.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<div class="col-12">
|
||||
<h2 class="h4 border-bottom pb-2">{{'quality-assurance.source'| translate}}</h2>
|
||||
|
||||
@if (loading()) {
|
||||
<ds-loading class="container" message="{{'quality-assurance.loading' | translate}}"></ds-loading>
|
||||
} @else {
|
||||
<ds-pagination
|
||||
[paginationOptions]="paginationConfig()"
|
||||
[collectionSize]="totalElements()"
|
||||
[hideGear]="false"
|
||||
[hideSortOptions]="true"
|
||||
(paginationChange)="paginationChange.emit($event)">
|
||||
@if (sources()?.length === 0) {
|
||||
<div class="alert alert-info w-100 mb-2 mt-2" role="alert">
|
||||
{{'quality-assurance.noSource' | translate}}
|
||||
</div>
|
||||
}
|
||||
@if (sources()?.length !== 0) {
|
||||
<div class="table-responsive mt-2">
|
||||
<table id="epeople" class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{'quality-assurance.table.source' | translate}}</th>
|
||||
@if (showLastEvent()) {
|
||||
<th scope="col">{{'quality-assurance.table.last-event' | translate}}</th>
|
||||
}
|
||||
<th scope="col">{{'quality-assurance.table.actions' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (sourceElement of sources(); track sourceElement; let i = $index) {
|
||||
<tr>
|
||||
<td>{{sourceElement.id}}</td>
|
||||
@if (showLastEvent()) {
|
||||
<td>{{sourceElement.lastEvent | date: 'dd/MM/yyyy hh:mm' }}</td>
|
||||
}
|
||||
<td>
|
||||
<div class="btn-group edit-field">
|
||||
<button
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
title="{{'quality-assurance.source-list.button.detail' | translate : { param: sourceElement.id } }}"
|
||||
(click)="sourceSelected.emit(sourceElement.id)">
|
||||
<span class="badge bg-info">{{sourceElement.total}}</span>
|
||||
<i class="fas fa-info fa-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
</ds-pagination>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
114
src/app/notifications/shared/source-list.component.spec.ts
Normal file
114
src/app/notifications/shared/source-list.component.spec.ts
Normal file
@@ -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<SourceListComponent>;
|
||||
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();
|
||||
});
|
||||
});
|
75
src/app/notifications/shared/source-list.component.ts
Normal file
75
src/app/notifications/shared/source-list.component.ts
Normal file
@@ -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<boolean> = input<boolean>(false);
|
||||
|
||||
/**
|
||||
* The pagination system configuration for HTML listing.
|
||||
* @type {PaginationComponentOptions}
|
||||
*/
|
||||
paginationConfig: InputSignal<PaginationComponentOptions> = input<PaginationComponentOptions>();
|
||||
|
||||
/**
|
||||
* A boolean indicating whether to show the last event column.
|
||||
*/
|
||||
showLastEvent: InputSignal<boolean> = input<boolean>();
|
||||
|
||||
/**
|
||||
* The source list.
|
||||
*/
|
||||
sources: InputSignal<SourceObject[]|null> = input<SourceObject[]|null>();
|
||||
|
||||
/**
|
||||
* The total number of Quality Assurance sources.
|
||||
*/
|
||||
totalElements: InputSignal<number> = input<number>();
|
||||
|
||||
/**
|
||||
* Event emitter for when a source is selected.
|
||||
* Emits the ID of the selected source.
|
||||
*/
|
||||
sourceSelected = output<string>();
|
||||
|
||||
/**
|
||||
* Event emitter for when the pagination changes.
|
||||
* Emits a string representation of the pagination change.
|
||||
*/
|
||||
paginationChange = output<string>();
|
||||
|
||||
}
|
@@ -34,7 +34,7 @@ export const qualityAssuranceSourceDataResolver: ResolveFn<QualityAssuranceSourc
|
||||
): Observable<QualityAssuranceSourceObject[]> => {
|
||||
const pageSize = appConfig.qualityAssuranceConfig.pageSize;
|
||||
|
||||
return qualityAssuranceSourceService.getSources(pageSize, 0).pipe(
|
||||
return qualityAssuranceSourceService.getSources(pageSize, 1).pipe(
|
||||
map((sources: PaginatedList<QualityAssuranceSourceObject>) => {
|
||||
if (sources.page.length === 1) {
|
||||
router.navigate([getResolvedUrl(route) + '/' + sources.page[0].id]);
|
||||
|
Reference in New Issue
Block a user