[CST-5537] Fix issues and refactoring in order to remove nested subscriptions

This commit is contained in:
Giuseppe Digilio
2022-11-11 12:56:57 +01:00
parent 58db3c7b0e
commit 3188374800
11 changed files with 196 additions and 154 deletions

View File

@@ -6,4 +6,4 @@ import { ResourceType } from '../../../shared/resource-type';
* Needs to be in a separate file to prevent circular * Needs to be in a separate file to prevent circular
* dependencies in webpack. * dependencies in webpack.
*/ */
export const QUALITY_ASSURANCE_EVENT_OBJECT = new ResourceType('qaevent'); export const QUALITY_ASSURANCE_EVENT_OBJECT = new ResourceType('qualityassuranceevent');

View File

@@ -1,21 +1,23 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h2 class="border-bottom pb-2">{{'notifications.events.title'| translate}}</h2> <h2 class="border-bottom pb-2">
<p>{{'quality-assurance.events.description'| translate}}</p> <div class="d-flex justify-content-between">
<p> {{'notifications.events.title'| translate}}
<a class="btn btn-outline-secondary" [routerLink]="['/admin/notifications/quality-assurance']"> <a class="btn btn-outline-secondary" [routerLink]="['/admin/notifications/quality-assurance']">
<i class="fas fa-angle-double-left"></i> <i class="fas fa-angle-double-left"></i>
{{'quality-assurance.events.back' | translate}} {{'quality-assurance.events.back' | translate}}
</a> </a>
</p> </div>
</h2>
<ds-alert [type]="'alert-info'" [content]="'quality-assurance.events.description'"></ds-alert>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h3 class="border-bottom pb-2"> <h4 class="border-bottom pb-2">
{{'quality-assurance.events.topic' | translate}} {{this.showTopic}} {{'quality-assurance.events.topic' | translate}} {{this.showTopic}}
</h3> </h4>
<ds-loading class="container" *ngIf="(isEventPageLoading | async)" message="{{'quality-assurance.loading' | translate}}"></ds-loading> <ds-loading class="container" *ngIf="(isEventPageLoading | async)" message="{{'quality-assurance.loading' | translate}}"></ds-loading>
@@ -25,8 +27,7 @@
[sortOptions]="paginationSortConfig" [sortOptions]="paginationSortConfig"
(paginationChange)="getQualityAssuranceEvents()"> (paginationChange)="getQualityAssuranceEvents()">
<ds-loading class="container" *ngIf="(isEventLoading | async)" message="{{'quality-assurance.loading' | translate}}"></ds-loading> <ng-container>
<ng-container *ngIf="!(isEventLoading | async)">
<div *ngIf="(eventsUpdated$|async)?.length == 0" class="alert alert-info w-100 mb-2 mt-2" role="alert"> <div *ngIf="(eventsUpdated$|async)?.length == 0" class="alert alert-info w-100 mb-2 mt-2" role="alert">
{{'quality-assurance.noEvents' | translate}} {{'quality-assurance.noEvents' | translate}}
</div> </div>
@@ -34,11 +35,15 @@
<table id="events" class="table table-striped table-hover table-bordered"> <table id="events" class="table table-striped table-hover table-bordered">
<thead> <thead>
<tr> <tr>
<th scope="col">{{'quality-assurance.event.table.trust' | translate}}</th> <th scope="col" class="trust-col">{{'quality-assurance.event.table.trust' | translate}}</th>
<th scope="col">{{'quality-assurance.event.table.publication' | translate}}</th> <th scope="col" class="title-col">{{'quality-assurance.event.table.publication' | translate}}</th>
<th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') == -1" scope="col">{{'quality-assurance.event.table.details' | translate}}</th> <th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') == -1" scope="col" class="content-col">
<th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') !== -1" scope="col">{{'quality-assurance.event.table.project-details' | translate}}</th> {{'quality-assurance.event.table.details' | translate}}
<th scope="col" class="button-rows">{{'quality-assurance.event.table.actions' | translate}}</th> </th>
<th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') !== -1" scope="col" class="content-col">
{{'quality-assurance.event.table.project-details' | translate}}
</th>
<th scope="col" class="button-col">{{'quality-assurance.event.table.actions' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -51,7 +56,7 @@
<span *ngIf="!eventElement?.target">{{eventElement.title}}</span> <span *ngIf="!eventElement?.target">{{eventElement.title}}</span>
</td> </td>
<td *ngIf="showTopic.indexOf('/PID') !== -1"> <td *ngIf="showTopic.indexOf('/PID') !== -1">
<p><span class="small">{{'quality-assurance.event.table.pidtype' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.type}}</span></p> <p><span class="small">{{'quality-assurance.event.table.pidtype' | translate}}</span>&nbsp;<span class="badge badge-info">{{eventElement.event.message.type}}</span></p>
<p><span class="small">{{'quality-assurance.event.table.pidvalue' | translate}}</span><br> <p><span class="small">{{'quality-assurance.event.table.pidvalue' | translate}}</span><br>
<a *ngIf="hasPIDHref(eventElement.event.message); else noPID" href="{{getPIDHref(eventElement.event.message)}}" target="_blank"> <a *ngIf="hasPIDHref(eventElement.event.message); else noPID" href="{{getPIDHref(eventElement.event.message)}}" target="_blank">
{{eventElement.event.message.value}} {{eventElement.event.message.value}}
@@ -82,18 +87,19 @@
<a href="https://explore.openaire.eu/search/project?projectId={{ eventElement.event.message.openaireId}}" target="_blank">{{eventElement.event.message.title}}</a> <a href="https://explore.openaire.eu/search/project?projectId={{ eventElement.event.message.openaireId}}" target="_blank">{{eventElement.event.message.title}}</a>
</p> </p>
<p> <p>
<span *ngIf="eventElement.event.message.acronym"><span class="small">{{'quality-assurance.event.table.acronym' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.acronym}}</span><br></span> <span *ngIf="eventElement.event.message.acronym"><span class="small">{{'quality-assurance.event.table.acronym' | translate}}</span>&nbsp;<span class="badge badge-info">{{eventElement.event.message.acronym}}</span><br></span>
<span *ngIf="eventElement.event.message.code"><span class="small">{{'quality-assurance.event.table.code' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.code}}</span><br></span> <span *ngIf="eventElement.event.message.code"><span class="small">{{'quality-assurance.event.table.code' | translate}}</span>&nbsp;<span class="badge badge-info">{{eventElement.event.message.code}}</span><br></span>
<span *ngIf="eventElement.event.message.funder"><span class="small">{{'quality-assurance.event.table.funder' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.funder}}</span><br></span> <span *ngIf="eventElement.event.message.funder"><span class="small">{{'quality-assurance.event.table.funder' | translate}}</span>&nbsp;<span class="badge badge-info">{{eventElement.event.message.funder}}</span><br></span>
<span *ngIf="eventElement.event.message.fundingProgram"><span class="small">{{'quality-assurance.event.table.fundingProgram' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.fundingProgram}}</span><br></span> <span *ngIf="eventElement.event.message.fundingProgram"><span class="small">{{'quality-assurance.event.table.fundingProgram' | translate}}</span>&nbsp;<span class="badge badge-info">{{eventElement.event.message.fundingProgram}}</span><br></span>
<span *ngIf="eventElement.event.message.jurisdiction"><span class="small">{{'quality-assurance.event.table.jurisdiction' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.jurisdiction}}</span></span> <span *ngIf="eventElement.event.message.jurisdiction"><span class="small">{{'quality-assurance.event.table.jurisdiction' | translate}}</span>&nbsp;<span class="badge badge-info">{{eventElement.event.message.jurisdiction}}</span></span>
</p> </p>
<hr> <hr>
<div> <div>
{{(eventElement.hasProject ? 'quality-assurance.event.project.found' : 'quality-assurance.event.project.notFound') | translate}} {{(eventElement.hasProject ? 'quality-assurance.event.project.found' : 'quality-assurance.event.project.notFound') | translate}}
<a target="_blank" *ngIf="eventElement.hasProject" title="{{eventElement.projectTitle}}" [routerLink]="['/items', eventElement.projectId]">{{eventElement.handle}} </a> <a target="_blank" *ngIf="eventElement.hasProject" title="{{eventElement.projectTitle}}" [routerLink]="['/items', eventElement.projectId]">{{eventElement.handle}} </a>
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-outline-primary btn-sm" <button *ngIf="!eventElement.hasProject"
class="btn btn-outline-primary btn-sm"
[disabled]="eventElement.isRunning" [disabled]="eventElement.isRunning"
(click)="openModalLookup(eventElement); $event.stopPropagation();"> (click)="openModalLookup(eventElement); $event.stopPropagation();">
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
@@ -149,7 +155,7 @@
</ds-pagination> </ds-pagination>
</div> </div>
</div> </div>
<div class="row"> <div class="row text-right">
<div class="col-md-12"> <div class="col-md-12">
<a class="btn btn-outline-secondary" [routerLink]="['/admin/notifications/quality-assurance']"> <a class="btn btn-outline-secondary" [routerLink]="['/admin/notifications/quality-assurance']">
<i class="fas fa-angle-double-left"></i> <i class="fas fa-angle-double-left"></i>

View File

@@ -1,5 +1,12 @@
.button-rows { .button-col, .trust-col {
min-width: 200px; width: 15%;
}
.title-col {
width: 30%;
}
.content-col {
width: 40%;
} }
.button-width { .button-width {

View File

@@ -5,16 +5,18 @@ import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/t
import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
import { QualityAssuranceEventRestService } from '../../../core/suggestion-notifications/qa/events/quality-assurance-event-rest.service'; import {
QualityAssuranceEventRestService
} from '../../../core/suggestion-notifications/qa/events/quality-assurance-event-rest.service';
import { QualityAssuranceEventsComponent } from './quality-assurance-events.component'; import { QualityAssuranceEventsComponent } from './quality-assurance-events.component';
import { import {
getMockQualityAssuranceEventRestService, getMockQualityAssuranceEventRestService,
ItemMockPid10, ItemMockPid10,
ItemMockPid8, ItemMockPid8,
ItemMockPid9, ItemMockPid9,
NotificationsMockDspaceObject,
qualityAssuranceEventObjectMissingProjectFound, qualityAssuranceEventObjectMissingProjectFound,
qualityAssuranceEventObjectMissingProjectNotFound, qualityAssuranceEventObjectMissingProjectNotFound
NotificationsMockDspaceObject
} from '../../../shared/mocks/notifications.mock'; } from '../../../shared/mocks/notifications.mock';
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub'; import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
@@ -22,10 +24,12 @@ import { getMockTranslateService } from '../../../shared/mocks/translate.service
import { createTestComponent } from '../../../shared/testing/utils.test'; import { createTestComponent } from '../../../shared/testing/utils.test';
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { QualityAssuranceEventObject } from '../../../core/suggestion-notifications/qa/models/quality-assurance-event.model'; import {
QualityAssuranceEventObject
} from '../../../core/suggestion-notifications/qa/models/quality-assurance-event.model';
import { QualityAssuranceEventData } from '../project-entry-import-modal/project-entry-import-modal.component'; import { QualityAssuranceEventData } from '../project-entry-import-modal/project-entry-import-modal.component';
import { TestScheduler } from 'rxjs/testing'; import { TestScheduler } from 'rxjs/testing';
import { getTestScheduler } from 'jasmine-marbles'; import { cold, getTestScheduler } from 'jasmine-marbles';
import { followLink } from '../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../shared/utils/follow-link-config.model';
import { PageInfo } from '../../../core/shared/page-info.model'; import { PageInfo } from '../../../core/shared/page-info.model';
import { buildPaginatedList } from '../../../core/data/paginated-list.model'; import { buildPaginatedList } from '../../../core/data/paginated-list.model';
@@ -37,7 +41,7 @@ import {
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationService } from '../../../core/pagination/pagination.service';
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub'; import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
import {FindListOptions} from '../../../core/data/find-list-options.model'; import { FindListOptions } from '../../../core/data/find-list-options.model';
describe('QualityAssuranceEventsComponent test suite', () => { describe('QualityAssuranceEventsComponent test suite', () => {
let fixture: ComponentFixture<QualityAssuranceEventsComponent>; let fixture: ComponentFixture<QualityAssuranceEventsComponent>;
@@ -156,18 +160,16 @@ describe('QualityAssuranceEventsComponent test suite', () => {
compAsAny = null; compAsAny = null;
}); });
describe('setEventUpdated', () => { describe('fetchEvents', () => {
it('should update events', () => { it('should fetch events', () => {
const expected = [ const result = compAsAny.fetchEvents(events);
getQualityAssuranceEventData1(), const expected = cold('(a|)', {
getQualityAssuranceEventData2() a: [
]; getQualityAssuranceEventData1(),
scheduler.schedule(() => { getQualityAssuranceEventData2()
compAsAny.setEventUpdated(events); ]
}); });
scheduler.flush(); expect(result).toBeObservable(expected);
expect(comp.eventsUpdated$.value).toEqual(expected);
}); });
}); });
@@ -229,7 +231,10 @@ describe('QualityAssuranceEventsComponent test suite', () => {
describe('executeAction', () => { describe('executeAction', () => {
it('should call getQualityAssuranceEvents on 200 response from REST', () => { it('should call getQualityAssuranceEvents on 200 response from REST', () => {
const action = 'ACCEPTED'; const action = 'ACCEPTED';
spyOn(compAsAny, 'getQualityAssuranceEvents'); spyOn(compAsAny, 'getQualityAssuranceEvents').and.returnValue(observableOf([
getQualityAssuranceEventData1(),
getQualityAssuranceEventData2()
]));
qualityAssuranceEventRestServiceStub.patchEvent.and.returnValue(createSuccessfulRemoteDataObject$({})); qualityAssuranceEventRestServiceStub.patchEvent.and.returnValue(createSuccessfulRemoteDataObject$({}));
scheduler.schedule(() => { scheduler.schedule(() => {
@@ -279,7 +284,7 @@ describe('QualityAssuranceEventsComponent test suite', () => {
}); });
describe('getQualityAssuranceEvents', () => { describe('getQualityAssuranceEvents', () => {
it('should call the "qualityAssuranceEventRestService.getEventsByTopic" to take data and "setEventUpdated" to populate eventData', () => { it('should call the "qualityAssuranceEventRestService.getEventsByTopic" to take data and "fetchEvents" to populate eventData', () => {
comp.paginationConfig = new PaginationComponentOptions(); comp.paginationConfig = new PaginationComponentOptions();
comp.paginationConfig.currentPage = 1; comp.paginationConfig.currentPage = 1;
comp.paginationConfig.pageSize = 20; comp.paginationConfig.pageSize = 20;
@@ -292,7 +297,7 @@ describe('QualityAssuranceEventsComponent test suite', () => {
const pageInfo = new PageInfo({ const pageInfo = new PageInfo({
elementsPerPage: comp.paginationConfig.pageSize, elementsPerPage: comp.paginationConfig.pageSize,
totalElements: 0, totalElements: 2,
totalPages: 1, totalPages: 1,
currentPage: comp.paginationConfig.currentPage currentPage: comp.paginationConfig.currentPage
}); });
@@ -303,10 +308,13 @@ describe('QualityAssuranceEventsComponent test suite', () => {
const paginatedList = buildPaginatedList(pageInfo, array); const paginatedList = buildPaginatedList(pageInfo, array);
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
qualityAssuranceEventRestServiceStub.getEventsByTopic.and.returnValue(observableOf(paginatedListRD)); qualityAssuranceEventRestServiceStub.getEventsByTopic.and.returnValue(observableOf(paginatedListRD));
spyOn(compAsAny, 'setEventUpdated'); spyOn(compAsAny, 'fetchEvents').and.returnValue(observableOf([
getQualityAssuranceEventData1(),
getQualityAssuranceEventData2()
]));
scheduler.schedule(() => { scheduler.schedule(() => {
compAsAny.getQualityAssuranceEvents(); compAsAny.getQualityAssuranceEvents().subscribe();
}); });
scheduler.flush(); scheduler.flush();
@@ -315,7 +323,7 @@ describe('QualityAssuranceEventsComponent test suite', () => {
options, options,
followLink('target'),followLink('related') followLink('target'),followLink('related')
); );
expect(compAsAny.setEventUpdated).toHaveBeenCalled(); expect(compAsAny.fetchEvents).toHaveBeenCalled();
}); });
}); });

View File

@@ -3,8 +3,8 @@ import { ActivatedRoute } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, from, Observable, of as observableOf, Subscription } from 'rxjs'; import { BehaviorSubject, combineLatest, from, Observable, of, Subscription } from 'rxjs';
import { distinctUntilChanged, map, mergeMap, scan, switchMap, take } from 'rxjs/operators'; import { distinctUntilChanged, last, map, mergeMap, scan, switchMap, take, tap } from 'rxjs/operators';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginatedList } from '../../../core/data/paginated-list.model';
@@ -19,7 +19,7 @@ import {
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { Metadata } from '../../../core/shared/metadata.utils'; import { Metadata } from '../../../core/shared/metadata.utils';
import { followLink } from '../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../shared/utils/follow-link-config.model';
import { hasValue, isEmpty } from '../../../shared/empty.util'; import { hasValue } from '../../../shared/empty.util';
import { ItemSearchResult } from '../../../shared/object-collection/shared/item-search-result.model'; import { ItemSearchResult } from '../../../shared/object-collection/shared/item-search-result.model';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { import {
@@ -28,7 +28,6 @@ import {
} from '../project-entry-import-modal/project-entry-import-modal.component'; } from '../project-entry-import-modal/project-entry-import-modal.component';
import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationService } from '../../../core/pagination/pagination.service';
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { FindListOptions } from '../../../core/data/find-list-options.model'; import { FindListOptions } from '../../../core/data/find-list-options.model';
@@ -65,7 +64,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
* The total number of Quality Assurance events. * The total number of Quality Assurance events.
* @type {Observable<number>} * @type {Observable<number>}
*/ */
public totalElements$: Observable<number>; public totalElements$: BehaviorSubject<number> = new BehaviorSubject<number>(null);
/** /**
* The topic of the Quality Assurance events; suitable for displaying. * The topic of the Quality Assurance events; suitable for displaying.
* @type {string} * @type {string}
@@ -86,11 +85,6 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
* @type {Observable<boolean>} * @type {Observable<boolean>}
*/ */
public isEventPageLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); public isEventPageLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
/**
* Contains the information about the loading status of the events inside the pagination component.
* @type {Observable<boolean>}
*/
public isEventLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
/** /**
* The modal reference. * The modal reference.
* @type {any} * @type {any}
@@ -104,7 +98,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
/** /**
* The FindListOptions object * The FindListOptions object
*/ */
protected defaultConfig: FindListOptions = Object.assign(new FindListOptions(), {sort: this.paginationSortConfig}); protected defaultConfig: FindListOptions = Object.assign(new FindListOptions(), { sort: this.paginationSortConfig });
/** /**
* Array to track all the component subscriptions. Useful to unsubscribe them with 'onDestroy'. * Array to track all the component subscriptions. Useful to unsubscribe them with 'onDestroy'.
* @type {Array} * @type {Array}
@@ -138,13 +132,17 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
this.activatedRoute.paramMap.pipe( this.activatedRoute.paramMap.pipe(
map((params) => params.get('topicId')), map((params) => params.get('topicId')),
take(1) take(1),
).subscribe((id: string) => { switchMap((id: string) => {
const regEx = /!/g; const regEx = /!/g;
this.showTopic = id.replace(regEx, '/'); this.showTopic = id.replace(regEx, '/');
this.topic = id; this.topic = id;
return this.getQualityAssuranceEvents();
})
).subscribe((events: QualityAssuranceEventData[]) => {
console.log(events);
this.eventsUpdated$.next(events);
this.isEventPageLoading.next(false); this.isEventPageLoading.next(false);
this.getQualityAssuranceEvents();
}); });
} }
@@ -240,20 +238,25 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
public executeAction(action: string, eventData: QualityAssuranceEventData): void { public executeAction(action: string, eventData: QualityAssuranceEventData): void {
eventData.isRunning = true; eventData.isRunning = true;
this.subs.push( this.subs.push(
this.qualityAssuranceEventRestService.patchEvent(action, eventData.event, eventData.reason).pipe(getFirstCompletedRemoteData()) this.qualityAssuranceEventRestService.patchEvent(action, eventData.event, eventData.reason).pipe(
.subscribe((rd: RemoteData<QualityAssuranceEventObject>) => { getFirstCompletedRemoteData(),
if (rd.isSuccess && rd.statusCode === 200) { switchMap((rd: RemoteData<QualityAssuranceEventObject>) => {
if (rd.hasSucceeded) {
this.notificationsService.success( this.notificationsService.success(
this.translateService.instant('quality-assurance.event.action.saved') this.translateService.instant('quality-assurance.event.action.saved')
); );
this.getQualityAssuranceEvents(); return this.getQualityAssuranceEvents();
} else { } else {
this.notificationsService.error( this.notificationsService.error(
this.translateService.instant('quality-assurance.event.action.error') this.translateService.instant('quality-assurance.event.action.error')
); );
return of(this.eventsUpdated$.value);
} }
eventData.isRunning = false;
}) })
).subscribe((events: QualityAssuranceEventData[]) => {
this.eventsUpdated$.next(events);
eventData.isRunning = false;
})
); );
} }
@@ -274,7 +277,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
this.subs.push( this.subs.push(
this.qualityAssuranceEventRestService.boundProject(eventData.id, projectId).pipe(getFirstCompletedRemoteData()) this.qualityAssuranceEventRestService.boundProject(eventData.id, projectId).pipe(getFirstCompletedRemoteData())
.subscribe((rd: RemoteData<QualityAssuranceEventObject>) => { .subscribe((rd: RemoteData<QualityAssuranceEventObject>) => {
if (rd.isSuccess) { if (rd.hasSucceeded) {
this.notificationsService.success( this.notificationsService.success(
this.translateService.instant('quality-assurance.event.project.bounded') this.translateService.instant('quality-assurance.event.project.bounded')
); );
@@ -303,7 +306,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
this.subs.push( this.subs.push(
this.qualityAssuranceEventRestService.removeProject(eventData.id).pipe(getFirstCompletedRemoteData()) this.qualityAssuranceEventRestService.removeProject(eventData.id).pipe(getFirstCompletedRemoteData())
.subscribe((rd: RemoteData<QualityAssuranceEventObject>) => { .subscribe((rd: RemoteData<QualityAssuranceEventObject>) => {
if (rd.isSuccess) { if (rd.hasSucceeded) {
this.notificationsService.success( this.notificationsService.success(
this.translateService.instant('quality-assurance.event.project.removed') this.translateService.instant('quality-assurance.event.project.removed')
); );
@@ -337,12 +340,11 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
return event.pidHref; return event.pidHref;
} }
/** /**
* Dispatch the Quality Assurance events retrival. * Dispatch the Quality Assurance events retrival.
*/ */
public getQualityAssuranceEvents(): void { public getQualityAssuranceEvents(): Observable<QualityAssuranceEventData[]> {
this.paginationService.getFindListOptions(this.paginationConfig.id, this.defaultConfig).pipe( return this.paginationService.getFindListOptions(this.paginationConfig.id, this.defaultConfig).pipe(
distinctUntilChanged(), distinctUntilChanged(),
switchMap((options: FindListOptions) => this.qualityAssuranceEventRestService.getEventsByTopic( switchMap((options: FindListOptions) => this.qualityAssuranceEventRestService.getEventsByTopic(
this.topic, this.topic,
@@ -350,16 +352,24 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
followLink('target'), followLink('related') followLink('target'), followLink('related')
)), )),
getFirstCompletedRemoteData(), getFirstCompletedRemoteData(),
).subscribe((rd: RemoteData<PaginatedList<QualityAssuranceEventObject>>) => { switchMap((rd: RemoteData<PaginatedList<QualityAssuranceEventObject>>) => {
if (rd.hasSucceeded) { if (rd.hasSucceeded) {
this.isEventLoading.next(false); this.totalElements$.next(rd.payload.totalElements);
this.totalElements$ = observableOf(rd.payload.totalElements); if (rd.payload.totalElements > 0) {
this.setEventUpdated(rd.payload.page); console.log(rd.payload.page);
} else { return this.fetchEvents(rd.payload.page);
throw new Error('Can\'t retrieve Quality Assurance events from the Broker events REST service'); } else {
} return of([]);
this.qualityAssuranceEventRestService.clearFindByTopicRequests(); }
}); } else {
throw new Error('Can\'t retrieve Quality Assurance events from the Broker events REST service');
}
}),
take(1),
tap(() => {
this.qualityAssuranceEventRestService.clearFindByTopicRequests();
})
);
} }
/** /**
@@ -372,55 +382,47 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
} }
/** /**
* Set the project status for the Quality Assurance events. * Fetch Quality Assurance events in order to build proper QualityAssuranceEventData object.
* *
* @param {QualityAssuranceEventObject[]} events * @param {QualityAssuranceEventObject[]} events
* the Quality Assurance event item * the Quality Assurance event item
* @return array of QualityAssuranceEventData
*/ */
protected setEventUpdated(events: QualityAssuranceEventObject[]): void { protected fetchEvents(events: QualityAssuranceEventObject[]): Observable<QualityAssuranceEventData[]> {
if (isEmpty(events)) { return from(events).pipe(
this.eventsUpdated$.next([]); mergeMap((event: QualityAssuranceEventObject) => {
} else { const related$ = event.related.pipe(
this.subs.push( getFirstCompletedRemoteData(),
from(events).pipe( );
mergeMap((event: QualityAssuranceEventObject) => { const target$ = event.target.pipe(
const related$ = event.related.pipe( getFirstCompletedRemoteData()
getFirstCompletedRemoteData(), );
); return combineLatest([related$, target$]).pipe(
const target$ = event.target.pipe( map(([relatedItemRD, targetItemRD]: [RemoteData<Item>, RemoteData<Item>]) => {
getFirstCompletedRemoteData() const data: QualityAssuranceEventData = {
); event: event,
return combineLatest([related$, target$]).pipe( id: event.id,
map(([relatedItemRD, targetItemRD]: [RemoteData<Item>, RemoteData<Item>]) => { title: event.title,
const data: QualityAssuranceEventData = { hasProject: false,
event: event, projectTitle: null,
id: event.id, projectId: null,
title: event.title, handle: null,
hasProject: false, reason: null,
projectTitle: null, isRunning: false,
projectId: null, target: (targetItemRD?.hasSucceeded) ? targetItemRD.payload : null,
handle: null, };
reason: null, if (relatedItemRD?.hasSucceeded && relatedItemRD?.payload?.id) {
isRunning: false, data.hasProject = true;
target: (targetItemRD?.hasSucceeded) ? targetItemRD.payload : null, data.projectTitle = event.message.title;
}; data.projectId = relatedItemRD?.payload?.id;
if (relatedItemRD?.hasSucceeded && relatedItemRD?.payload?.id) { data.handle = relatedItemRD?.payload?.handle;
data.hasProject = true; }
data.projectTitle = event.message.title; return data;
data.projectId = relatedItemRD?.payload?.id; })
data.handle = relatedItemRD?.payload?.handle; );
} }),
return data; scan((acc: any, value: any) => [...acc, value], []),
}) last()
); );
}),
scan((acc: any, value: any) => [...acc, value], []),
take(events.length)
).subscribe((eventsReduced) => {
this.eventsUpdated$.next(eventsReduced);
}
)
);
}
} }
} }

View File

@@ -2,12 +2,12 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h2 class="border-bottom pb-2">{{'quality-assurance.title'| translate}}</h2> <h2 class="border-bottom pb-2">{{'quality-assurance.title'| translate}}</h2>
<p>{{'quality-assurance.source.description'| translate}}</p> <ds-alert [type]="'alert-info'" [content]="'quality-assurance.source.description'"></ds-alert>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h3 class="border-bottom pb-2">{{'quality-assurance.source'| translate}}</h3> <h4 class="border-bottom pb-2">{{'quality-assurance.source'| translate}}</h4>
<ds-loading class="container" *ngIf="(isSourceLoading() | async)" message="{{'quality-assurance.loading' | translate}}"></ds-loading> <ds-loading class="container" *ngIf="(isSourceLoading() | async)" message="{{'quality-assurance.loading' | translate}}"></ds-loading>
<ds-pagination *ngIf="!(isSourceLoading() | async)" <ds-pagination *ngIf="!(isSourceLoading() | async)"

View File

@@ -1,12 +1,19 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { find, map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { QualityAssuranceSourceRestService } from '../../../core/suggestion-notifications/qa/source/quality-assurance-source-rest.service';
import {
QualityAssuranceSourceRestService
} from '../../../core/suggestion-notifications/qa/source/quality-assurance-source-rest.service';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { QualityAssuranceSourceObject } from '../../../core/suggestion-notifications/qa/models/quality-assurance-source.model'; import {
import {FindListOptions} from '../../../core/data/find-list-options.model'; QualityAssuranceSourceObject
} from '../../../core/suggestion-notifications/qa/models/quality-assurance-source.model';
import { FindListOptions } from '../../../core/data/find-list-options.model';
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
/** /**
* The service handling all Quality Assurance source requests to the REST service. * The service handling all Quality Assurance source requests to the REST service.
@@ -20,7 +27,8 @@ export class QualityAssuranceSourceService {
*/ */
constructor( constructor(
private qualityAssuranceSourceRestService: QualityAssuranceSourceRestService private qualityAssuranceSourceRestService: QualityAssuranceSourceRestService
) { } ) {
}
/** /**
* Return the list of Quality Assurance source managing pagination and errors. * Return the list of Quality Assurance source managing pagination and errors.
@@ -42,7 +50,7 @@ export class QualityAssuranceSourceService {
}; };
return this.qualityAssuranceSourceRestService.getSources(findListOptions).pipe( return this.qualityAssuranceSourceRestService.getSources(findListOptions).pipe(
find((rd: RemoteData<PaginatedList<QualityAssuranceSourceObject>>) => !rd.isResponsePending), getFirstCompletedRemoteData(),
map((rd: RemoteData<PaginatedList<QualityAssuranceSourceObject>>) => { map((rd: RemoteData<PaginatedList<QualityAssuranceSourceObject>>) => {
if (rd.hasSucceeded) { if (rd.hasSucceeded) {
return rd.payload; return rd.payload;

View File

@@ -2,12 +2,12 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h2 class="border-bottom pb-2">{{'quality-assurance.title'| translate}}</h2> <h2 class="border-bottom pb-2">{{'quality-assurance.title'| translate}}</h2>
<p>{{'quality-assurance.topics.description'| translate:{source: sourceId} }}</p> <ds-alert [type]="'alert-info'">{{'quality-assurance.topics.description'| translate:{source: sourceId} }}</ds-alert>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h3 class="border-bottom pb-2">{{'quality-assurance.topics'| translate}}</h3> <h4 class="border-bottom pb-2">{{'quality-assurance.topics'| translate}}</h4>
<ds-loading class="container" *ngIf="(isTopicsLoading() | async)" message="{{'quality-assurance.loading' | translate}}"></ds-loading> <ds-loading class="container" *ngIf="(isTopicsLoading() | async)" message="{{'quality-assurance.loading' | translate}}"></ds-loading>
<ds-pagination *ngIf="!(isTopicsLoading() | async)" <ds-pagination *ngIf="!(isTopicsLoading() | async)"

View File

@@ -1,14 +1,18 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { distinctUntilChanged, map, take } from 'rxjs/operators'; import { distinctUntilChanged, take } from 'rxjs/operators';
import { SortOptions } from '../../../core/cache/models/sort-options.model'; import { SortOptions } from '../../../core/cache/models/sort-options.model';
import { QualityAssuranceTopicObject } from '../../../core/suggestion-notifications/qa/models/quality-assurance-topic.model'; import {
QualityAssuranceTopicObject
} from '../../../core/suggestion-notifications/qa/models/quality-assurance-topic.model';
import { hasValue } from '../../../shared/empty.util'; import { hasValue } from '../../../shared/empty.util';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { SuggestionNotificationsStateService } from '../../suggestion-notifications-state.service'; import { SuggestionNotificationsStateService } from '../../suggestion-notifications-state.service';
import { AdminQualityAssuranceTopicsPageParams } from '../../../admin/admin-notifications/admin-quality-assurance-topics-page/admin-quality-assurance-topics-page-resolver.service'; import {
AdminQualityAssuranceTopicsPageParams
} from '../../../admin/admin-notifications/admin-quality-assurance-topics-page/admin-quality-assurance-topics-page-resolver.service';
import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationService } from '../../../core/pagination/pagination.service';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { QualityAssuranceTopicsService } from './quality-assurance-topics.service'; import { QualityAssuranceTopicsService } from './quality-assurance-topics.service';
@@ -59,7 +63,9 @@ export class QualityAssuranceTopicsComponent implements OnInit {
/** /**
* Initialize the component variables. * Initialize the component variables.
* @param {PaginationService} paginationService * @param {PaginationService} paginationService
* @param {ActivatedRoute} activatedRoute
* @param {SuggestionNotificationsStateService} notificationsStateService * @param {SuggestionNotificationsStateService} notificationsStateService
* @param {QualityAssuranceTopicsService} qualityAssuranceTopicsService
*/ */
constructor( constructor(
private paginationService: PaginationService, private paginationService: PaginationService,

View File

@@ -1,13 +1,18 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { find, map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { QualityAssuranceTopicRestService } from '../../../core/suggestion-notifications/qa/topics/quality-assurance-topic-rest.service'; import {
QualityAssuranceTopicRestService
} from '../../../core/suggestion-notifications/qa/topics/quality-assurance-topic-rest.service';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list.model'; import { PaginatedList } from '../../../core/data/paginated-list.model';
import { QualityAssuranceTopicObject } from '../../../core/suggestion-notifications/qa/models/quality-assurance-topic.model'; import {
QualityAssuranceTopicObject
} from '../../../core/suggestion-notifications/qa/models/quality-assurance-topic.model';
import { RequestParam } from '../../../core/cache/models/request-param.model'; import { RequestParam } from '../../../core/cache/models/request-param.model';
import {FindListOptions} from '../../../core/data/find-list-options.model'; import { FindListOptions } from '../../../core/data/find-list-options.model';
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
/** /**
* The service handling all Quality Assurance topic requests to the REST service. * The service handling all Quality Assurance topic requests to the REST service.
@@ -49,7 +54,7 @@ export class QualityAssuranceTopicsService {
}; };
return this.qualityAssuranceTopicRestService.getTopics(findListOptions).pipe( return this.qualityAssuranceTopicRestService.getTopics(findListOptions).pipe(
find((rd: RemoteData<PaginatedList<QualityAssuranceTopicObject>>) => !rd.isResponsePending), getFirstCompletedRemoteData(),
map((rd: RemoteData<PaginatedList<QualityAssuranceTopicObject>>) => { map((rd: RemoteData<PaginatedList<QualityAssuranceTopicObject>>) => {
if (rd.hasSucceeded) { if (rd.hasSucceeded) {
return rd.payload; return rd.payload;

View File

@@ -30,6 +30,7 @@ import {
const MODULES = [ const MODULES = [
CommonModule, CommonModule,
SharedModule, SharedModule,
SearchModule,
CoreModule.forRoot(), CoreModule.forRoot(),
StoreModule.forFeature('suggestionNotifications', suggestionNotificationsReducers, storeModuleConfig as StoreConfig<SuggestionNotificationsState, Action>), StoreModule.forFeature('suggestionNotifications', suggestionNotificationsReducers, storeModuleConfig as StoreConfig<SuggestionNotificationsState, Action>),
EffectsModule.forFeature(suggestionNotificationsEffects), EffectsModule.forFeature(suggestionNotificationsEffects),
@@ -59,8 +60,7 @@ const PROVIDERS = [
@NgModule({ @NgModule({
imports: [ imports: [
...MODULES, ...MODULES
SearchModule
], ],
declarations: [ declarations: [
...COMPONENTS, ...COMPONENTS,