diff --git a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts index 761c819b1b..e00a88cbe2 100644 --- a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts +++ b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts @@ -56,6 +56,21 @@ import { 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 ], path: `${QUALITY_ASSURANCE_EDIT_PATH}`, diff --git a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.html b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.html index e38ecd8239..169fe00950 100644 --- a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.html +++ b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.html @@ -2,9 +2,21 @@
-
{{ this.item.isArchived ? ('qa-event-notification.check.notification-withdrawn' | translate) - : ('qa-event-notification.check.notification-reinstate' | translate) }}
-
+
diff --git a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.spec.ts b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.spec.ts index 2a1ccb28ae..a9d16440a8 100644 --- a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.spec.ts +++ b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.spec.ts @@ -1,47 +1,79 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { QaEventNotificationComponent } from './qa-event-notification.component'; -import { createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils'; -import { createPaginatedList } from 'src/app/shared/testing/utils.test'; -import { QualityAssuranceSourceObject } from 'src/app/core/notifications/qa/models/quality-assurance-source.model'; +import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { createPaginatedList } from '../../../shared/testing/utils.test'; +import { QualityAssuranceSourceObject } from '../../../core/notifications/qa/models/quality-assurance-source.model'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; -import { QualityAssuranceSourceDataService } from 'src/app/core/notifications/qa/source/quality-assurance-source-data.service'; -import { RequestService } from 'src/app/core/data/request.service'; -import { NotificationsService } from 'src/app/shared/notifications/notifications.service'; -import { ObjectCacheService } from 'src/app/core/cache/object-cache.service'; -import { RemoteDataBuildService } from 'src/app/core/cache/builders/remote-data-build.service'; +import { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service'; +import { RequestService } from '../../../core/data/request.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { ObjectCacheService } from '../../../core/cache/object-cache.service'; +import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service'; import { provideMockStore } from '@ngrx/store/testing'; -import { HALEndpointService } from 'src/app/core/shared/hal-endpoint.service'; -import { HALEndpointServiceStub } from 'src/app/shared/testing/hal-endpoint-service.stub'; +import { HALEndpointService } from '../../../core/shared/hal-endpoint.service'; +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', () => { let component: QaEventNotificationComponent; let fixture: ComponentFixture; 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' }); beforeEach(async () => { + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: of(true) + }); + + qualityAssuranceSourceDataServiceStub = { + getSourcesByTarget: () => objPL + }; await TestBed.configureTestingModule({ imports: [CommonModule, TranslateModule.forRoot()], - declarations: [ QaEventNotificationComponent ], + declarations: [QaEventNotificationComponent, SplitPipe], providers: [ - { provide: QualityAssuranceSourceDataService, useValue: qualityAssuranceSourceDataServiceStub }, - { provide: RequestService, useValue: {} }, - { provide: NotificationsService, useValue: {} }, - { provide: HALEndpointService, useValue: new HALEndpointServiceStub('test')}, - ObjectCacheService, - RemoteDataBuildService, - provideMockStore({}) - ], + { provide: QualityAssuranceSourceDataService, useValue: qualityAssuranceSourceDataServiceStub }, + { provide: RequestService, useValue: {} }, + { provide: NotificationsService, useValue: {} }, + { provide: HALEndpointService, useValue: new HALEndpointServiceStub('test') }, + { provide: AuthorizationDataService, useValue: authorizationService }, + ObjectCacheService, + RemoteDataBuildService, + provideMockStore({}) + ], }) .compileComponents(); fixture = TestBed.createComponent(QaEventNotificationComponent); component = fixture.componentInstance; component.item = item; + component.sources$ = of([obj]); fixture.detectChanges(); }); + it('should create', () => { 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'); + }); }); diff --git a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.ts b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.ts index e5f509c405..6f8e55c9d2 100644 --- a/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.ts +++ b/src/app/item-page/simple/qa-event-notification/qa-event-notification.component.ts @@ -6,10 +6,12 @@ import { FindListOptions } from '../../../core/data/find-list-options.model'; import { RequestParam } from '../../../core/cache/models/request-param.model'; import { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service'; 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 { getNotificatioQualityAssuranceRoute } from '../../../admin/admin-routing-paths'; 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({ selector: 'ds-qa-event-notification', @@ -32,11 +34,16 @@ export class QaEventNotificationComponent implements OnChanges { */ sources$: Observable; /** - * 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; + constructor( private qualityAssuranceSourceDataService: QualityAssuranceSourceDataService, - ) { } + private authService: AuthorizationDataService, + ) { + this.isAdmin$ = this.authService.isAuthorized(FeatureID.AdministratorOf); + } /** * 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 []; - }) + }), + catchError(() => []) ); } diff --git a/src/app/notifications/qa/events/quality-assurance-events.component.html b/src/app/notifications/qa/events/quality-assurance-events.component.html index 9d9cd2b58c..286552a00c 100644 --- a/src/app/notifications/qa/events/quality-assurance-events.component.html +++ b/src/app/notifications/qa/events/quality-assurance-events.component.html @@ -196,14 +196,6 @@ - diff --git a/src/app/notifications/qa/events/quality-assurance-events.component.ts b/src/app/notifications/qa/events/quality-assurance-events.component.ts index 7dcccae2de..60550a8baf 100644 --- a/src/app/notifications/qa/events/quality-assurance-events.component.ts +++ b/src/app/notifications/qa/events/quality-assurance-events.component.ts @@ -33,7 +33,7 @@ import { FindListOptions } from '../../../core/data/find-list-options.model'; import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../../core/data/feature-authorization/feature-id'; 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. @@ -119,6 +119,9 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy { */ protected subs: Subscription[] = []; + /** + * Observable that emits a boolean value indicating whether the user is an admin. + */ isAdmin$: Observable; /** @@ -146,15 +149,13 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy { */ ngOnInit(): void { this.isEventPageLoading.next(true); - this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf); - // this.sourceId = this.activatedRoute.snapshot.params.sourceId; this.activatedRoute.paramMap.pipe( - tap((params) => { - this.sourceUrlForProjectSearch = environment.qualityAssuranceConfig.sourceUrlMapForProjectSearch[params.get('sourceId')]; - this.sourceId = params.get('sourceId'); - }), - map((params) => params.get('topicId')), + tap((params) => { + this.sourceUrlForProjectSearch = environment.qualityAssuranceConfig.sourceUrlMapForProjectSearch[params.get('sourceId')]; + this.sourceId = params.get('sourceId'); + }), + map((params) => params.get('topicId')), take(1), switchMap((id: string) => { const regEx = /!/g; @@ -180,11 +181,11 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy { */ public hasDetailColumn(): boolean { return (this.showTopic.indexOf('/PROJECT') !== -1 || - this.showTopic.indexOf('/PID') !== -1 || - this.showTopic.indexOf('/SUBJECT') !== -1 || - this.showTopic.indexOf('/WITHDRAWN') !== -1 || - this.showTopic.indexOf('/REINSTATE') !== -1 || - this.showTopic.indexOf('/ABSTRACT') !== -1 + this.showTopic.indexOf('/PID') !== -1 || + this.showTopic.indexOf('/SUBJECT') !== -1 || + this.showTopic.indexOf('/WITHDRAWN') !== -1 || + this.showTopic.indexOf('/REINSTATE') !== -1 || + this.showTopic.indexOf('/ABSTRACT') !== -1 ); } @@ -270,9 +271,9 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy { eventData.isRunning = true; let operation; if (action === 'UNDO') { - operation = this.delete(eventData); + operation = this.delete(eventData); } else { - operation = this.qualityAssuranceEventRestService.patchEvent(action, eventData.event, eventData.reason); + operation = this.qualityAssuranceEventRestService.patchEvent(action, eventData.event, eventData.reason); } this.subs.push( operation.pipe( @@ -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> { return this.qualityAssuranceEventRestService.deleteQAEvent(qaEvent); } diff --git a/src/app/notifications/qa/topics/quality-assurance-topics.component.html b/src/app/notifications/qa/topics/quality-assurance-topics.component.html index e2148a611e..631296ea1c 100644 --- a/src/app/notifications/qa/topics/quality-assurance-topics.component.html +++ b/src/app/notifications/qa/topics/quality-assurance-topics.component.html @@ -44,7 +44,7 @@ @@ -58,12 +58,4 @@ - diff --git a/src/app/notifications/qa/topics/quality-assurance-topics.component.ts b/src/app/notifications/qa/topics/quality-assurance-topics.component.ts index 96a613d26c..bda45c71c8 100644 --- a/src/app/notifications/qa/topics/quality-assurance-topics.component.ts +++ b/src/app/notifications/qa/topics/quality-assurance-topics.component.ts @@ -14,7 +14,7 @@ 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 { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { ItemDataService } from '../../../core/data/item-data.service'; import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../core/shared/operators'; import { Item } from '../../../core/shared/item.model'; @@ -87,6 +87,7 @@ export class QualityAssuranceTopicsComponent implements OnInit, OnDestroy, After private activatedRoute: ActivatedRoute, private itemService: ItemDataService, private notificationsStateService: NotificationsStateService, + private router: Router, ) { this.sourceId = this.activatedRoute.snapshot.params.sourceId; this.targetId = this.activatedRoute.snapshot.params.targetId; @@ -96,7 +97,15 @@ export class QualityAssuranceTopicsComponent implements OnInit, OnDestroy, After * Component initialization. */ 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(); } diff --git a/src/app/shared/utils/split.pipe.ts b/src/app/shared/utils/split.pipe.ts index cccd285922..e4d0f2cc49 100644 --- a/src/app/shared/utils/split.pipe.ts +++ b/src/app/shared/utils/split.pipe.ts @@ -1,4 +1,10 @@ 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({ name: 'dsSplit' }) diff --git a/src/app/suggestion-notifications/suggestions.service.ts b/src/app/suggestion-notifications/suggestions.service.ts index 874659eab5..8a99403586 100644 --- a/src/app/suggestion-notifications/suggestions.service.ts +++ b/src/app/suggestion-notifications/suggestions.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; 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 { RemoteData } from '../core/data/remote-data'; @@ -12,8 +12,9 @@ import { ResearcherProfile } from '../core/profile/model/researcher-profile.mode import { getAllSucceededRemoteDataPayload, getFinishedRemoteData, + getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload, - getFirstSucceededRemoteListPayload + getFirstSucceededRemoteListPayload, } from '../core/shared/operators'; import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model'; import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; @@ -155,10 +156,10 @@ export class SuggestionsService { */ public retrieveCurrentUserSuggestions(userUuid: string): Observable { return this.researcherProfileService.findById(userUuid, true).pipe( - getFirstSucceededRemoteDataPayload(), - mergeMap((profile: ResearcherProfile) => { - if (isNotEmpty(profile)) { - return this.researcherProfileService.findRelatedItemId(profile).pipe( + getFirstCompletedRemoteData(), + mergeMap((profile: RemoteData ) => { + if (isNotEmpty(profile) && profile.hasSucceeded && isNotEmpty(profile.payload)) { + return this.researcherProfileService.findRelatedItemId(profile.payload).pipe( mergeMap((itemId: string) => { return this.suggestionsDataService.getTargetsByUser(itemId).pipe( getFirstSucceededRemoteListPayload() @@ -169,7 +170,7 @@ export class SuggestionsService { return of([]); } }), - take(1) + catchError(() => of([])) ); } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 27e0fee744..27cc62033f 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2708,13 +2708,15 @@ "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.", @@ -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-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.topics": "Current Topics",