[CST-12109] qa-event notification box fixes

This commit is contained in:
Alisa Ismailati
2024-02-21 18:06:45 +01:00
parent 8d0971d9ff
commit 7350fdd125
11 changed files with 149 additions and 72 deletions

View File

@@ -56,6 +56,21 @@ import {
showBreadcrumbsFluid: false showBreadcrumbsFluid: false
} }
}, },
{
canActivate: [ AuthenticatedGuard ],
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/target/:targetId`,
component: AdminQualityAssuranceTopicsPageComponent,
pathMatch: 'full',
resolve: {
breadcrumb: I18nBreadcrumbResolver,
openaireQualityAssuranceTopicsParams: AdminQualityAssuranceTopicsPageResolver
},
data: {
title: 'admin.quality-assurance.page.title',
breadcrumbKey: 'admin.quality-assurance',
showBreadcrumbsFluid: false
}
},
{ {
canActivate: [ SiteAdministratorGuard ], canActivate: [ SiteAdministratorGuard ],
path: `${QUALITY_ASSURANCE_EDIT_PATH}`, path: `${QUALITY_ASSURANCE_EDIT_PATH}`,

View File

@@ -2,9 +2,21 @@
<ng-container *ngFor="let source of sources"> <ng-container *ngFor="let source of sources">
<div class="alert alert-info d-flex flex-row" *ngIf="source.totalEvents > 0"> <div class="alert alert-info d-flex flex-row" *ngIf="source.totalEvents > 0">
<div class="w-100 d-flex justify-content-between"> <div class="w-100 d-flex justify-content-between">
<div class="pl-4 align-self-center">{{ this.item.isArchived ? ('qa-event-notification.check.notification-withdrawn' | translate) <div class="pl-4 align-self-center">
: ('qa-event-notification.check.notification-reinstate' | translate) }} </div> <ng-container *ngIf="this.item.isArchived; else reinstate">
<button [routerLink]="[ getQualityAssuranceRoute(), (source.id | dsSplit: ':')[0]]" <span>
{{
(isAdmin$ | async) ? ('qa-event-notification.check.notification-withdrawn.admin' | translate : { source: (source.id | dsSplit: ':')[0], num: source.totalEvents })
: ('qa-event-notification.check.notification-withdrawn.user' | translate : { num: source.totalEvents })
}}
</span>
</ng-container>
<ng-template #reinstate>
{{ 'qa-event-notification.check.notification-reinstate' | translate: { num: source.totalEvents } }}
</ng-template>
</div>
<button [routerLink]="[ getQualityAssuranceRoute(), (source.id | dsSplit: ':')[0], 'target', item.id]"
[queryParams]="{ forward: true }"
class="btn btn-primary align-self-center">{{ this.item.isArchived ? ('qa-event-notification-undo-withdrawn.check.button' | translate) class="btn btn-primary align-self-center">{{ this.item.isArchived ? ('qa-event-notification-undo-withdrawn.check.button' | translate)
: ('qa-event-notification-undo-reinstate.check.button' | translate) }}</button> : ('qa-event-notification-undo-reinstate.check.button' | translate) }}</button>
</div> </div>

View File

@@ -1,35 +1,55 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { QaEventNotificationComponent } from './qa-event-notification.component'; import { QaEventNotificationComponent } from './qa-event-notification.component';
import { createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils'; import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { createPaginatedList } from 'src/app/shared/testing/utils.test'; import { createPaginatedList } from '../../../shared/testing/utils.test';
import { QualityAssuranceSourceObject } from 'src/app/core/notifications/qa/models/quality-assurance-source.model'; import { QualityAssuranceSourceObject } from '../../../core/notifications/qa/models/quality-assurance-source.model';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { QualityAssuranceSourceDataService } from 'src/app/core/notifications/qa/source/quality-assurance-source-data.service'; import { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service';
import { RequestService } from 'src/app/core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { NotificationsService } from 'src/app/shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { ObjectCacheService } from 'src/app/core/cache/object-cache.service'; import { ObjectCacheService } from '../../../core/cache/object-cache.service';
import { RemoteDataBuildService } from 'src/app/core/cache/builders/remote-data-build.service'; import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
import { provideMockStore } from '@ngrx/store/testing'; import { provideMockStore } from '@ngrx/store/testing';
import { HALEndpointService } from 'src/app/core/shared/hal-endpoint.service'; import { HALEndpointService } from '../../../core/shared/hal-endpoint.service';
import { HALEndpointServiceStub } from 'src/app/shared/testing/hal-endpoint-service.stub'; import { HALEndpointServiceStub } from '../../../shared/testing/hal-endpoint-service.stub';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { of } from 'rxjs';
import { By } from '@angular/platform-browser';
import { SplitPipe } from 'src/app/shared/utils/split.pipe';
describe('QaEventNotificationComponent', () => { describe('QaEventNotificationComponent', () => {
let component: QaEventNotificationComponent; let component: QaEventNotificationComponent;
let fixture: ComponentFixture<QaEventNotificationComponent>; let fixture: ComponentFixture<QaEventNotificationComponent>;
let qualityAssuranceSourceDataServiceStub: any; let qualityAssuranceSourceDataServiceStub: any;
let authorizationService: AuthorizationDataService;
const obj = createSuccessfulRemoteDataObject$(createPaginatedList([new QualityAssuranceSourceObject()])); const obj = Object.assign(new QualityAssuranceSourceObject(), {
id: 'sourceName:target',
source: 'sourceName',
target: 'target',
totalEvents: 1
});
const objPL = createSuccessfulRemoteDataObject$(createPaginatedList([obj]));
const item = Object.assign({ uuid: '1234' }); const item = Object.assign({ uuid: '1234' });
beforeEach(async () => { beforeEach(async () => {
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: of(true)
});
qualityAssuranceSourceDataServiceStub = {
getSourcesByTarget: () => objPL
};
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [CommonModule, TranslateModule.forRoot()], imports: [CommonModule, TranslateModule.forRoot()],
declarations: [ QaEventNotificationComponent ], declarations: [QaEventNotificationComponent, SplitPipe],
providers: [ providers: [
{ provide: QualityAssuranceSourceDataService, useValue: qualityAssuranceSourceDataServiceStub }, { provide: QualityAssuranceSourceDataService, useValue: qualityAssuranceSourceDataServiceStub },
{ provide: RequestService, useValue: {} }, { provide: RequestService, useValue: {} },
{ provide: NotificationsService, useValue: {} }, { provide: NotificationsService, useValue: {} },
{ provide: HALEndpointService, useValue: new HALEndpointServiceStub('test')}, { provide: HALEndpointService, useValue: new HALEndpointServiceStub('test') },
{ provide: AuthorizationDataService, useValue: authorizationService },
ObjectCacheService, ObjectCacheService,
RemoteDataBuildService, RemoteDataBuildService,
provideMockStore({}) provideMockStore({})
@@ -39,9 +59,21 @@ describe('QaEventNotificationComponent', () => {
fixture = TestBed.createComponent(QaEventNotificationComponent); fixture = TestBed.createComponent(QaEventNotificationComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.item = item; component.item = item;
component.sources$ = of([obj]);
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should display sources if present', () => {
const alertElements = fixture.debugElement.queryAll(By.css('.alert'));
expect(alertElements.length).toBe(1);
});
it('should return the quality assurance route when getQualityAssuranceRoute is called', () => {
const route = component.getQualityAssuranceRoute();
expect(route).toBe('/notifications/quality-assurance');
});
}); });

View File

@@ -6,10 +6,12 @@ import { FindListOptions } from '../../../core/data/find-list-options.model';
import { RequestParam } from '../../../core/cache/models/request-param.model'; import { RequestParam } from '../../../core/cache/models/request-param.model';
import { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service'; import { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service';
import { QualityAssuranceSourceObject } from '../../../core/notifications/qa/models/quality-assurance-source.model'; import { QualityAssuranceSourceObject } from '../../../core/notifications/qa/models/quality-assurance-source.model';
import { map } from 'rxjs/operators'; import { catchError, map } from 'rxjs/operators';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { getNotificatioQualityAssuranceRoute } from '../../../admin/admin-routing-paths'; import { getNotificatioQualityAssuranceRoute } from '../../../admin/admin-routing-paths';
import { PaginatedList } from 'src/app/core/data/paginated-list.model'; import { PaginatedList } from 'src/app/core/data/paginated-list.model';
import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service';
import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id';
@Component({ @Component({
selector: 'ds-qa-event-notification', selector: 'ds-qa-event-notification',
@@ -32,11 +34,16 @@ export class QaEventNotificationComponent implements OnChanges {
*/ */
sources$: Observable<QualityAssuranceSourceObject[]>; sources$: Observable<QualityAssuranceSourceObject[]>;
/** /**
* The type of alert to display for the notification. * An observable that emits a boolean representing whether the current user is an admin.
*/ */
isAdmin$: Observable<boolean>;
constructor( constructor(
private qualityAssuranceSourceDataService: QualityAssuranceSourceDataService, private qualityAssuranceSourceDataService: QualityAssuranceSourceDataService,
) { } private authService: AuthorizationDataService,
) {
this.isAdmin$ = this.authService.isAuthorized(FeatureID.AdministratorOf);
}
/** /**
* Detect changes to the item input and update the sources$ observable. * Detect changes to the item input and update the sources$ observable.
@@ -63,7 +70,8 @@ export class QaEventNotificationComponent implements OnChanges {
return data.payload.page; return data.payload.page;
} }
return []; return [];
}) }),
catchError(() => [])
); );
} }

View File

@@ -196,14 +196,6 @@
</ds-pagination> </ds-pagination>
</div> </div>
</div> </div>
<div class="row text-right">
<div class="col-md-12">
<a class="btn btn-outline-secondary" [routerLink]="['/notifications/quality-assurance', sourceId]">
<i class="fas fa-angle-double-left"></i>
{{'quality-assurance.events.back' | translate}}
</a>
</div>
</div>
</div> </div>
<ng-template #acceptModal let-modal> <ng-template #acceptModal let-modal>

View File

@@ -33,7 +33,7 @@ import { FindListOptions } from '../../../core/data/find-list-options.model';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { NoContent } from '../../../core/shared/NoContent.model'; import { NoContent } from '../../../core/shared/NoContent.model';
import {environment} from '../../../../environments/environment'; import { environment } from '../../../../environments/environment';
/** /**
* Component to display the Quality Assurance event list. * Component to display the Quality Assurance event list.
@@ -119,6 +119,9 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
*/ */
protected subs: Subscription[] = []; protected subs: Subscription[] = [];
/**
* Observable that emits a boolean value indicating whether the user is an admin.
*/
isAdmin$: Observable<boolean>; isAdmin$: Observable<boolean>;
/** /**
@@ -146,9 +149,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
*/ */
ngOnInit(): void { ngOnInit(): void {
this.isEventPageLoading.next(true); this.isEventPageLoading.next(true);
this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf); this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf);
// this.sourceId = this.activatedRoute.snapshot.params.sourceId;
this.activatedRoute.paramMap.pipe( this.activatedRoute.paramMap.pipe(
tap((params) => { tap((params) => {
this.sourceUrlForProjectSearch = environment.qualityAssuranceConfig.sourceUrlMapForProjectSearch[params.get('sourceId')]; this.sourceUrlForProjectSearch = environment.qualityAssuranceConfig.sourceUrlMapForProjectSearch[params.get('sourceId')];
@@ -462,6 +463,11 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
); );
} }
/**
* Deletes a quality assurance event.
* @param qaEvent The quality assurance event to delete.
* @returns An Observable of RemoteData containing NoContent.
*/
delete(qaEvent: QualityAssuranceEventData): Observable<RemoteData<NoContent>> { delete(qaEvent: QualityAssuranceEventData): Observable<RemoteData<NoContent>> {
return this.qualityAssuranceEventRestService.deleteQAEvent(qaEvent); return this.qualityAssuranceEventRestService.deleteQAEvent(qaEvent);
} }

View File

@@ -44,7 +44,7 @@
<button <button
class="btn btn-outline-primary btn-sm" class="btn btn-outline-primary btn-sm"
title="{{'quality-assurance.topics-list.button.detail' | translate : { param: topicElement.name } }}" title="{{'quality-assurance.topics-list.button.detail' | translate : { param: topicElement.name } }}"
[routerLink]="[topicElement.id]"> [routerLink]="[getQualityAssuranceRoute(), sourceId, topicElement.id]">
<span class="badge badge-info">{{topicElement.totalEvents}}</span> <span class="badge badge-info">{{topicElement.totalEvents}}</span>
<i class="fas fa-info fa-fw"></i> <i class="fas fa-info fa-fw"></i>
</button> </button>
@@ -58,12 +58,4 @@
</ds-pagination> </ds-pagination>
</div> </div>
</div> </div>
<div class="row text-right">
<div class="col-md-12">
<a class="btn btn-outline-secondary" [routerLink]="['/notifications/quality-assurance']">
<i class="fas fa-angle-double-left"></i>
{{'quality-assurance.events.back-to-sources' | translate}}
</a>
</div>
</div>
</div> </div>

View File

@@ -14,7 +14,7 @@ import {
AdminQualityAssuranceTopicsPageParams AdminQualityAssuranceTopicsPageParams
} from '../../../admin/admin-notifications/admin-quality-assurance-topics-page/admin-quality-assurance-topics-page-resolver.service'; } 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, Router } from '@angular/router';
import { ItemDataService } from '../../../core/data/item-data.service'; import { ItemDataService } from '../../../core/data/item-data.service';
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../core/shared/operators'; import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../core/shared/operators';
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
@@ -87,6 +87,7 @@ export class QualityAssuranceTopicsComponent implements OnInit, OnDestroy, After
private activatedRoute: ActivatedRoute, private activatedRoute: ActivatedRoute,
private itemService: ItemDataService, private itemService: ItemDataService,
private notificationsStateService: NotificationsStateService, private notificationsStateService: NotificationsStateService,
private router: Router,
) { ) {
this.sourceId = this.activatedRoute.snapshot.params.sourceId; this.sourceId = this.activatedRoute.snapshot.params.sourceId;
this.targetId = this.activatedRoute.snapshot.params.targetId; this.targetId = this.activatedRoute.snapshot.params.targetId;
@@ -96,7 +97,15 @@ export class QualityAssuranceTopicsComponent implements OnInit, OnDestroy, After
* Component initialization. * Component initialization.
*/ */
ngOnInit(): void { ngOnInit(): void {
this.topics$ = this.notificationsStateService.getQualityAssuranceTopics(); this.topics$ = this.notificationsStateService.getQualityAssuranceTopics().pipe(
tap((topics: QualityAssuranceTopicObject[]) => {
const forward = this.activatedRoute.snapshot.queryParams?.forward === 'true';
if (topics.length === 1 && forward) {
// If there is only one topic, navigate to the first topic automatically
this.router.navigate([this.getQualityAssuranceRoute(), this.sourceId, topics[0].id]);
}
})
);
this.totalElements$ = this.notificationsStateService.getQualityAssuranceTopicsTotals(); this.totalElements$ = this.notificationsStateService.getQualityAssuranceTopicsTotals();
} }

View File

@@ -1,4 +1,10 @@
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
/**
* Custom pipe to split a string into an array of substrings based on a specified separator.
* @param value - The string to be split.
* @param separator - The separator used to split the string.
* @returns An array of substrings.
*/
@Pipe({ @Pipe({
name: 'dsSplit' name: 'dsSplit'
}) })

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { of, forkJoin, Observable } from 'rxjs'; import { of, forkJoin, Observable } from 'rxjs';
import { catchError, map, mergeMap, take } from 'rxjs/operators'; import { catchError, map, mergeMap, 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 { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
@@ -12,8 +12,9 @@ import { ResearcherProfile } from '../core/profile/model/researcher-profile.mode
import { import {
getAllSucceededRemoteDataPayload, getAllSucceededRemoteDataPayload,
getFinishedRemoteData, getFinishedRemoteData,
getFirstCompletedRemoteData,
getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteDataPayload,
getFirstSucceededRemoteListPayload getFirstSucceededRemoteListPayload,
} from '../core/shared/operators'; } from '../core/shared/operators';
import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model'; import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model';
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
@@ -155,10 +156,10 @@ export class SuggestionsService {
*/ */
public retrieveCurrentUserSuggestions(userUuid: string): Observable<SuggestionTarget[]> { public retrieveCurrentUserSuggestions(userUuid: string): Observable<SuggestionTarget[]> {
return this.researcherProfileService.findById(userUuid, true).pipe( return this.researcherProfileService.findById(userUuid, true).pipe(
getFirstSucceededRemoteDataPayload(), getFirstCompletedRemoteData(),
mergeMap((profile: ResearcherProfile) => { mergeMap((profile: RemoteData<ResearcherProfile> ) => {
if (isNotEmpty(profile)) { if (isNotEmpty(profile) && profile.hasSucceeded && isNotEmpty(profile.payload)) {
return this.researcherProfileService.findRelatedItemId(profile).pipe( return this.researcherProfileService.findRelatedItemId(profile.payload).pipe(
mergeMap((itemId: string) => { mergeMap((itemId: string) => {
return this.suggestionsDataService.getTargetsByUser(itemId).pipe( return this.suggestionsDataService.getTargetsByUser(itemId).pipe(
getFirstSucceededRemoteListPayload() getFirstSucceededRemoteListPayload()
@@ -169,7 +170,7 @@ export class SuggestionsService {
return of([]); return of([]);
} }
}), }),
take(1) catchError(() => of([]))
); );
} }

View File

@@ -2708,13 +2708,15 @@
"correction-type.manage-relation.action.notification.withdrawn": "Withdraw request sent.", "correction-type.manage-relation.action.notification.withdrawn": "Withdraw request sent.",
"qa-event-notification.check.notification-withdrawn": "You have requested to withdraw this item.", "qa-event-notification.check.notification-withdrawn.user": "There are {{num}} items pending review(s) to check.",
"qa-event-notification.check.notification-reinstate": "You have requested to reinstate this item.", "qa-event-notification.check.notification-withdrawn.admin": "There are {{num}} {{source}} feedback(s) pending.",
"qa-event-notification-undo-withdrawn.check.button": "Undo request withdrawal", "qa-event-notification.check.notification-reinstate": "There are {{num}} pending reinstatement(s) pending for this item.",
"qa-event-notification-undo-reinstate.check.button": "Undo request reinstatment", "qa-event-notification-undo-withdrawn.check.button": "Check",
"qa-event-notification-undo-reinstate.check.button": "Check",
"item.version.create.modal.submitted.text": "The new version is being created. This may take some time if the item has a lot of relationships.", "item.version.create.modal.submitted.text": "The new version is being created. This may take some time if the item has a lot of relationships.",
@@ -3246,6 +3248,8 @@
"quality-assurance.topics.description": "Below you can see all the topics received from the subscriptions to {{source}}.", "quality-assurance.topics.description": "Below you can see all the topics received from the subscriptions to {{source}}.",
"quality-assurance.topics.description-with-target": "Below you can see all the topics received from the subscriptions to {{source}} in regards to the",
"quality-assurance.source.description": "Below you can see all the notification's sources.", "quality-assurance.source.description": "Below you can see all the notification's sources.",
"quality-assurance.topics": "Current Topics", "quality-assurance.topics": "Current Topics",