mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'main' into coar-notify-7
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||
import { getNotificationsModuleRoute } from '../admin-routing-paths';
|
||||
|
||||
export const QUALITY_ASSURANCE_EDIT_PATH = 'quality-assurance';
|
||||
export const PUBLICATION_CLAIMS_PATH = 'publication-claim';
|
||||
|
||||
export function getQualityAssuranceRoute(id: string) {
|
||||
return new URLCombiner(getNotificationsModuleRoute(), QUALITY_ASSURANCE_EDIT_PATH, id).toString();
|
||||
export function getQualityAssuranceEditRoute() {
|
||||
return `/${QUALITY_ASSURANCE_EDIT_PATH}`;
|
||||
}
|
||||
|
@@ -6,34 +6,22 @@ import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.r
|
||||
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||
import { PUBLICATION_CLAIMS_PATH } from './admin-notifications-routing-paths';
|
||||
import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component';
|
||||
import { AdminNotificationsPublicationClaimPageResolver } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service';
|
||||
import { QUALITY_ASSURANCE_EDIT_PATH } from './admin-notifications-routing-paths';
|
||||
import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component';
|
||||
import { AdminQualityAssuranceEventsPageComponent } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.component';
|
||||
import { AdminQualityAssuranceTopicsPageResolver } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page-resolver.service';
|
||||
import { AdminQualityAssuranceEventsPageResolver } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.resolver';
|
||||
import { AdminQualityAssuranceSourcePageComponent } from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-page.component';
|
||||
import { AdminQualityAssuranceSourcePageResolver } from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-page-resolver.service';
|
||||
import {
|
||||
SiteAdministratorGuard
|
||||
} from '../../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
import { QualityAssuranceBreadcrumbResolver } from '../../core/breadcrumbs/quality-assurance-breadcrumb.resolver';
|
||||
import { QualityAssuranceBreadcrumbService } from '../../core/breadcrumbs/quality-assurance-breadcrumb.service';
|
||||
import {
|
||||
AdminNotificationsPublicationClaimPageResolver
|
||||
} from '../../quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service';
|
||||
import {
|
||||
QualityAssuranceTopicsPageComponent
|
||||
} from '../../quality-assurance-notifications-pages/quality-assurance-topics-page/quality-assurance-topics-page.component';
|
||||
import {
|
||||
QualityAssuranceTopicsPageResolver
|
||||
} from '../../quality-assurance-notifications-pages/quality-assurance-topics-page/quality-assurance-topics-page-resolver.service';
|
||||
import {
|
||||
SourceDataResolver
|
||||
} from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-data.resolver';
|
||||
import {
|
||||
QualityAssuranceEventsPageResolver
|
||||
} from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.resolver';
|
||||
import {
|
||||
QualityAssuranceEventsPageComponent
|
||||
} from '../../quality-assurance-notifications-pages/quality-assurance-events-page/quality-assurance-events-page.component';
|
||||
import {
|
||||
QualityAssuranceSourcePageResolver
|
||||
} from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-page-resolver.service';
|
||||
import {
|
||||
QualityAssuranceSourcePageComponent
|
||||
} from '../../quality-assurance-notifications-pages/quality-assurance-source-page-component/quality-assurance-source-page.component';
|
||||
|
||||
} from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-data.resolver';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -56,11 +44,11 @@ import {
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId`,
|
||||
component: QualityAssuranceTopicsPageComponent,
|
||||
component: AdminQualityAssuranceTopicsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: QualityAssuranceBreadcrumbResolver,
|
||||
openaireQualityAssuranceTopicsParams: QualityAssuranceTopicsPageResolver
|
||||
openaireQualityAssuranceTopicsParams: AdminQualityAssuranceTopicsPageResolver
|
||||
},
|
||||
data: {
|
||||
title: 'admin.quality-assurance.page.title',
|
||||
@@ -70,12 +58,27 @@ import {
|
||||
},
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}`,
|
||||
component: QualityAssuranceSourcePageComponent,
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/target/:targetId`,
|
||||
component: AdminQualityAssuranceTopicsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
openaireQualityAssuranceSourceParams: QualityAssuranceSourcePageResolver,
|
||||
openaireQualityAssuranceTopicsParams: AdminQualityAssuranceTopicsPageResolver
|
||||
},
|
||||
data: {
|
||||
title: 'admin.quality-assurance.page.title',
|
||||
breadcrumbKey: 'admin.quality-assurance',
|
||||
showBreadcrumbsFluid: false
|
||||
}
|
||||
},
|
||||
{
|
||||
canActivate: [ SiteAdministratorGuard ],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}`,
|
||||
component: AdminQualityAssuranceSourcePageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
openaireQualityAssuranceSourceParams: AdminQualityAssuranceSourcePageResolver,
|
||||
sourceData: SourceDataResolver
|
||||
},
|
||||
data: {
|
||||
@@ -87,11 +90,11 @@ import {
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/:topicId`,
|
||||
component: QualityAssuranceEventsPageComponent,
|
||||
component: AdminQualityAssuranceEventsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: QualityAssuranceBreadcrumbResolver,
|
||||
openaireQualityAssuranceEventsParams: QualityAssuranceEventsPageResolver
|
||||
openaireQualityAssuranceEventsParams: AdminQualityAssuranceEventsPageResolver
|
||||
},
|
||||
data: {
|
||||
title: 'admin.notifications.event.page.title',
|
||||
@@ -106,10 +109,10 @@ import {
|
||||
I18nBreadcrumbsService,
|
||||
AdminNotificationsPublicationClaimPageResolver,
|
||||
SourceDataResolver,
|
||||
QualityAssuranceSourcePageResolver,
|
||||
QualityAssuranceTopicsPageResolver,
|
||||
QualityAssuranceEventsPageResolver,
|
||||
QualityAssuranceSourcePageResolver,
|
||||
AdminQualityAssuranceSourcePageResolver,
|
||||
AdminQualityAssuranceTopicsPageResolver,
|
||||
AdminQualityAssuranceEventsPageResolver,
|
||||
AdminQualityAssuranceSourcePageResolver,
|
||||
QualityAssuranceBreadcrumbResolver,
|
||||
QualityAssuranceBreadcrumbService
|
||||
]
|
||||
|
@@ -1,25 +1,18 @@
|
||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||
import { getAdminModuleRoute } from '../app-routing-paths';
|
||||
import { getQualityAssuranceEditRoute } from '../quality-assurance-notifications-pages/notifications-pages-routing-paths';
|
||||
import { getQualityAssuranceEditRoute } from './admin-notifications/admin-notifications-routing-paths';
|
||||
|
||||
export const REGISTRIES_MODULE_PATH = 'registries';
|
||||
export const NOTIFICATIONS_MODULE_PATH = 'notifications';
|
||||
|
||||
export const LDN_PATH = 'ldn';
|
||||
|
||||
export function getRegistriesModuleRoute() {
|
||||
return new URLCombiner(getAdminModuleRoute(), REGISTRIES_MODULE_PATH).toString();
|
||||
}
|
||||
|
||||
export function getLdnServicesModuleRoute() {
|
||||
return new URLCombiner(getAdminModuleRoute(), LDN_PATH).toString();
|
||||
export function getNotificationsModuleRoute() {
|
||||
return new URLCombiner(getAdminModuleRoute(), NOTIFICATIONS_MODULE_PATH).toString();
|
||||
}
|
||||
|
||||
export function getNotificatioQualityAssuranceRoute() {
|
||||
return new URLCombiner(`/${NOTIFICATIONS_MODULE_PATH}`, getQualityAssuranceEditRoute()).toString();
|
||||
}
|
||||
|
||||
|
||||
export function getNotificationsModuleRoute() {
|
||||
return new URLCombiner(getAdminModuleRoute(), NOTIFICATIONS_MODULE_PATH).toString();
|
||||
}
|
||||
|
@@ -6,72 +6,64 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
|
||||
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
|
||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
|
||||
import { LDN_PATH, NOTIFICATIONS_MODULE_PATH, REGISTRIES_MODULE_PATH } from './admin-routing-paths';
|
||||
import { REGISTRIES_MODULE_PATH } from './admin-routing-paths';
|
||||
import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component';
|
||||
import { NotifyInfoGuard } from '../core/coar-notify/notify-info/notify-info.guard';
|
||||
import {
|
||||
SiteAdministratorGuard
|
||||
} from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: NOTIFICATIONS_MODULE_PATH,
|
||||
loadChildren: () => import('./admin-notifications/admin-notifications.module')
|
||||
.then((m) => m.AdminNotificationsModule),
|
||||
},
|
||||
{
|
||||
path: REGISTRIES_MODULE_PATH,
|
||||
loadChildren: () => import('./admin-registries/admin-registries.module')
|
||||
.then((m) => m.AdminRegistriesModule),
|
||||
canActivate: [SiteAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
component: AdminSearchPageComponent,
|
||||
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' }
|
||||
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' },
|
||||
canActivate: [SiteAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: 'workflow',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
component: AdminWorkflowPageComponent,
|
||||
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' }
|
||||
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' },
|
||||
canActivate: [SiteAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: 'curation-tasks',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
component: AdminCurationTasksComponent,
|
||||
data: { title: 'admin.curation-tasks.title', breadcrumbKey: 'admin.curation-tasks' }
|
||||
data: { title: 'admin.curation-tasks.title', breadcrumbKey: 'admin.curation-tasks' },
|
||||
canActivate: [SiteAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: 'metadata-import',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
component: MetadataImportPageComponent,
|
||||
data: { title: 'admin.metadata-import.title', breadcrumbKey: 'admin.metadata-import' }
|
||||
data: { title: 'admin.metadata-import.title', breadcrumbKey: 'admin.metadata-import' },
|
||||
canActivate: [SiteAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: 'batch-import',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
component: BatchImportPageComponent,
|
||||
data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' }
|
||||
},
|
||||
{
|
||||
path: LDN_PATH,
|
||||
children: [
|
||||
{ path: '', pathMatch: 'full', redirectTo: 'services' },
|
||||
{
|
||||
path: 'services',
|
||||
loadChildren: () => import('./admin-ldn-services/admin-ldn-services.module')
|
||||
.then((m) => m.AdminLdnServicesModule),
|
||||
}
|
||||
],
|
||||
canActivate: [NotifyInfoGuard]
|
||||
data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' },
|
||||
canActivate: [SiteAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: 'system-wide-alert',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||
loadChildren: () => import('../system-wide-alert/system-wide-alert.module').then((m) => m.SystemWideAlertModule),
|
||||
data: {title: 'admin.system-wide-alert.title', breadcrumbKey: 'admin.system-wide-alert'}
|
||||
data: {title: 'admin.system-wide-alert.title', breadcrumbKey: 'admin.system-wide-alert'},
|
||||
canActivate: [SiteAdministratorGuard]
|
||||
},
|
||||
]),
|
||||
])
|
||||
],
|
||||
providers: [
|
||||
I18nBreadcrumbResolver,
|
||||
|
@@ -133,3 +133,10 @@ export const SUBSCRIPTIONS_MODULE_PATH = 'subscriptions';
|
||||
export function getSubscriptionsModuleRoute() {
|
||||
return `/${SUBSCRIPTIONS_MODULE_PATH}`;
|
||||
}
|
||||
|
||||
export const EDIT_ITEM_PATH = 'edit-items';
|
||||
export function getEditItemPageRoute() {
|
||||
return `/${EDIT_ITEM_PATH}`;
|
||||
}
|
||||
export const CORRECTION_TYPE_PATH = 'corrections';
|
||||
|
||||
|
@@ -3,9 +3,6 @@ import { RouterModule, NoPreloading } from '@angular/router';
|
||||
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||
|
||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||
import {
|
||||
SiteAdministratorGuard
|
||||
} from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
import {
|
||||
ACCESS_CONTROL_MODULE_PATH,
|
||||
ADMIN_MODULE_PATH,
|
||||
@@ -157,7 +154,13 @@ import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-c
|
||||
path: ADMIN_MODULE_PATH,
|
||||
loadChildren: () => import('./admin/admin.module')
|
||||
.then((m) => m.AdminModule),
|
||||
canActivate: [SiteAdministratorGuard, EndUserAgreementCurrentUserGuard]
|
||||
canActivate: [EndUserAgreementCurrentUserGuard]
|
||||
},
|
||||
{
|
||||
path: NOTIFICATIONS_MODULE_PATH,
|
||||
loadChildren: () => import('./admin/admin-notifications/admin-notifications.module')
|
||||
.then((m) => m.AdminNotificationsModule),
|
||||
canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard]
|
||||
},
|
||||
{
|
||||
path: NOTIFICATIONS_MODULE_PATH,
|
||||
@@ -251,7 +254,7 @@ import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-c
|
||||
.then((m) => m.SubscriptionsPageRoutingModule),
|
||||
canActivate: [AuthenticatedGuard]
|
||||
},
|
||||
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
|
||||
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent }
|
||||
]
|
||||
}
|
||||
], {
|
||||
|
@@ -2,7 +2,10 @@ import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
|
||||
import { BreadcrumbsProviderService } from './breadcrumbsProviderService';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { QualityAssuranceTopicDataService } from '../notifications/qa/topics/quality-assurance-topic-data.service';
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +19,7 @@ export class QualityAssuranceBreadcrumbService implements BreadcrumbsProviderSer
|
||||
|
||||
private QUALITY_ASSURANCE_BREADCRUMB_KEY = 'admin.quality-assurance.breadcrumbs';
|
||||
constructor(
|
||||
protected qualityAssuranceService: QualityAssuranceTopicDataService,
|
||||
private translationService: TranslateService,
|
||||
) {
|
||||
|
||||
@@ -28,14 +32,18 @@ export class QualityAssuranceBreadcrumbService implements BreadcrumbsProviderSer
|
||||
* @param url The url to use as a link for this breadcrumb
|
||||
*/
|
||||
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
|
||||
const args = key.split(':');
|
||||
const sourceId = args[0];
|
||||
const topicId = args.length > 2 ? args[args.length - 1] : args[1];
|
||||
const sourceId = key.split(':')[0];
|
||||
const topicId = key.split(':')[2];
|
||||
|
||||
if (topicId) {
|
||||
return observableOf( [new Breadcrumb(this.translationService.instant(this.QUALITY_ASSURANCE_BREADCRUMB_KEY), url),
|
||||
return this.qualityAssuranceService.getTopic(topicId).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((topic) => {
|
||||
return [new Breadcrumb(this.translationService.instant(this.QUALITY_ASSURANCE_BREADCRUMB_KEY), url),
|
||||
new Breadcrumb(sourceId, `${url}${sourceId}`),
|
||||
new Breadcrumb(topicId, undefined)]);
|
||||
new Breadcrumb(topicId.replace(/[!:]/g, '/'), undefined)];
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return observableOf([new Breadcrumb(this.translationService.instant(this.QUALITY_ASSURANCE_BREADCRUMB_KEY), url),
|
||||
new Breadcrumb(sourceId, `${url}${sourceId}`)]);
|
||||
|
@@ -185,19 +185,9 @@ import { FlatBrowseDefinition } from './shared/flat-browse-definition.model';
|
||||
import { ValueListBrowseDefinition } from './shared/value-list-browse-definition.model';
|
||||
import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition';
|
||||
import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model';
|
||||
import { LdnServicesService } from '../admin/admin-ldn-services/ldn-services-data/ldn-services-data.service';
|
||||
import { LdnService } from '../admin/admin-ldn-services/ldn-services-model/ldn-services.model';
|
||||
import { LdnItemfiltersService } from '../admin/admin-ldn-services/ldn-services-data/ldn-itemfilters-data.service';
|
||||
import { Itemfilter } from '../admin/admin-ldn-services/ldn-services-model/ldn-service-itemfilters';
|
||||
import {
|
||||
CoarNotifyConfigDataService
|
||||
} from '../submission/sections/section-coar-notify/coar-notify-config-data.service';
|
||||
import { SubmissionCoarNotifyConfig } from '../submission/sections/section-coar-notify/submission-coar-notify.config';
|
||||
import { NotifyRequestsStatus } from '../item-page/simple/notify-requests-status/notify-requests-status.model';
|
||||
import { NotifyRequestsStatusDataService } from './data/notify-services-status-data.service';
|
||||
import { SuggestionTarget } from './notifications/models/suggestion-target.model';
|
||||
import { SuggestionSource } from './notifications/models/suggestion-source.model';
|
||||
|
||||
import { CorrectionTypeDataService } from './submission/correctiontype-data.service';
|
||||
import { SuggestionTarget } from './suggestion-notifications/models/suggestion-target.model';
|
||||
import { SuggestionSource } from './suggestion-notifications/models/suggestion-source.model';
|
||||
|
||||
/**
|
||||
* When not in production, endpoint responses can be mocked for testing purposes
|
||||
@@ -321,10 +311,7 @@ const PROVIDERS = [
|
||||
OrcidQueueDataService,
|
||||
OrcidHistoryDataService,
|
||||
SupervisionOrderDataService,
|
||||
LdnServicesService,
|
||||
LdnItemfiltersService,
|
||||
CoarNotifyConfigDataService,
|
||||
NotifyRequestsStatusDataService
|
||||
CorrectionTypeDataService
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -405,11 +392,7 @@ export const models =
|
||||
ItemRequest,
|
||||
BulkAccessConditionOptions,
|
||||
SuggestionTarget,
|
||||
SuggestionSource,
|
||||
LdnService,
|
||||
Itemfilter,
|
||||
SubmissionCoarNotifyConfig,
|
||||
NotifyRequestsStatus,
|
||||
SuggestionSource
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { find, take } from 'rxjs/operators';
|
||||
import { find, switchMap, take } from 'rxjs/operators';
|
||||
import { ReplaceOperation } from 'fast-json-patch';
|
||||
|
||||
import { HALEndpointService } from '../../../shared/hal-endpoint.service';
|
||||
@@ -25,6 +25,11 @@ import { SearchData, SearchDataImpl } from '../../../data/base/search-data';
|
||||
import { DefaultChangeAnalyzer } from '../../../data/default-change-analyzer.service';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
import { DeleteByIDRequest, PostRequest } from '../../../data/request.models';
|
||||
import { HttpHeaders, HttpParams } from '@angular/common/http';
|
||||
import { HttpOptions } from '../../../dspace-rest/dspace-rest.service';
|
||||
import {
|
||||
QualityAssuranceEventData
|
||||
} from '../../../../notifications/qa/project-entry-import-modal/project-entry-import-modal.component';
|
||||
|
||||
/**
|
||||
* The service handling all Quality Assurance topic REST requests.
|
||||
@@ -210,4 +215,38 @@ export class QualityAssuranceEventDataService extends IdentifiableDataService<Qu
|
||||
|
||||
return this.rdbService.buildFromRequestUUID<QualityAssuranceEventObject>(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a post on an endpoint related to correction type
|
||||
* @param data the data to post
|
||||
* @returns the RestResponse as an Observable
|
||||
*/
|
||||
postData(target: string, correctionType: string, related: string, reason: string): Observable<RemoteData<QualityAssuranceEventObject>> {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
const href$ = this.getBrowseEndpoint();
|
||||
|
||||
return href$.pipe(
|
||||
switchMap((href: string) => {
|
||||
const options: HttpOptions = Object.create({});
|
||||
let headers = new HttpHeaders();
|
||||
headers = headers.append('Content-Type', 'application/json');
|
||||
options.headers = headers;
|
||||
let params = new HttpParams();
|
||||
params = params.append('target', target)
|
||||
.append('correctionType', correctionType);
|
||||
options.params = params;
|
||||
const request = new PostRequest(requestId, href, {'reason': reason} , options);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.send(request);
|
||||
return this.rdbService.buildFromRequestUUID<QualityAssuranceEventObject>(requestId);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public deleteQAEvent(qaEvent: QualityAssuranceEventData): Observable<RemoteData<NoContent>> {
|
||||
return this.deleteData.delete(qaEvent.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -28,6 +28,8 @@ export interface SourceQualityAssuranceEventMessageObject {
|
||||
*/
|
||||
type: string;
|
||||
|
||||
reason: string;
|
||||
|
||||
/**
|
||||
* The value suggested by Notifications
|
||||
*/
|
||||
|
@@ -16,7 +16,7 @@ import { PaginatedList } from '../../../data/paginated-list.model';
|
||||
import { FindListOptions } from '../../../data/find-list-options.model';
|
||||
import { IdentifiableDataService } from '../../../data/base/identifiable-data.service';
|
||||
import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
||||
import { SearchData, SearchDataImpl } from 'src/app/core/data/base/search-data';
|
||||
import { SearchData, SearchDataImpl } from '../../../data/base/search-data';
|
||||
|
||||
/**
|
||||
* The service handling all Quality Assurance source REST requests.
|
||||
|
@@ -81,8 +81,9 @@ describe('QualityAssuranceTopicDataService', () => {
|
||||
notificationsService
|
||||
);
|
||||
|
||||
spyOn((service as any).searchData, 'searchBy').and.callThrough();
|
||||
spyOn((service as any).findAllData, 'findAll').and.callThrough();
|
||||
spyOn((service as any), 'findById').and.callThrough();
|
||||
spyOn((service as any).searchData, 'searchBy').and.callThrough();
|
||||
});
|
||||
|
||||
describe('searchTopicsByTarget', () => {
|
||||
@@ -110,31 +111,6 @@ describe('QualityAssuranceTopicDataService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchTopicsBySource', () => {
|
||||
it('should call searchData.searchBy with the correct parameters', () => {
|
||||
const options = { elementsPerPage: 10 };
|
||||
const useCachedVersionIfAvailable = true;
|
||||
const reRequestOnStale = true;
|
||||
|
||||
service.searchTopicsBySource(options, useCachedVersionIfAvailable, reRequestOnStale);
|
||||
|
||||
expect((service as any).searchData.searchBy).toHaveBeenCalledWith(
|
||||
'bySource',
|
||||
options,
|
||||
useCachedVersionIfAvailable,
|
||||
reRequestOnStale,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<PaginatedList<QualityAssuranceTopicObject>> for the object with the given URL', () => {
|
||||
const result = service.searchTopicsBySource();
|
||||
const expected = cold('(a)', {
|
||||
a: paginatedListRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTopic', () => {
|
||||
it('should call findByHref', (done) => {
|
||||
service.getTopic(qualityAssuranceTopicObjectMorePid.id).subscribe(
|
||||
@@ -153,4 +129,5 @@ describe('QualityAssuranceTopicDataService', () => {
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -15,7 +15,7 @@ import { FindListOptions } from '../../../data/find-list-options.model';
|
||||
import { IdentifiableDataService } from '../../../data/base/identifiable-data.service';
|
||||
import { dataService } from '../../../data/base/data-service.decorator';
|
||||
import { QUALITY_ASSURANCE_TOPIC_OBJECT } from '../models/quality-assurance-topic-object.resource-type';
|
||||
import { SearchData, SearchDataImpl } from '../../../../core/data/base/search-data';
|
||||
import { SearchData, SearchDataImpl } from '../../../data/base/search-data';
|
||||
import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
||||
|
||||
/**
|
||||
|
89
src/app/core/submission/correctiontype-data.service.ts
Normal file
89
src/app/core/submission/correctiontype-data.service.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { dataService } from '../data/base/data-service.decorator';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
||||
import { SearchDataImpl } from '../data/base/search-data';
|
||||
import { CorrectionType } from './models/correctiontype.model';
|
||||
import { Observable, map } from 'rxjs';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { PaginatedList } from '../data/paginated-list.model';
|
||||
import { FindListOptions } from '../data/find-list-options.model';
|
||||
import { RequestParam } from '../cache/models/request-param.model';
|
||||
import { getAllSucceededRemoteDataPayload, getPaginatedListPayload } from '../shared/operators';
|
||||
|
||||
/**
|
||||
* A service that provides methods to make REST requests with correctiontypes endpoint.
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(CorrectionType.type)
|
||||
export class CorrectionTypeDataService extends IdentifiableDataService<CorrectionType> {
|
||||
protected linkPath = 'correctiontypes';
|
||||
protected searchByTopic = 'findByTopic';
|
||||
protected searchFindByItem = 'findByItem';
|
||||
private searchData: SearchDataImpl<CorrectionType>;
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
) {
|
||||
super('correctiontypes', requestService, rdbService, objectCache, halService);
|
||||
|
||||
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correction type by id
|
||||
* @param id the id of the correction type
|
||||
* @param useCachedVersionIfAvailable use the cached version if available
|
||||
* @param reRequestOnStale re-request on stale
|
||||
* @returns {Observable<RemoteData<CorrectionType>>} the correction type
|
||||
*/
|
||||
getCorrectionTypeById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true): Observable<RemoteData<CorrectionType>> {
|
||||
return this.findById(id, useCachedVersionIfAvailable, reRequestOnStale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the correction types for the item
|
||||
* @param itemUuid the uuid of the item
|
||||
* @param useCachedVersionIfAvailable use the cached version if available
|
||||
* @returns the list of correction types for the item
|
||||
*/
|
||||
findByItem(itemUuid: string, useCachedVersionIfAvailable): Observable<RemoteData<PaginatedList<CorrectionType>>> {
|
||||
const options = new FindListOptions();
|
||||
options.searchParams = [new RequestParam('uuid', itemUuid)];
|
||||
return this.searchData.searchBy(this.searchFindByItem, options, useCachedVersionIfAvailable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the correction type for the topic
|
||||
* @param topic the topic of the correction type to search for
|
||||
* @param useCachedVersionIfAvailable use the cached version if available
|
||||
* @param reRequestOnStale re-request on stale
|
||||
* @returns the correction type for the topic
|
||||
*/
|
||||
findByTopic(topic: string, useCachedVersionIfAvailable = true, reRequestOnStale = true): Observable<CorrectionType> {
|
||||
const options = new FindListOptions();
|
||||
options.searchParams = [
|
||||
{
|
||||
fieldName: 'topic',
|
||||
fieldValue: topic,
|
||||
},
|
||||
];
|
||||
|
||||
return this.searchData.searchBy(this.searchByTopic, options, useCachedVersionIfAvailable, reRequestOnStale).pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
getPaginatedListPayload(),
|
||||
map((list: CorrectionType[]) => {
|
||||
return list[0];
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
49
src/app/core/submission/models/correctiontype.model.ts
Normal file
49
src/app/core/submission/models/correctiontype.model.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
import { typedObject } from '../../cache/builders/build-decorators';
|
||||
import { CacheableObject } from '../../cache/cacheable-object.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { excludeFromEquals } from '../../utilities/equals.decorators';
|
||||
import { HALLink } from '../../shared/hal-link.model';
|
||||
|
||||
@typedObject
|
||||
/**
|
||||
* Represents a correction type. It extends the CacheableObject.
|
||||
* The correction type represents a type of correction that can be applied to a submission.
|
||||
*/
|
||||
export class CorrectionType extends CacheableObject {
|
||||
static type = new ResourceType('correctiontype');
|
||||
|
||||
/**
|
||||
* The object type
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
type: ResourceType;
|
||||
@autoserialize
|
||||
/**
|
||||
* The unique identifier for the correction type mode.
|
||||
*/
|
||||
id: string;
|
||||
@autoserialize
|
||||
/**
|
||||
* The topic of the correction type mode.
|
||||
*/
|
||||
topic: string;
|
||||
@autoserialize
|
||||
/**
|
||||
* The discovery configuration for the correction type mode.
|
||||
*/
|
||||
discoveryConfiguration: string;
|
||||
@autoserialize
|
||||
/**
|
||||
* The form used for creating a correction type.
|
||||
*/
|
||||
creationForm: string;
|
||||
@deserialize
|
||||
/**
|
||||
* Represents the links associated with the correction type mode.
|
||||
*/
|
||||
_links: {
|
||||
self: HALLink;
|
||||
};
|
||||
}
|
@@ -6,7 +6,10 @@
|
||||
<ds-alert [type]="AlertTypeEnum.Warning">
|
||||
<div class="d-flex justify-content-between flex-wrap">
|
||||
<span class="align-self-center">{{'item.alerts.withdrawn' | translate}}</span>
|
||||
<div class="gap-2 d-flex">
|
||||
<a routerLink="/home" class="btn btn-primary btn-sm">{{"404.link.home-page" | translate}}</a>
|
||||
<a *ngIf="showReinstateButton$() | async" class="btn btn-primary btn-sm" (click)="openReinstateModal()">{{ 'item.alerts.reinstate-request' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</ds-alert>
|
||||
</div>
|
||||
|
@@ -4,16 +4,41 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { DsoWithdrawnReinstateModalService, REQUEST_REINSTATE } from '../../shared/dso-page/dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
import { CorrectionTypeDataService } from '../../core/submission/correctiontype-data.service';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { CorrectionType } from '../../core/submission/models/correctiontype.model';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('ItemAlertsComponent', () => {
|
||||
let component: ItemAlertsComponent;
|
||||
let fixture: ComponentFixture<ItemAlertsComponent>;
|
||||
let item: Item;
|
||||
let authorizationService;
|
||||
let dsoWithdrawnReinstateModalService;
|
||||
let correctionTypeDataService;
|
||||
let testScheduler: TestScheduler;
|
||||
|
||||
const itemMock = Object.assign(new Item(), {
|
||||
uuid: 'item-uuid',
|
||||
id: 'item-uuid',
|
||||
});
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
authorizationService = jasmine.createSpyObj('authorizationService', ['isAuthorized']);
|
||||
dsoWithdrawnReinstateModalService = jasmine.createSpyObj('dsoWithdrawnReinstateModalService', ['openCreateWithdrawnReinstateModal']);
|
||||
correctionTypeDataService = jasmine.createSpyObj('correctionTypeDataService', ['findByItem']);
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ItemAlertsComponent],
|
||||
imports: [TranslateModule.forRoot()],
|
||||
providers: [
|
||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||
{ provide: DsoWithdrawnReinstateModalService, useValue: dsoWithdrawnReinstateModalService },
|
||||
{ provide: CorrectionTypeDataService, useValue: correctionTypeDataService }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
@@ -21,7 +46,9 @@ describe('ItemAlertsComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ItemAlertsComponent);
|
||||
|
||||
component = fixture.componentInstance;
|
||||
component.item = itemMock;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -61,6 +88,7 @@ describe('ItemAlertsComponent', () => {
|
||||
isWithdrawn: true
|
||||
});
|
||||
component.item = item;
|
||||
(correctionTypeDataService.findByItem).and.returnValue(createSuccessfulRemoteDataObject$([]));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -76,6 +104,7 @@ describe('ItemAlertsComponent', () => {
|
||||
isWithdrawn: false
|
||||
});
|
||||
component.item = item;
|
||||
(correctionTypeDataService.findByItem).and.returnValue(createSuccessfulRemoteDataObject$([]));
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -84,4 +113,43 @@ describe('ItemAlertsComponent', () => {
|
||||
expect(privateWarning).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the item is reinstated', () => {
|
||||
const correctionType = Object.assign(new CorrectionType(), {
|
||||
topic: REQUEST_REINSTATE
|
||||
});
|
||||
const correctionRD = createSuccessfulRemoteDataObject(createPaginatedList([correctionType]));
|
||||
|
||||
beforeEach(() => {
|
||||
item = itemMock;
|
||||
component.item = item;
|
||||
(correctionTypeDataService.findByItem).and.returnValue(of(correctionRD));
|
||||
|
||||
testScheduler = new TestScheduler((actual, expected) => {
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should return true when user is not an admin and there is at least one correction with topic REQUEST_REINSTATE', () => {
|
||||
testScheduler.run(({ cold, expectObservable }) => {
|
||||
const isAdminMarble = 'a';
|
||||
const correctionMarble = 'b';
|
||||
const expectedMarble = 'c';
|
||||
|
||||
const isAdminValues = { a: false };
|
||||
const correctionValues = { b: correctionRD };
|
||||
const expectedValues = { c: true };
|
||||
|
||||
const isAdmin$ = cold(isAdminMarble, isAdminValues);
|
||||
const correction$ = cold(correctionMarble, correctionValues);
|
||||
|
||||
(authorizationService.isAuthorized).and.returnValue(isAdmin$);
|
||||
(correctionTypeDataService.findByItem).and.returnValue(correction$);
|
||||
|
||||
expectObservable(component.showReinstateButton$()).toBe(expectedMarble, expectedValues);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,12 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { AlertType } from '../../shared/alert/alert-type';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { Observable, combineLatest, map } from 'rxjs';
|
||||
import { DsoWithdrawnReinstateModalService, REQUEST_REINSTATE } from '../../shared/dso-page/dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
import { CorrectionTypeDataService } from '../../core/submission/correctiontype-data.service';
|
||||
import { getFirstCompletedRemoteData, getPaginatedListPayload, getRemoteDataPayload } from 'src/app/core/shared/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-alerts',
|
||||
@@ -21,4 +27,37 @@ export class ItemAlertsComponent {
|
||||
* @type {AlertType}
|
||||
*/
|
||||
public AlertTypeEnum = AlertType;
|
||||
|
||||
constructor(
|
||||
private authService: AuthorizationDataService,
|
||||
private dsoWithdrawnReinstateModalService: DsoWithdrawnReinstateModalService,
|
||||
private correctionTypeDataService: CorrectionTypeDataService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to show the reinstate button.
|
||||
* The button is shown if the user is not an admin and the item has a reinstate request.
|
||||
* @returns An Observable that emits a boolean value indicating whether to show the reinstate button.
|
||||
*/
|
||||
showReinstateButton$(): Observable<boolean> {
|
||||
const correction$ = this.correctionTypeDataService.findByItem(this.item.uuid, true).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
getPaginatedListPayload()
|
||||
);
|
||||
const isAdmin$ = this.authService.isAuthorized(FeatureID.AdministratorOf);
|
||||
return combineLatest([isAdmin$, correction$]).pipe(
|
||||
map(([isAdmin, correction]) => {
|
||||
return !isAdmin && correction.some((correctionType) => correctionType.topic === REQUEST_REINSTATE);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the reinstate modal for the item.
|
||||
*/
|
||||
openReinstateModal() {
|
||||
this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(this.item, 'request-reinstate', this.item.isArchived);
|
||||
}
|
||||
}
|
||||
|
@@ -30,11 +30,15 @@ import { RelatedItemsComponent } from './simple/related-items/related-items-comp
|
||||
import {
|
||||
ThemedMetadataRepresentationListComponent
|
||||
} from './simple/metadata-representation-list/themed-metadata-representation-list.component';
|
||||
import {
|
||||
ItemWithdrawnReinstateModalComponent
|
||||
} from '../shared/correction-suggestion/withdrawn-reinstate-modal.component';
|
||||
import { ItemPageImgFieldComponent } from './simple/field-components/specific-field/img/item-page-img-field.component';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
ItemVersionsDeleteModalComponent,
|
||||
ItemVersionsSummaryModalComponent,
|
||||
ItemWithdrawnReinstateModalComponent
|
||||
|
||||
];
|
||||
|
||||
|
@@ -1,13 +1,21 @@
|
||||
<ng-container *ngIf="(getQualityAssuranceSources$() | async)?.length > 0">
|
||||
<ng-container *ngFor="let source of (getQualityAssuranceSources$() | async)">
|
||||
<ng-container *ngIf="(sources$ | async) as sources">
|
||||
<ng-container *ngFor="let source of sources">
|
||||
<div class="alert alert-info d-flex flex-row" *ngIf="source.totalEvents > 0">
|
||||
<img class="source-logo" src="assets/images/qa-{{(source.id | dsSplit: ':')[0]}}-logo.png" alt="{{source.id}} logo">
|
||||
<div class="source-logo-container">
|
||||
<img class="source-logo"
|
||||
src="assets/images/qa-{{(source.id | dsSplit: ':')[0]}}-logo.png"
|
||||
alt="{{source.id}} logo"
|
||||
onerror="this.src='assets/images/dspace-logo.svg'">
|
||||
</div>
|
||||
<div class="w-100 d-flex justify-content-between">
|
||||
<div class="pl-4 align-self-center">{{'item.qa-event-notification.check.notification-info' | translate : {num:
|
||||
source.totalEvents } }} </div>
|
||||
<div class="pl-4 align-self-center">
|
||||
{{'item.qa-event-notification.check.notification-info' | translate : {num: source.totalEvents } }}
|
||||
</div>
|
||||
<button [routerLink]="[ getQualityAssuranceRoute(), (source.id | dsSplit: ':')[0], 'target', item.id]"
|
||||
class="btn btn-primary align-self-center">{{'item.qa-event-notification-info.check.button' | translate
|
||||
}}</button>
|
||||
[queryParams]="{ forward: true }"
|
||||
class="btn btn-primary align-self-center">
|
||||
{{'item.qa-event-notification-info.check.button' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
@@ -1,8 +1,13 @@
|
||||
|
||||
.source-logo {
|
||||
max-height: var(--ds-header-logo-height);
|
||||
}
|
||||
|
||||
.source-logo-container {
|
||||
width: var(--ds-qa-logo-width);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sections-gap {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
@@ -1,34 +1,40 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { QaEventNotificationComponent } from './qa-event-notification.component';
|
||||
import { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||
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 { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SplitPipe } from '../../../shared/utils/split.pipe';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
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 { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { HALEndpointService } from '../../../core/shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { HALEndpointServiceStub } from '../../../shared/testing/hal-endpoint-service.stub';
|
||||
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<QaEventNotificationComponent>;
|
||||
|
||||
let qualityAssuranceSourceDataServiceStub: any;
|
||||
|
||||
const obj = createSuccessfulRemoteDataObject$(createPaginatedList([new QualityAssuranceSourceObject()]));
|
||||
const item = Object.assign({ uuid: '1234' });
|
||||
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 () => {
|
||||
|
||||
qualityAssuranceSourceDataServiceStub = {
|
||||
getSourcesByTarget: () => obj
|
||||
getSourcesByTarget: () => objPL
|
||||
};
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CommonModule, TranslateModule.forRoot()],
|
||||
@@ -37,22 +43,31 @@ describe('QaEventNotificationComponent', () => {
|
||||
{ provide: QualityAssuranceSourceDataService, useValue: qualityAssuranceSourceDataServiceStub },
|
||||
{ provide: RequestService, useValue: {} },
|
||||
{ provide: NotificationsService, useValue: {} },
|
||||
{ provide: HALEndpointService, useValue: new HALEndpointServiceStub('test')},
|
||||
{ provide: HALEndpointService, useValue: new HALEndpointServiceStub('test') },
|
||||
ObjectCacheService,
|
||||
RemoteDataBuildService,
|
||||
provideMockStore({})
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.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');
|
||||
});
|
||||
});
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { getFirstCompletedRemoteData, getPaginatedListPayload, getRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import { Observable, filter } from 'rxjs';
|
||||
import { AlertType } from '../../../shared/alert/alert-type';
|
||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||
import { RequestParam } from '../../../core/cache/models/request-param.model';
|
||||
import { getNotificatioQualityAssuranceRoute } from '../../../admin/admin-routing-paths';
|
||||
import { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||
import { QualityAssuranceSourceObject } from '../../../core/notifications/qa/models/quality-assurance-source.model';
|
||||
import { PaginatedList } from 'src/app/core/data/paginated-list.model';
|
||||
import { hasValue } from 'src/app/shared/empty.util';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { getNotificatioQualityAssuranceRoute } from '../../../admin/admin-routing-paths';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-qa-event-notification',
|
||||
@@ -21,22 +21,29 @@ import { hasValue } from 'src/app/shared/empty.util';
|
||||
/**
|
||||
* Component for displaying quality assurance event notifications for an item.
|
||||
*/
|
||||
export class QaEventNotificationComponent {
|
||||
|
||||
export class QaEventNotificationComponent implements OnChanges {
|
||||
/**
|
||||
* The item to display quality assurance event notifications for.
|
||||
*/
|
||||
@Input() item: Item;
|
||||
|
||||
/**
|
||||
* The type of alert to display for the notification.
|
||||
* An observable that emits an array of QualityAssuranceSourceObject.
|
||||
*/
|
||||
AlertTypeInfo = AlertType.Info;
|
||||
sources$: Observable<QualityAssuranceSourceObject[]>;
|
||||
|
||||
constructor(
|
||||
private qualityAssuranceSourceDataService: QualityAssuranceSourceDataService,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Detect changes to the item input and update the sources$ observable.
|
||||
*/
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.item && changes.item.currentValue.uuid !== changes.item.previousValue?.uuid) {
|
||||
this.sources$ = this.getQualityAssuranceSources$();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns an Observable of QualityAssuranceSourceObject[] for the current item.
|
||||
* @returns An Observable of QualityAssuranceSourceObject[] for the current item.
|
||||
@@ -46,12 +53,16 @@ export class QaEventNotificationComponent {
|
||||
const findListTopicOptions: FindListOptions = {
|
||||
searchParams: [new RequestParam('target', this.item.uuid)]
|
||||
};
|
||||
return this.qualityAssuranceSourceDataService.getSourcesByTarget(findListTopicOptions)
|
||||
return this.qualityAssuranceSourceDataService.getSourcesByTarget(findListTopicOptions, false)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
filter((pl: PaginatedList<QualityAssuranceSourceObject>) => hasValue(pl)),
|
||||
getPaginatedListPayload(),
|
||||
map((data: RemoteData<PaginatedList<QualityAssuranceSourceObject>>) => {
|
||||
if (data.hasSucceeded) {
|
||||
return data.payload.page;
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
catchError(() => [])
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<div class="container">
|
||||
<ds-suggestions-notification></ds-suggestions-notification>
|
||||
<ds-my-dspace-qa-events-notifications></ds-my-dspace-qa-events-notifications>
|
||||
<ds-my-dspace-new-submission *dsShowOnlyForRole="[roleTypeEnum.Submitter]"></ds-my-dspace-new-submission>
|
||||
<ds-suggestions-notification></ds-suggestions-notification>
|
||||
</div>
|
||||
|
||||
<ds-themed-search *ngIf="configuration && context"
|
||||
|
@@ -15,8 +15,10 @@ import { MyDSpaceNewExternalDropdownComponent } from './my-dspace-new-submission
|
||||
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
|
||||
import { SearchModule } from '../shared/search/search.module';
|
||||
import { UploadModule } from '../shared/upload/upload.module';
|
||||
import {
|
||||
MyDspaceQaEventsNotificationsComponent
|
||||
} from './my-dspace-qa-events-notifications/my-dspace-qa-events-notifications.component';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
import { MyDspaceQaEventsNotificationsComponent } from './my-dspace-qa-events-notifications/my-dspace-qa-events-notifications.component';
|
||||
|
||||
const DECLARATIONS = [
|
||||
MyDSpacePageComponent,
|
||||
@@ -25,7 +27,7 @@ const DECLARATIONS = [
|
||||
CollectionSelectorComponent,
|
||||
MyDSpaceNewSubmissionDropdownComponent,
|
||||
MyDSpaceNewExternalDropdownComponent,
|
||||
MyDspaceQaEventsNotificationsComponent,
|
||||
MyDspaceQaEventsNotificationsComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -36,7 +38,7 @@ const DECLARATIONS = [
|
||||
MyDspacePageRoutingModule,
|
||||
MyDspaceSearchModule.withEntryComponents(),
|
||||
UploadModule,
|
||||
NotificationsModule,
|
||||
NotificationsModule
|
||||
],
|
||||
declarations: DECLARATIONS,
|
||||
providers: [
|
||||
|
@@ -4,20 +4,21 @@
|
||||
class="alert alert-info d-flex flex-row"
|
||||
*ngIf="source.totalEvents > 0"
|
||||
>
|
||||
<div class="source-logo-container">
|
||||
<img
|
||||
class="source-logo"
|
||||
src="assets/images/qa-{{ source.id }}-logo.png"
|
||||
onerror="this.src='assets/images/dspace-logo.svg'"
|
||||
alt="{{ source.id }} logo"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-100 d-flex justify-content-between">
|
||||
<div class="pl-4 align-self-center">
|
||||
{{
|
||||
"mydspace.qa-event-notification.check.notification-info"
|
||||
| translate : { num: source.totalEvents }
|
||||
}}
|
||||
{{ "mydspace.qa-event-notification.check.notification-info" | translate : { num: source.totalEvents } }}
|
||||
</div>
|
||||
<button
|
||||
[routerLink]="[getQualityAssuranceRoute(), source.id]"
|
||||
[queryParams]="{ forward: true }"
|
||||
class="btn btn-primary align-self-center"
|
||||
>
|
||||
{{ "mydspace.qa-event-notification-info.check.button" | translate }}
|
||||
|
@@ -1,8 +1,13 @@
|
||||
|
||||
.source-logo {
|
||||
max-height: var(--ds-header-logo-height);
|
||||
}
|
||||
|
||||
.source-logo-container {
|
||||
width: var(--ds-qa-logo-width);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sections-gap {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
@@ -26,23 +26,30 @@ import { QualityAssuranceSourceService } from './qa/source/quality-assurance-sou
|
||||
import {
|
||||
QualityAssuranceSourceDataService
|
||||
} from '../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||
|
||||
import { SuggestionsPopupComponent } from './suggestions-popup/suggestions-popup.component';
|
||||
import { SuggestionSourceDataService } from '../core/notifications/source/suggestion-source-data.service';
|
||||
import { SuggestionTargetDataService } from '../core/notifications/target/suggestion-target-data.service';
|
||||
import { SuggestionsDataService } from '../core/notifications/suggestions-data.service';
|
||||
import { PublicationClaimComponent } from '../notifications/suggestion-targets/publication-claim/publication-claim.component';
|
||||
|
||||
import { SuggestionsNotificationComponent } from './suggestions-notification/suggestions-notification.component';
|
||||
import { SuggestionsService } from './suggestions.service';
|
||||
import { SuggestionTargetsStateService } from './suggestion-targets/suggestion-targets.state.service';
|
||||
import { SuggestionActionsComponent } from './suggestion-actions/suggestion-actions.component';
|
||||
import { SuggestionListElementComponent } from './suggestion-list-element/suggestion-list-element.component';
|
||||
import { EPersonDataComponent } from './qa/events/ePerson-data/ePerson-data.component';
|
||||
import { PublicationClaimComponent } from '../suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component';
|
||||
import { SuggestionActionsComponent } from '../suggestion-notifications/suggestion-actions/suggestion-actions.component';
|
||||
import {
|
||||
SuggestionListElementComponent
|
||||
} from '../suggestion-notifications/suggestion-list-element/suggestion-list-element.component';
|
||||
import {
|
||||
SuggestionEvidencesComponent
|
||||
} from './suggestion-list-element/suggestion-evidences/suggestion-evidences.component';
|
||||
|
||||
|
||||
} from '../suggestion-notifications/suggestion-list-element/suggestion-evidences/suggestion-evidences.component';
|
||||
import { SuggestionsPopupComponent } from '../suggestion-notifications/suggestions-popup/suggestions-popup.component';
|
||||
import {
|
||||
SuggestionsNotificationComponent
|
||||
} from '../suggestion-notifications/suggestions-notification/suggestions-notification.component';
|
||||
import { SuggestionsService } from '../suggestion-notifications/suggestions.service';
|
||||
import { SuggestionsDataService } from '../core/suggestion-notifications/suggestions-data.service';
|
||||
import {
|
||||
SuggestionSourceDataService
|
||||
} from '../core/suggestion-notifications/source/suggestion-source-data.service';
|
||||
import {
|
||||
SuggestionTargetDataService
|
||||
} from '../core/suggestion-notifications/target/suggestion-target-data.service';
|
||||
import {
|
||||
SuggestionTargetsStateService
|
||||
} from '../suggestion-notifications/suggestion-targets/suggestion-targets.state.service';
|
||||
|
||||
|
||||
const MODULES = [
|
||||
@@ -59,6 +66,7 @@ const COMPONENTS = [
|
||||
QualityAssuranceTopicsComponent,
|
||||
QualityAssuranceEventsComponent,
|
||||
QualityAssuranceSourceComponent,
|
||||
EPersonDataComponent,
|
||||
PublicationClaimComponent,
|
||||
SuggestionActionsComponent,
|
||||
SuggestionListElementComponent,
|
||||
@@ -94,7 +102,7 @@ const PROVIDERS = [
|
||||
declarations: [
|
||||
...COMPONENTS,
|
||||
...DIRECTIVES,
|
||||
...ENTRY_COMPONENTS
|
||||
...ENTRY_COMPONENTS,
|
||||
],
|
||||
providers: [
|
||||
...PROVIDERS
|
||||
@@ -104,7 +112,7 @@ const PROVIDERS = [
|
||||
],
|
||||
exports: [
|
||||
...COMPONENTS,
|
||||
...DIRECTIVES
|
||||
...DIRECTIVES,
|
||||
]
|
||||
})
|
||||
|
||||
|
@@ -0,0 +1,10 @@
|
||||
<ng-container *ngIf="ePersonId">
|
||||
<ng-container *ngIf="getEPersonData$() | async as ePersonData">
|
||||
<ng-container *ngFor="let property of properties">
|
||||
<span *ngIf="ePersonData[property]">
|
||||
{{ ePersonData[property] }}
|
||||
</span>
|
||||
<br>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
@@ -0,0 +1,58 @@
|
||||
/* tslint:disable:no-unused-variable */
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { EPersonDataComponent } from './ePerson-data.component';
|
||||
import { EPersonDataService } from './../../../../core/eperson/eperson-data.service';
|
||||
import { EPerson } from 'src/app/core/eperson/models/eperson.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from 'src/app/shared/remote-data.utils';
|
||||
|
||||
describe('EPersonDataComponent', () => {
|
||||
let component: EPersonDataComponent;
|
||||
let fixture: ComponentFixture<EPersonDataComponent>;
|
||||
let ePersonDataService = jasmine.createSpyObj('EPersonDataService', ['findById']);
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ EPersonDataComponent ],
|
||||
providers: [ {
|
||||
provide: EPersonDataService,
|
||||
useValue: ePersonDataService
|
||||
} ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EPersonDataComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should retrieve EPerson data when ePersonId is provided', () => {
|
||||
const ePersonId = '123';
|
||||
const ePersonData = Object.assign(new EPerson(), {
|
||||
id: ePersonId,
|
||||
email: 'john.doe@domain.com',
|
||||
metadata: [
|
||||
{
|
||||
key: 'eperson.firstname',
|
||||
value: 'John'
|
||||
},
|
||||
{
|
||||
key: 'eperson.lastname',
|
||||
value: 'Doe'
|
||||
}
|
||||
]
|
||||
});
|
||||
const ePersonDataRD$ = createSuccessfulRemoteDataObject$(ePersonData);
|
||||
ePersonDataService.findById.and.returnValue(ePersonDataRD$);
|
||||
component.ePersonId = ePersonId;
|
||||
component.getEPersonData$();
|
||||
fixture.detectChanges();
|
||||
expect(ePersonDataService.findById).toHaveBeenCalledWith(ePersonId, true);
|
||||
});
|
||||
});
|
@@ -0,0 +1,45 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../../core/shared/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { EPerson } from '../../../../core/eperson/models/eperson.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-eperson-data',
|
||||
templateUrl: './ePerson-data.component.html',
|
||||
styleUrls: ['./ePerson-data.component.scss']
|
||||
})
|
||||
/**
|
||||
* Represents the component for displaying ePerson data.
|
||||
*/
|
||||
export class EPersonDataComponent {
|
||||
|
||||
/**
|
||||
* The ID of the ePerson.
|
||||
*/
|
||||
@Input() ePersonId: string;
|
||||
|
||||
/**
|
||||
* The properties of the ePerson to display.
|
||||
*/
|
||||
@Input() properties: string[];
|
||||
|
||||
/**
|
||||
* Creates an instance of the EPersonDataComponent.
|
||||
* @param ePersonDataService The service for retrieving ePerson data.
|
||||
*/
|
||||
constructor(private ePersonDataService: EPersonDataService) { }
|
||||
|
||||
/**
|
||||
* Retrieves the EPerson data based on the provided ePersonId.
|
||||
* @returns An Observable that emits the EPerson data.
|
||||
*/
|
||||
getEPersonData$(): Observable<EPerson> {
|
||||
if (this.ePersonId) {
|
||||
return this.ePersonDataService.findById(this.ePersonId, true).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -39,12 +39,17 @@
|
||||
<tr>
|
||||
<th scope="col" class="trust-col">{{'quality-assurance.event.table.trust' | translate}}</th>
|
||||
<th scope="col" class="title-col">{{'quality-assurance.event.table.publication' | translate}}</th>
|
||||
<th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') == -1" scope="col" class="content-col">
|
||||
{{'quality-assurance.event.table.details' | translate}}
|
||||
</th>
|
||||
<th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') !== -1" scope="col" class="content-col">
|
||||
{{'quality-assurance.event.table.project-details' | translate}}
|
||||
</th>
|
||||
<ng-container *ngIf="hasDetailColumn() && (showTopic.indexOf('/REINSTATE') !== -1 || showTopic.indexOf('/WITHDRAWN') !== -1)">
|
||||
<th scope="col" class="content-col">
|
||||
{{'quality-assurance.event.table.reasons' | translate}}
|
||||
</th>
|
||||
<th scope="col" class="content-col">
|
||||
{{'quality-assurance.event.table.person-who-requested' | translate}}
|
||||
</th>
|
||||
</ng-container>
|
||||
<th scope="col" class="button-col">{{'quality-assurance.event.table.actions' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -80,7 +85,8 @@
|
||||
</p>
|
||||
</td>
|
||||
<td *ngIf="showTopic.indexOf('/SUBJECT') !== -1">
|
||||
<p><span class="small">{{'quality-assurance.event.table.subjectValue' | translate}}</span><br><span class="badge badge-info">{{eventElement.event.message.value}}</span></p>
|
||||
<p><span class="small">{{'quality-assurance.event.table.subjectValue' | translate}}
|
||||
</span><br><span class="badge badge-info">{{eventElement.event.message.value}}</span></p>
|
||||
</td>
|
||||
<td *ngIf="showTopic.indexOf('/ABSTRACT') !== -1">
|
||||
<p class="abstract-container" [class.show]="showMore">
|
||||
@@ -93,6 +99,23 @@
|
||||
{{ (showMore ? 'quality-assurance.event.table.less': 'quality-assurance.event.table.more') | translate }}
|
||||
</button>
|
||||
</td>
|
||||
<ng-container *ngIf="showTopic.indexOf('/REINSTATE') !== -1 || showTopic.indexOf('/WITHDRAWN') !== -1">
|
||||
<td>
|
||||
<p>
|
||||
<span *ngIf="eventElement.event.message">
|
||||
<span>{{eventElement.event.message.reason}}</span><br>
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>
|
||||
<span *ngIf="eventElement.event.originalId">
|
||||
<ds-eperson-data [ePersonId]="eventElement.event.originalId" [properties]="['email']"></ds-eperson-data>
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<td *ngIf="showTopic.indexOf('/PROJECT') !== -1">
|
||||
<p>
|
||||
{{'quality-assurance.event.table.suggestedProject' | translate}}
|
||||
@@ -133,7 +156,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group button-width">
|
||||
<div *ngIf="(isAdmin$ | async)" class="btn-group button-width">
|
||||
<button *ngIf="showTopic.indexOf('/PROJECT') !== -1"
|
||||
class="btn btn-outline-success btn-sm button-width"
|
||||
ngbTooltip="{{'quality-assurance.event.action.import' | translate}}"
|
||||
@@ -173,6 +196,16 @@
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="!(isAdmin$ | async)" class="btn-group button-width">
|
||||
<button class="btn btn-outline-danger btn-sm button-width"
|
||||
ngbTooltip="{{'quality-assurance.event.action.undo' | translate}}"
|
||||
container="body"
|
||||
[disabled]="eventElement.isRunning"
|
||||
[attr.aria-label]="'quality-assurance.event.action.undo' | translate"
|
||||
(click)="openModal('UNDO', eventElement, undoModal)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -235,3 +268,20 @@
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #undoModal let-modal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="undoModal">{{'quality-assurance.event.sure' | translate}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'quality-assurance.event.undo.description' | translate}}</p>
|
||||
<button class="btn btn-outline-danger float-right" (click)="modal.close('do')">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<span class="d-none d-sm-inline"> {{'quality-assurance.event.action.undo' | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" (click)="modal.close('cancel')">
|
||||
<i class="fas fa-close"></i>
|
||||
<span class="d-none d-sm-inline"> {{'quality-assurance.event.action.cancel' | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
@@ -43,6 +43,7 @@ import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||
import { ItemDataService } from 'src/app/core/data/item-data.service';
|
||||
import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service';
|
||||
|
||||
describe('QualityAssuranceEventsComponent test suite', () => {
|
||||
let fixture: ComponentFixture<QualityAssuranceEventsComponent>;
|
||||
@@ -120,6 +121,7 @@ describe('QualityAssuranceEventsComponent test suite', () => {
|
||||
{ provide: TranslateService, useValue: getMockTranslateService() },
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
{ provide: ItemDataService, useValue: {} },
|
||||
{ provide: AuthorizationDataService, useValue: {} },
|
||||
QualityAssuranceEventsComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
|
@@ -26,13 +26,14 @@ import {
|
||||
ProjectEntryImportModalComponent,
|
||||
QualityAssuranceEventData
|
||||
} from '../project-entry-import-modal/project-entry-import-modal.component';
|
||||
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||
import { getItemPageRoute } from '../../../item-page/item-page-routing-paths';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
import {environment} from '../../../../environments/environment';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Component to display the Quality Assurance event list.
|
||||
@@ -78,6 +79,11 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
* @type {string}
|
||||
*/
|
||||
public topic: string;
|
||||
/**
|
||||
* The sourceId of the Quality Assurance events.
|
||||
* @type {string}
|
||||
*/
|
||||
sourceId: string;
|
||||
/**
|
||||
* The rejected/ignore reason.
|
||||
* @type {string}
|
||||
@@ -88,6 +94,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
* @type {Observable<boolean>}
|
||||
*/
|
||||
public isEventPageLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
|
||||
/**
|
||||
* The modal reference.
|
||||
* @type {any}
|
||||
@@ -113,24 +120,9 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
protected subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* The target item id, retrieved from the topic-id composition.
|
||||
* Observable that emits a boolean value indicating whether the user is an admin.
|
||||
*/
|
||||
public targetId: string;
|
||||
|
||||
/**
|
||||
* The URL of the item page/target.
|
||||
*/
|
||||
public itemPageUrl: string;
|
||||
|
||||
/**
|
||||
* Plain topic name (without the source id)
|
||||
*/
|
||||
public selectedTopicName: string;
|
||||
|
||||
/**
|
||||
* The source id, retrieved from the topic-id composition.
|
||||
*/
|
||||
public sourceId: string;
|
||||
isAdmin$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Initialize the component variables.
|
||||
@@ -148,7 +140,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
private qualityAssuranceEventRestService: QualityAssuranceEventDataService,
|
||||
private paginationService: PaginationService,
|
||||
private translateService: TranslateService,
|
||||
private itemService: ItemDataService,
|
||||
private authorizationService: AuthorizationDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -157,10 +149,11 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.isEventPageLoading.next(true);
|
||||
|
||||
this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf);
|
||||
this.activatedRoute.paramMap.pipe(
|
||||
tap((params) => {
|
||||
this.sourceUrlForProjectSearch = environment.qualityAssuranceConfig.sourceUrlMapForProjectSearch[params.get('sourceId')];
|
||||
this.sourceId = params.get('sourceId');
|
||||
}),
|
||||
map((params) => params.get('topicId')),
|
||||
take(1),
|
||||
@@ -168,16 +161,19 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
const regEx = /!/g;
|
||||
this.showTopic = id.replace(regEx, '/');
|
||||
this.topic = id;
|
||||
const splitList = this.showTopic?.split(':');
|
||||
this.targetId = splitList.length > 2 ? splitList.pop() : null;
|
||||
this.sourceId = splitList[0];
|
||||
this.selectedTopicName = splitList[1];
|
||||
return this.getQualityAssuranceEvents();
|
||||
})
|
||||
).subscribe((events: QualityAssuranceEventData[]) => {
|
||||
).subscribe(
|
||||
{
|
||||
next: (events: QualityAssuranceEventData[]) => {
|
||||
this.eventsUpdated$.next(events);
|
||||
this.isEventPageLoading.next(false);
|
||||
});
|
||||
},
|
||||
error: (error) => {
|
||||
this.isEventPageLoading.next(false);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,6 +183,8 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
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
|
||||
);
|
||||
}
|
||||
@@ -271,8 +269,14 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
public executeAction(action: string, eventData: QualityAssuranceEventData): void {
|
||||
eventData.isRunning = true;
|
||||
let operation;
|
||||
if (action === 'UNDO') {
|
||||
operation = this.delete(eventData);
|
||||
} else {
|
||||
operation = this.qualityAssuranceEventRestService.patchEvent(action, eventData.event, eventData.reason);
|
||||
}
|
||||
this.subs.push(
|
||||
this.qualityAssuranceEventRestService.patchEvent(action, eventData.event, eventData.reason).pipe(
|
||||
operation.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
switchMap((rd: RemoteData<QualityAssuranceEventObject>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
@@ -389,7 +393,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
switchMap((rd: RemoteData<PaginatedList<QualityAssuranceEventObject>>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
this.totalElements$.next(rd.payload.totalElements);
|
||||
if (rd.payload.totalElements > 0) {
|
||||
if (rd.payload?.page?.length > 0) {
|
||||
return this.fetchEvents(rd.payload.page);
|
||||
} else {
|
||||
return of([]);
|
||||
@@ -460,29 +464,11 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the page route for the given item.
|
||||
* @param item The item to get the page route for.
|
||||
* @returns The page route for the given item.
|
||||
* Deletes a quality assurance event.
|
||||
* @param qaEvent The quality assurance event to delete.
|
||||
* @returns An Observable of RemoteData containing NoContent.
|
||||
*/
|
||||
public getItemPageRoute(item: Item): string {
|
||||
return getItemPageRoute(item);
|
||||
delete(qaEvent: QualityAssuranceEventData): Observable<RemoteData<NoContent>> {
|
||||
return this.qualityAssuranceEventRestService.deleteQAEvent(qaEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Observable that emits the title of the target item.
|
||||
* The target item is retrieved by its ID using the itemService.
|
||||
* The title is extracted from the first metadata value of the item.
|
||||
* The item page URL is also set in the component.
|
||||
* @returns An Observable that emits the title of the target item.
|
||||
*/
|
||||
public getTargetItemTitle(): Observable<string> {
|
||||
return this.itemService.findById(this.targetId).pipe(
|
||||
take(1),
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
tap((item: Item) => this.itemPageUrl = getItemPageRoute(item)),
|
||||
map((item: Item) => item.firstMetadataValue('dc.title'))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -34,12 +34,12 @@
|
||||
<tbody>
|
||||
<tr *ngFor="let sourceElement of (sources$ | async); let i = index">
|
||||
<td>{{sourceElement.id}}</td>
|
||||
<td>{{sourceElement.lastEvent}}</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.button.detail' | translate }}"
|
||||
title="{{'quality-assurance.source-list.button.detail' | translate : { param: sourceElement.id } }}"
|
||||
[routerLink]="[sourceElement.id]">
|
||||
<span class="badge badge-info">{{sourceElement.totalEvents}}</span>
|
||||
<i class="fas fa-info fa-fw"></i>
|
||||
|
@@ -38,12 +38,12 @@
|
||||
<tbody>
|
||||
<tr *ngFor="let topicElement of (topics$ | async); let i = index">
|
||||
<td>{{topicElement.name}}</td>
|
||||
<td>{{topicElement.lastEvent}}</td>
|
||||
<td>{{topicElement.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.button.detail' | translate }}"
|
||||
title="{{'quality-assurance.topics-list.button.detail' | translate : { param: topicElement.name } }}"
|
||||
[routerLink]="[getQualityAssuranceRoute(), sourceId, topicElement.id]">
|
||||
<span class="badge badge-info">{{topicElement.totalEvents}}</span>
|
||||
<i class="fas fa-info fa-fw"></i>
|
||||
|
@@ -16,7 +16,7 @@ import { NotificationsStateService } from '../../notifications-state.service';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
import { ItemDataService } from 'src/app/core/data/item-data.service';
|
||||
|
||||
describe('QualityAssuranceTopicsComponent test suite', () => {
|
||||
let fixture: ComponentFixture<QualityAssuranceTopicsComponent>;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { distinctUntilChanged, map, take, tap } from 'rxjs/operators';
|
||||
@@ -10,14 +10,16 @@ import {
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { NotificationsStateService } from '../../notifications-state.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 { 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';
|
||||
import { getItemPageRoute } from '../../../item-page/item-page-routing-paths';
|
||||
import { getNotificatioQualityAssuranceRoute } from '../../../admin/admin-routing-paths';
|
||||
import { QualityAssuranceTopicsPageParams } from '../../../quality-assurance-notifications-pages/quality-assurance-topics-page/quality-assurance-topics-page-resolver.service';
|
||||
|
||||
/**
|
||||
* Component to display the Quality Assurance topic list.
|
||||
@@ -27,7 +29,7 @@ import { QualityAssuranceTopicsPageParams } from '../../../quality-assurance-not
|
||||
templateUrl: './quality-assurance-topics.component.html',
|
||||
styleUrls: ['./quality-assurance-topics.component.scss'],
|
||||
})
|
||||
export class QualityAssuranceTopicsComponent implements OnInit {
|
||||
export class QualityAssuranceTopicsComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
/**
|
||||
* The pagination system configuration for HTML listing.
|
||||
* @type {PaginationComponentOptions}
|
||||
@@ -78,12 +80,14 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
||||
* @param {PaginationService} paginationService
|
||||
* @param {ActivatedRoute} activatedRoute
|
||||
* @param {NotificationsStateService} notificationsStateService
|
||||
* @param {QualityAssuranceTopicsService} qualityAssuranceTopicsService
|
||||
*/
|
||||
constructor(
|
||||
private paginationService: PaginationService,
|
||||
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;
|
||||
@@ -93,7 +97,15 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
||||
* 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();
|
||||
}
|
||||
|
||||
@@ -134,7 +146,7 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
||||
* Dispatch the Quality Assurance topics retrival.
|
||||
*/
|
||||
public getQualityAssuranceTopics(source: string, target?: string): void {
|
||||
this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig).pipe(
|
||||
this.subs.push(this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig).pipe(
|
||||
distinctUntilChanged(),
|
||||
).subscribe((options: PaginationComponentOptions) => {
|
||||
this.notificationsStateService.dispatchRetrieveQualityAssuranceTopics(
|
||||
@@ -143,7 +155,7 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
||||
source,
|
||||
target
|
||||
);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,7 +163,7 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
||||
*
|
||||
* @param eventsRouteParams
|
||||
*/
|
||||
protected updatePaginationFromRouteParams(eventsRouteParams: QualityAssuranceTopicsPageParams) {
|
||||
protected updatePaginationFromRouteParams(eventsRouteParams: AdminQualityAssuranceTopicsPageParams) {
|
||||
if (eventsRouteParams.currentPage) {
|
||||
this.paginationConfig.currentPage = eventsRouteParams.currentPage;
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ describe('qualityAssuranceTopicsReducer test suite', () => {
|
||||
const expectedState = qualityAssuranceTopicInitialState;
|
||||
expectedState.processing = true;
|
||||
|
||||
const action = new RetrieveAllTopicsAction(elementPerPage, currentPage, 'source', 'target');
|
||||
const action = new RetrieveAllTopicsAction(elementPerPage, currentPage, 'ENRICH!MORE!ABSTRACT');
|
||||
const newState = qualityAssuranceTopicsReducer(qualityAssuranceTopicInitialState, action);
|
||||
|
||||
expect(newState).toEqual(expectedState);
|
||||
|
@@ -48,7 +48,7 @@ describe('QualityAssuranceTopicsService', () => {
|
||||
serviceAsAny = service;
|
||||
});
|
||||
|
||||
describe('getTopicsBySource', () => {
|
||||
describe('getTopics', () => {
|
||||
it('should proxy the call to qualityAssuranceTopicRestService.searchTopicsBySource', () => {
|
||||
const sortOptions = new SortOptions('name', SortDirection.ASC);
|
||||
const findListOptions: FindListOptions = {
|
||||
@@ -57,7 +57,7 @@ describe('QualityAssuranceTopicsService', () => {
|
||||
sort: sortOptions,
|
||||
searchParams: [new RequestParam('source', 'openaire')]
|
||||
};
|
||||
const result = service.getTopics(elementsPerPage, currentPage, 'openaire');
|
||||
service.getTopics(elementsPerPage, currentPage, 'openaire');
|
||||
expect((service as any).qualityAssuranceTopicRestService.searchTopicsBySource).toHaveBeenCalledWith(findListOptions);
|
||||
});
|
||||
|
||||
@@ -68,20 +68,5 @@ describe('QualityAssuranceTopicsService', () => {
|
||||
const result = service.getTopics(elementsPerPage, currentPage, 'openaire');
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should include targetId in searchParams if set', () => {
|
||||
const sortOptions = new SortOptions('name', SortDirection.ASC);
|
||||
const findListOptions: FindListOptions = {
|
||||
elementsPerPage: elementsPerPage,
|
||||
currentPage: currentPage,
|
||||
sort: sortOptions,
|
||||
searchParams: [
|
||||
new RequestParam('source', 'openaire'),
|
||||
new RequestParam('target', '0000-0000-0000-0000-0000')
|
||||
]
|
||||
};
|
||||
const result = service.getTopics(elementsPerPage, currentPage,'openaire', '0000-0000-0000-0000-0000');
|
||||
expect((service as any).qualityAssuranceTopicRestService.searchTopicsByTarget).toHaveBeenCalledWith(findListOptions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -10,9 +10,10 @@ import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
getFinishedRemoteData, getFirstCompletedRemoteData,
|
||||
getFinishedRemoteData,
|
||||
getFirstCompletedRemoteData,
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getFirstSucceededRemoteListPayload
|
||||
getFirstSucceededRemoteListPayload,
|
||||
} from '../core/shared/operators';
|
||||
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
@@ -0,0 +1,55 @@
|
||||
<div *ngIf="!(this.submitted$ | async); else waiting">
|
||||
<div *ngIf="this.canWithdraw; else reinstateHeader" class="modal-header">
|
||||
{{ 'item.qa.withdrawn.modal.header' | translate }}
|
||||
<button type="button" class="close" (click)="onModalClose()" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="reason">{{ this.canWithdraw ? ('qa-withdrawn.create.modal.form.summary.label' | translate)
|
||||
: ('qa-reinstate.create.modal.form.summary.label' | translate) }}</label>
|
||||
<label for="reason">{{ this.canWithdraw ? ('qa-withdrawn.create.modal.form.summary2.label' | translate)
|
||||
: ('qa-reinstate.create.modal.form.summary2.label' | translate) }}</label>
|
||||
<textarea class="form-control" id="reason"
|
||||
rows="6"
|
||||
[(ngModel)]="reason"
|
||||
placeholder="{{ this.canWithdraw ? ('qa-withdrown.modal.form.summary.placeholder' | translate)
|
||||
: ('qa-reinstate.modal.form.summary.placeholder' | translate) }}"
|
||||
name="message"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer space-children-mr">
|
||||
<button class="btn btn-outline-secondary btn-sm ml-0"
|
||||
type="button"
|
||||
(click)="onModalClose()"
|
||||
title="{{'item.qa.withdrawn-reinstate.create.modal.button.cancel.tooltip' | translate}}">
|
||||
<i class="fas fa-times fa-fw"></i> {{'item.qa.withdrawn-reinstate.create.modal.button.cancel' | translate}}
|
||||
</button>
|
||||
<button class="btn btn-success btn-sm ml-0"
|
||||
type="submit"
|
||||
(click)="onModalSubmit()"
|
||||
title="{{'item.qa.withdrawn-reinstate.modal.button.confirm.tooltip' | translate}}">
|
||||
<i class="fas fa-check fa-fw"></i> {{ this.canWithdraw ? ('qa-withdrown.create.modal.button.confirm' | translate)
|
||||
: ('qa-reinstate.create.modal.button.confirm' | translate) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #waiting>
|
||||
<div class="modal-header">{{'item.qa.withdrawn.modal.submitted.header' | translate}}</div>
|
||||
<div class="modal-body">
|
||||
<div class="d-flex justify-content-center">
|
||||
<ds-loading [showMessage]="false"></ds-loading>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #reinstateHeader>
|
||||
<div *ngIf="!this.canWithdraw" class="modal-header">
|
||||
{{'item.qa.reinstate.modal.header' | translate}}
|
||||
<button type="button" class="close" (click)="onModalClose()" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
@@ -0,0 +1,74 @@
|
||||
import { Component, EventEmitter, Output } from '@angular/core';
|
||||
import { ModalBeforeDismiss } from '../interfaces/modal-before-dismiss.interface';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-withdrawn-reinstate-modal',
|
||||
templateUrl: './item-withdrawn-reinstate-modal.component.html',
|
||||
styleUrls: ['./item-withdrawn-reinstate-modal.component.scss']
|
||||
})
|
||||
/**
|
||||
* Represents a modal component for withdrawing or reinstating an item.
|
||||
* Implements the ModalBeforeDismiss interface.
|
||||
*/
|
||||
export class ItemWithdrawnReinstateModalComponent implements ModalBeforeDismiss {
|
||||
|
||||
/**
|
||||
* The reason for withdrawing or reinstating a suggestion.
|
||||
*/
|
||||
reason: string;
|
||||
/**
|
||||
* Indicates whether the item can be withdrawn.
|
||||
*/
|
||||
canWithdraw: boolean;
|
||||
/**
|
||||
* BehaviorSubject that represents the submitted state.
|
||||
* Emits a boolean value indicating whether the form has been submitted or not.
|
||||
*/
|
||||
submitted$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
/**
|
||||
* Event emitter for creating a QA event.
|
||||
* @event createQAEvent
|
||||
*/
|
||||
@Output() createQAEvent: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
constructor(
|
||||
protected activeModal: NgbActiveModal,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Closes the modal.
|
||||
*/
|
||||
onModalClose() {
|
||||
this.activeModal.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the modal can be dismissed.
|
||||
* @returns {boolean} True if the modal can be dismissed, false otherwise.
|
||||
*/
|
||||
beforeDismiss(): boolean {
|
||||
// prevent the modal from being dismissed after version creation is initiated
|
||||
return !this.submitted$.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the submission of the modal form.
|
||||
* Emits the reason for withdrawal or reinstatement through the createQAEvent output.
|
||||
*/
|
||||
onModalSubmit() {
|
||||
this.submitted$.next(true);
|
||||
this.createQAEvent.emit(this.reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the withdrawal state of the component.
|
||||
* @param state The new withdrawal state.
|
||||
*/
|
||||
public setWithdraw(state: boolean) {
|
||||
this.canWithdraw = state;
|
||||
}
|
||||
}
|
@@ -23,6 +23,10 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import flatten from 'lodash/flatten';
|
||||
import { DsoWithdrawnReinstateModalService } from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
import { AuthService } from 'src/app/core/auth/auth.service';
|
||||
import { AuthServiceMock } from '../mocks/auth.service.mock';
|
||||
import { CorrectionTypeDataService } from 'src/app/core/submission/correctiontype-data.service';
|
||||
|
||||
describe('DSOEditMenuResolver', () => {
|
||||
|
||||
@@ -39,6 +43,8 @@ describe('DSOEditMenuResolver', () => {
|
||||
let researcherProfileService;
|
||||
let notificationsService;
|
||||
let translate;
|
||||
let dsoWithdrawnReinstateModalService;
|
||||
let correctionsDataService;
|
||||
|
||||
const dsoRoute = (dso: DSpaceObject) => {
|
||||
return {
|
||||
@@ -141,6 +147,14 @@ describe('DSOEditMenuResolver', () => {
|
||||
error: {},
|
||||
});
|
||||
|
||||
dsoWithdrawnReinstateModalService = jasmine.createSpyObj('dsoWithdrawnReinstateModalService', {
|
||||
openCreateWithdrawnReinstateModal: {},
|
||||
});
|
||||
|
||||
correctionsDataService = jasmine.createSpyObj('correctionsDataService', {
|
||||
findByItem: observableOf([])
|
||||
});
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule],
|
||||
declarations: [AdminSidebarComponent],
|
||||
@@ -152,6 +166,9 @@ describe('DSOEditMenuResolver', () => {
|
||||
{provide: ResearcherProfileDataService, useValue: researcherProfileService},
|
||||
{provide: TranslateService, useValue: translate},
|
||||
{provide: NotificationsService, useValue: notificationsService},
|
||||
{provide: DsoWithdrawnReinstateModalService, useValue: dsoWithdrawnReinstateModalService},
|
||||
{provide: AuthService, useValue: new AuthServiceMock()},
|
||||
{provide: CorrectionTypeDataService, useValue: correctionsDataService},
|
||||
{
|
||||
provide: NgbModal, useValue: {
|
||||
open: () => {/*comment*/
|
||||
@@ -350,7 +367,7 @@ describe('DSOEditMenuResolver', () => {
|
||||
route = dsoRoute(testItem);
|
||||
});
|
||||
|
||||
it('should return Item-specific entries', (done) => {
|
||||
it('should return Item-specific entries', () => {
|
||||
const result = resolver.getDsoMenus(testObject, route, state);
|
||||
combineLatest(result).pipe(map(flatten)).subscribe((menu) => {
|
||||
const orcidEntry = menu.find(entry => entry.id === 'orcid-dso');
|
||||
@@ -371,20 +388,18 @@ describe('DSOEditMenuResolver', () => {
|
||||
expect(claimEntry.active).toBeFalse();
|
||||
expect(claimEntry.visible).toBeFalse();
|
||||
expect(claimEntry.model.type).toEqual(MenuItemType.ONCLICK);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not return Community/Collection-specific entries', (done) => {
|
||||
it('should not return Community/Collection-specific entries', () => {
|
||||
const result = resolver.getDsoMenus(testObject, route, state);
|
||||
combineLatest(result).pipe(map(flatten)).subscribe((menu) => {
|
||||
const subscribeEntry = menu.find(entry => entry.id === 'subscribe');
|
||||
expect(subscribeEntry).toBeFalsy();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return as third part the common list ', (done) => {
|
||||
it('should return as third part the common list ', () => {
|
||||
const result = resolver.getDsoMenus(testObject, route, state);
|
||||
combineLatest(result).pipe(map(flatten)).subscribe((menu) => {
|
||||
const editEntry = menu.find(entry => entry.id === 'edit-dso');
|
||||
@@ -395,7 +410,6 @@ describe('DSOEditMenuResolver', () => {
|
||||
expect((editEntry.model as LinkMenuItemModel).link).toEqual(
|
||||
'/items/test-item-uuid/edit/metadata'
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -8,7 +8,9 @@ import { LinkMenuItemModel } from '../menu/menu-item/models/link.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { OnClickMenuItemModel } from '../menu/menu-item/models/onclick.model';
|
||||
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||
import {
|
||||
getFirstCompletedRemoteData, getRemoteDataPayload,
|
||||
} from '../../core/shared/operators';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||
@@ -21,6 +23,11 @@ import { getDSORoute } from '../../app-routing-paths';
|
||||
import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DsoWithdrawnReinstateModalService, REQUEST_REINSTATE, REQUEST_WITHDRAWN } from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { FindListOptions } from '../../core/data/find-list-options.model';
|
||||
import { RequestParam } from '../../core/cache/models/request-param.model';
|
||||
import { CorrectionTypeDataService } from '../../core/submission/correctiontype-data.service';
|
||||
import { SubscriptionModalComponent } from '../subscriptions/subscription-modal/subscription-modal.component';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
@@ -42,6 +49,9 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
||||
protected researcherProfileService: ResearcherProfileDataService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService,
|
||||
protected dsoWithdrawnReinstateModalService: DsoWithdrawnReinstateModalService,
|
||||
private auth: AuthService,
|
||||
private correctionTypeDataService: CorrectionTypeDataService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -123,14 +133,20 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
||||
*/
|
||||
protected getItemMenu(dso): Observable<MenuSection[]> {
|
||||
if (dso instanceof Item) {
|
||||
const findListTopicOptions: FindListOptions = {
|
||||
searchParams: [new RequestParam('target', dso.uuid)]
|
||||
};
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self),
|
||||
this.dsoVersioningModalService.isNewVersionButtonDisabled(dso),
|
||||
this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create'),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, dso.self),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanClaimItem, dso.self),
|
||||
this.correctionTypeDataService.findByItem(dso.uuid, false).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload())
|
||||
]).pipe(
|
||||
map(([canCreateVersion, disableVersioning, versionTooltip, canSynchronizeWithOrcid, canClaimItem]) => {
|
||||
map(([canCreateVersion, disableVersioning, versionTooltip, canSynchronizeWithOrcid, canClaimItem, correction]) => {
|
||||
const isPerson = this.getDsoType(dso) === 'person';
|
||||
return [
|
||||
{
|
||||
@@ -174,6 +190,34 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
||||
icon: 'hand-paper',
|
||||
index: 3
|
||||
},
|
||||
{
|
||||
id: 'withdrawn-item',
|
||||
active: false,
|
||||
visible: dso.isArchived && correction?.page.some((c) => c.topic === REQUEST_WITHDRAWN),
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text:'item.page.withdrawn',
|
||||
function: () => {
|
||||
this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(dso, 'request-withdrawn', dso.isArchived);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'eye-slash',
|
||||
index: 4
|
||||
},
|
||||
{
|
||||
id: 'reinstate-item',
|
||||
active: false,
|
||||
visible: dso.isWithdrawn && correction?.page.some((c) => c.topic === REQUEST_REINSTATE),
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text:'item.page.reinstate',
|
||||
function: () => {
|
||||
this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(dso, 'request-reinstate', dso.isArchived);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'eye',
|
||||
index: 5
|
||||
}
|
||||
];
|
||||
}),
|
||||
);
|
||||
@@ -263,4 +307,5 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
||||
return menu;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,102 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Router } from '@angular/router';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
import { ItemWithdrawnReinstateModalComponent } from '../../correction-suggestion/withdrawn-reinstate-modal.component';
|
||||
import {
|
||||
QualityAssuranceEventDataService
|
||||
} from '../../../core/notifications/qa/events/quality-assurance-event-data.service';
|
||||
import {
|
||||
QualityAssuranceEventObject
|
||||
} from '../../../core/notifications/qa/models/quality-assurance-event.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { NotificationsService } from '../../notifications/notifications.service';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { Item } from 'src/app/core/shared/item.model';
|
||||
|
||||
export const REQUEST_WITHDRAWN = 'REQUEST/WITHDRAWN';
|
||||
export const REQUEST_REINSTATE = 'REQUEST/REINSTATE';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
/**
|
||||
* Service for managing the withdrawn/reinstate modal for a DSO.
|
||||
*/
|
||||
export class DsoWithdrawnReinstateModalService {
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
protected modalService: NgbModal,
|
||||
protected itemService: ItemDataService,
|
||||
private notificationsService: NotificationsService,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
private translateService: TranslateService,
|
||||
protected qaEventDataService: QualityAssuranceEventDataService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Open the create withdrawn modal for the provided dso
|
||||
*/
|
||||
openCreateWithdrawnReinstateModal(dso: Item, correctionType: 'request-reinstate' | 'request-withdrawn', state: boolean): void {
|
||||
const target = dso.id;
|
||||
// Open modal
|
||||
const activeModal = this.modalService.open(ItemWithdrawnReinstateModalComponent);
|
||||
(activeModal.componentInstance as ItemWithdrawnReinstateModalComponent).setWithdraw(state);
|
||||
(activeModal.componentInstance as ItemWithdrawnReinstateModalComponent).createQAEvent
|
||||
.pipe(
|
||||
take(1)
|
||||
).subscribe(
|
||||
(reasone) => {
|
||||
this.sendQARequest(target, correctionType, reasone);
|
||||
activeModal.close();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a quality assurance request.
|
||||
*
|
||||
* @param target - The target - the item's UUID.
|
||||
* @param correctionType - The type of correction.
|
||||
* @param reason - The reason for the request.
|
||||
* Reloads the current page in order to update the withdrawn/reinstate button.
|
||||
* and desplay a notification box.
|
||||
*/
|
||||
sendQARequest(target: string, correctionType: 'request-reinstate' | 'request-withdrawn', reason: string): void {
|
||||
this.qaEventDataService.postData(target, correctionType, '', reason)
|
||||
.pipe (
|
||||
getFirstCompletedRemoteData()
|
||||
)
|
||||
.subscribe((res: RemoteData<QualityAssuranceEventObject>) => {
|
||||
if (res.hasSucceeded) {
|
||||
const message = (correctionType === 'request-withdrawn')
|
||||
? 'correction-type.manage-relation.action.notification.withdrawn'
|
||||
: 'correction-type.manage-relation.action.notification.reinstate';
|
||||
|
||||
this.notificationsService.success(this.translateService.get(message));
|
||||
this.authorizationService.invalidateAuthorizationsRequestCache();
|
||||
this.reloadPage(true);
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get('correction-type.manage-relation.action.notification.error'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the current page or navigates to a specified URL.
|
||||
* @param self - A boolean indicating whether to reload the current page (true) or navigate to a specified URL (false).
|
||||
* @param urlToNavigateTo - The URL to navigate to if `self` is false.
|
||||
* skipLocationChange:true means dont update the url to / when navigating
|
||||
*/
|
||||
reloadPage(self: boolean, urlToNavigateTo?: string) {
|
||||
const url = self ? this.router.url : urlToNavigateTo;
|
||||
this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
|
||||
this.router.navigate([`/${url}`]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1482,7 +1482,8 @@ export const qualityAssuranceEventObjectMissingPid: QualityAssuranceEventObject
|
||||
funder: null,
|
||||
fundingProgram: null,
|
||||
jurisdiction: null,
|
||||
title: null
|
||||
title: null,
|
||||
reason: 'Missing PID'
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
@@ -1519,7 +1520,8 @@ export const qualityAssuranceEventObjectMissingPid2: QualityAssuranceEventObject
|
||||
funder: null,
|
||||
fundingProgram: null,
|
||||
jurisdiction: null,
|
||||
title: null
|
||||
title: null,
|
||||
reason: 'Missing PID'
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
@@ -1556,7 +1558,8 @@ export const qualityAssuranceEventObjectMissingPid3: QualityAssuranceEventObject
|
||||
funder: null,
|
||||
fundingProgram: null,
|
||||
jurisdiction: null,
|
||||
title: null
|
||||
title: null,
|
||||
reason: 'Missing PID'
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
@@ -1593,7 +1596,8 @@ export const qualityAssuranceEventObjectMissingPid4: QualityAssuranceEventObject
|
||||
funder: null,
|
||||
fundingProgram: null,
|
||||
jurisdiction: null,
|
||||
title: null
|
||||
title: null,
|
||||
reason: 'Missing DOI'
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
@@ -1630,7 +1634,8 @@ export const qualityAssuranceEventObjectMissingPid5: QualityAssuranceEventObject
|
||||
funder: null,
|
||||
fundingProgram: null,
|
||||
jurisdiction: null,
|
||||
title: null
|
||||
title: null,
|
||||
reason: 'Missing PID'
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
@@ -1667,7 +1672,8 @@ export const qualityAssuranceEventObjectMissingPid6: QualityAssuranceEventObject
|
||||
funder: null,
|
||||
fundingProgram: null,
|
||||
jurisdiction: null,
|
||||
title: null
|
||||
title: null,
|
||||
reason: 'Missing PID'
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
@@ -1704,7 +1710,8 @@ export const qualityAssuranceEventObjectMissingAbstract: QualityAssuranceEventOb
|
||||
funder: null,
|
||||
fundingProgram: null,
|
||||
jurisdiction: null,
|
||||
title: null
|
||||
title: null,
|
||||
reason: 'Missing abstract'
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
@@ -1741,6 +1748,7 @@ export const qualityAssuranceEventObjectMissingProjectFound: QualityAssuranceEve
|
||||
funder: 'EC',
|
||||
fundingProgram: 'H2020',
|
||||
jurisdiction: 'EU',
|
||||
reason: 'Project found',
|
||||
title: 'Tracking Papyrus and Parchment Paths: An Archaeological Atlas of Coptic Literature.\nLiterary Texts in their Geographical Context: Production, Copying, Usage, Dissemination and Storage'
|
||||
},
|
||||
_links: {
|
||||
@@ -1778,7 +1786,8 @@ export const qualityAssuranceEventObjectMissingProjectNotFound: QualityAssurance
|
||||
funder: 'EC',
|
||||
fundingProgram: 'H2021',
|
||||
jurisdiction: 'EU',
|
||||
title: 'Tracking Unknown Papyrus and Parchment Paths: An Archaeological Atlas of Coptic Literature.\nLiterary Texts in their Geographical Context: Production, Copying, Usage, Dissemination and Storage'
|
||||
title: 'Tracking Unknown Papyrus and Parchment Paths: An Archaeological Atlas of Coptic Literature.\nLiterary Texts in their Geographical Context: Production, Copying, Usage, Dissemination and Storage',
|
||||
reason: 'Project not found'
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
@@ -1838,8 +1847,10 @@ export function getMockNotificationsStateService(): any {
|
||||
*/
|
||||
export function getMockQualityAssuranceTopicRestService(): QualityAssuranceTopicDataService {
|
||||
return jasmine.createSpyObj('QualityAssuranceTopicDataService', {
|
||||
searchTopicsBySource: jasmine.createSpy('searchTopicsBySource'),
|
||||
getTopic: jasmine.createSpy('getTopic'),
|
||||
searchTopicsByTarget: jasmine.createSpy('searchTopicsByTarget'),
|
||||
searchTopicsBySource: jasmine.createSpy('searchTopicsBySource'),
|
||||
clearFindAllTopicsRequests: jasmine.createSpy('clearFindAllTopicsRequests'),
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -282,7 +282,8 @@ import { NgxPaginationModule } from 'ngx-pagination';
|
||||
import { SplitPipe } from './utils/split.pipe';
|
||||
import { ThemedUserMenuComponent } from './auth-nav-menu/user-menu/themed-user-menu.component';
|
||||
import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component';
|
||||
import { IpV4Validator } from './utils/ipV4.validator';
|
||||
import { QualityAssuranceEventDataService } from '../core/notifications/qa/events/quality-assurance-event-data.service';
|
||||
import { QualityAssuranceSourceDataService } from '../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||
import { DynamicComponentLoaderDirective } from './abstract-component-loader/dynamic-component-loader.directive';
|
||||
import { StartsWithLoaderComponent } from './starts-with/starts-with-loader.component';
|
||||
|
||||
@@ -325,7 +326,7 @@ const PIPES = [
|
||||
BrowserOnlyPipe,
|
||||
MarkdownPipe,
|
||||
ShortNumberPipe,
|
||||
SplitPipe,
|
||||
SplitPipe
|
||||
];
|
||||
|
||||
const COMPONENTS = [
|
||||
@@ -475,7 +476,9 @@ const ENTRY_COMPONENTS = [
|
||||
const PROVIDERS = [
|
||||
TruncatableService,
|
||||
MockAdminGuard,
|
||||
AbstractTrackableComponent
|
||||
AbstractTrackableComponent,
|
||||
QualityAssuranceEventDataService,
|
||||
QualityAssuranceSourceDataService
|
||||
];
|
||||
|
||||
const DIRECTIVES = [
|
||||
@@ -492,8 +495,7 @@ const DIRECTIVES = [
|
||||
MetadataFieldValidator,
|
||||
HoverClassDirective,
|
||||
ContextHelpDirective,
|
||||
IpV4Validator,
|
||||
DynamicComponentLoaderDirective
|
||||
DynamicComponentLoaderDirective,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@@ -1,10 +1,14 @@
|
||||
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'
|
||||
})
|
||||
export class SplitPipe implements PipeTransform {
|
||||
|
||||
transform(value: string, separator: string): string[] {
|
||||
return value.split(separator);
|
||||
}
|
||||
|
@@ -534,7 +534,7 @@
|
||||
|
||||
"admin.quality-assurance.page.title": "Quality Assurance",
|
||||
|
||||
"admin.notifications.source.breadcrumbs": "Quality Assurance Source",
|
||||
"admin.notifications.source.breadcrumbs": "Quality Assurance",
|
||||
|
||||
"admin.access-control.groups.form.tooltip.editGroupPage": "On this page, you can modify the properties and members of a group. In the top section, you can edit the group name and description, unless this is an admin group for a collection or community, in which case the group name and description are auto-generated and cannot be edited. In the following sections, you can edit group membership. See [the wiki](https://wiki.lyrasis.org/display/DSDOC7x/Create+or+manage+a+user+group) for more details.",
|
||||
|
||||
@@ -1954,6 +1954,10 @@
|
||||
|
||||
"item.alerts.withdrawn": "This item has been withdrawn",
|
||||
|
||||
"item.alerts.reinstate-request": "Request reinstate",
|
||||
|
||||
"quality-assurance.event.table.person-who-requested": "Requested by",
|
||||
|
||||
"item.edit.authorizations.heading": "With this editor you can view and alter the policies of an item, plus alter policies of individual item components: bundles and bitstreams. Briefly, an item is a container of bundles, and bundles are containers of bitstreams. Containers usually have ADD/REMOVE/READ/WRITE policies, while bitstreams only have READ/WRITE policies.",
|
||||
|
||||
"item.edit.authorizations.title": "Edit item's Policies",
|
||||
@@ -2432,6 +2436,14 @@
|
||||
|
||||
"item.truncatable-part.show-less": "Collapse",
|
||||
|
||||
"item.qa-event-notification.check.notification-info": "There are {{num}} pending suggestions related to your account",
|
||||
|
||||
"item.qa-event-notification-info.check.button": "View",
|
||||
|
||||
"mydspace.qa-event-notification.check.notification-info": "There are {{num}} pending suggestions related to your account",
|
||||
|
||||
"mydspace.qa-event-notification-info.check.button": "View",
|
||||
|
||||
"workflow-item.search.result.delete-supervision.modal.header": "Delete Supervision Order",
|
||||
|
||||
"workflow-item.search.result.delete-supervision.modal.info": "Are you sure you want to delete Supervision Order",
|
||||
@@ -2536,6 +2548,10 @@
|
||||
|
||||
"item.page.version.create": "Create new version",
|
||||
|
||||
"item.page.withdrawn": "Request a withdrawal for this item",
|
||||
|
||||
"item.page.reinstate": "Request reinstatement",
|
||||
|
||||
"item.page.version.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history",
|
||||
|
||||
"item.page.claim.button": "Claim",
|
||||
@@ -2670,6 +2686,12 @@
|
||||
|
||||
"item.version.create.modal.header": "New version",
|
||||
|
||||
"item.qa.withdrawn.modal.header": "Request withdrawal",
|
||||
|
||||
"item.qa.reinstate.modal.header": "Request reinstate",
|
||||
|
||||
"item.qa.reinstate.create.modal.header": "New version",
|
||||
|
||||
"item.version.create.modal.text": "Create a new version for this item",
|
||||
|
||||
"item.version.create.modal.text.startingFrom": "starting from version {{version}}",
|
||||
@@ -2678,16 +2700,44 @@
|
||||
|
||||
"item.version.create.modal.button.confirm.tooltip": "Create new version",
|
||||
|
||||
"item.qa.withdrawn-reinstate.modal.button.confirm.tooltip": "Send request",
|
||||
|
||||
"qa-withdrown.create.modal.button.confirm": "Withdraw",
|
||||
|
||||
"qa-reinstate.create.modal.button.confirm": "Reinstate",
|
||||
|
||||
"item.version.create.modal.button.cancel": "Cancel",
|
||||
|
||||
"item.qa.withdrawn-reinstate.create.modal.button.cancel": "Cancel",
|
||||
|
||||
"item.version.create.modal.button.cancel.tooltip": "Do not create new version",
|
||||
|
||||
"item.qa.withdrawn-reinstate.create.modal.button.cancel.tooltip": "Do not send request",
|
||||
|
||||
"item.version.create.modal.form.summary.label": "Summary",
|
||||
|
||||
"qa-withdrawn.create.modal.form.summary.label": "You are requesting to withdraw this item",
|
||||
|
||||
"qa-withdrawn.create.modal.form.summary2.label": "Please enter the reason for the withdrawal",
|
||||
|
||||
"qa-reinstate.create.modal.form.summary.label": "You are requesting to reinstate this item",
|
||||
|
||||
"qa-reinstate.create.modal.form.summary2.label": "Please enter the reason for the reinstatment",
|
||||
|
||||
"item.version.create.modal.form.summary.placeholder": "Insert the summary for the new version",
|
||||
|
||||
"qa-withdrown.modal.form.summary.placeholder": "Enter the reason for the withdrawal",
|
||||
|
||||
"qa-reinstate.modal.form.summary.placeholder": "Enter the reason for the reinstatement",
|
||||
|
||||
"item.version.create.modal.submitted.header": "Creating new version...",
|
||||
|
||||
"item.qa.withdrawn.modal.submitted.header": "Sending withdrawn request...",
|
||||
|
||||
"correction-type.manage-relation.action.notification.reinstate": "Reinstate request sent.",
|
||||
|
||||
"correction-type.manage-relation.action.notification.withdrawn": "Withdraw request sent.",
|
||||
|
||||
"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.notification.success": "New version has been created with version number {{version}}",
|
||||
@@ -3216,6 +3266,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",
|
||||
@@ -3230,7 +3282,9 @@
|
||||
|
||||
"quality-assurance.table.actions": "Actions",
|
||||
|
||||
"quality-assurance.button.detail": "Show details",
|
||||
"quality-assurance.source-list.button.detail": "Show topics for {{param}}",
|
||||
|
||||
"quality-assurance.topics-list.button.detail": "Show suggestions for {{param}}",
|
||||
|
||||
"quality-assurance.noTopics": "No topics found.",
|
||||
|
||||
@@ -3256,12 +3310,16 @@
|
||||
|
||||
"quality-assurance.event.table.project-details": "Project details",
|
||||
|
||||
"quality-assurance.event.table.reasons": "Reasons",
|
||||
|
||||
"quality-assurance.event.table.actions": "Actions",
|
||||
|
||||
"quality-assurance.event.action.accept": "Accept suggestion",
|
||||
|
||||
"quality-assurance.event.action.ignore": "Ignore suggestion",
|
||||
|
||||
"quality-assurance.event.action.undo": "DELETE",
|
||||
|
||||
"quality-assurance.event.action.reject": "Reject suggestion",
|
||||
|
||||
"quality-assurance.event.action.import": "Import project and accept suggestion",
|
||||
@@ -3290,6 +3348,8 @@
|
||||
|
||||
"quality-assurance.events.back": "Back to topics",
|
||||
|
||||
"quality-assurance.events.back-to-sources": "Back to sources",
|
||||
|
||||
"quality-assurance.event.table.less": "Show less",
|
||||
|
||||
"quality-assurance.event.table.more": "Show more",
|
||||
@@ -3302,6 +3362,8 @@
|
||||
|
||||
"quality-assurance.event.ignore.description": "This operation can't be undone. Ignore this suggestion?",
|
||||
|
||||
"quality-assurance.event.undo.description": "This operation can't be undone!",
|
||||
|
||||
"quality-assurance.event.reject.description": "This operation can't be undone. Reject this suggestion?",
|
||||
|
||||
"quality-assurance.event.accept.description": "No DSpace project selected. A new project will be created based on the suggestion data.",
|
||||
|
@@ -3535,21 +3535,21 @@
|
||||
// "item.truncatable-part.show-less": "Collapse",
|
||||
"item.truncatable-part.show-less": "Riduci",
|
||||
|
||||
// "item.qa-event-notification.check.notification-info": "There are {{num}} pending review to check",
|
||||
// "item.qa-event-notification.check.notification-info": "There are {{num}} pending suggestions related to your account",
|
||||
// TODO New key - Add a translation
|
||||
"item.qa-event-notification.check.notification-info": "There are {{num}} pending review to check",
|
||||
"item.qa-event-notification.check.notification-info": "There are {{num}} pending suggestions related to your account",
|
||||
|
||||
// "item.qa-event-notification-info.check.button": "Check",
|
||||
// "item.qa-event-notification-info.check.button": "View",
|
||||
// TODO New key - Add a translation
|
||||
"item.qa-event-notification-info.check.button": "Check",
|
||||
"item.qa-event-notification-info.check.button": "View",
|
||||
|
||||
// "mydspace.qa-event-notification.check.notification-info": "There are {{num}} pending review to check",
|
||||
// "mydspace.qa-event-notification.check.notification-info": "There are {{num}} pending suggestions related to your account",
|
||||
// TODO New key - Add a translation
|
||||
"mydspace.qa-event-notification.check.notification-info": "There are {{num}} pending review to check",
|
||||
"mydspace.qa-event-notification.check.notification-info": "There are {{num}} pending suggestions related to your account",
|
||||
|
||||
// "mydspace.qa-event-notification-info.check.button": "Check",
|
||||
// "mydspace.qa-event-notification-info.check.button": "View",
|
||||
// TODO New key - Add a translation
|
||||
"mydspace.qa-event-notification-info.check.button": "Check",
|
||||
"mydspace.qa-event-notification-info.check.button": "View",
|
||||
|
||||
// "workflow-item.search.result.delete-supervision.modal.header": "Delete Supervision Order",
|
||||
"workflow-item.search.result.delete-supervision.modal.header": "Elimina l'ordine di supervisione",
|
||||
@@ -7073,25 +7073,6 @@
|
||||
// "submission.workspace.generic.view-help": "Select this option to view the item's metadata.",
|
||||
"submission.workspace.generic.view-help": "Seleziona questa opzione per vedere i metadata dell'item.",
|
||||
|
||||
// "submission.section.section-coar-notify.dropdown.no-data": "No data available",
|
||||
// TODO New key - a translation
|
||||
"submission.section.section-coar-notify.dropdown.no-data": "No data available",
|
||||
|
||||
// "submission.section.section-coar-notify.dropdown.select-none": "Select none",
|
||||
// TODO New key - a translation
|
||||
"submission.section.section-coar-notify.dropdown.select-none": "Select none",
|
||||
|
||||
// "submission.section.section-coar-notify.small.notification": "Select a service for {{ pattern }} of this item",
|
||||
// TODO New key - a translation
|
||||
"submission.section.section-coar-notify.small.notification": "Select a service for {{ pattern }} of this item",
|
||||
|
||||
// "submission.section.section-coar-notify.selection.description": "Selected service's description:",
|
||||
// TODO New key - a translation
|
||||
"submission.section.section-coar-notify.selection.description": "Selected service's description:",
|
||||
|
||||
// "submission.section.section-coar-notify.notification.error": "The selected service is not suitable for the current item.Please check the description for details about which record can be managed by this service.",
|
||||
// TODO New key - a translation
|
||||
"submission.section.section-coar-notify.notification.error": "The selected service is not suitable for the current item.Please check the description for details about which record can be managed by this service.",
|
||||
|
||||
// "subscriptions.title": "Subscriptions",
|
||||
"subscriptions.title": "Sottoscrizioni",
|
||||
|
BIN
src/assets/images/qa-DSpaceUsers-logo.png
Normal file
BIN
src/assets/images/qa-DSpaceUsers-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.4 KiB |
@@ -109,6 +109,8 @@
|
||||
|
||||
--ds-item-page-img-field-default-inline-height: 24px;
|
||||
|
||||
--ds-qa-logo-width: 100px;
|
||||
|
||||
--ds-process-overview-table-nb-processes-badge-size: 0.5em;
|
||||
--ds-process-overview-table-id-column-width: 120px;
|
||||
--ds-process-overview-table-name-column-width: auto;
|
||||
|
@@ -304,7 +304,7 @@ const DECLARATIONS = [
|
||||
NgxGalleryModule,
|
||||
FormModule,
|
||||
RequestCopyModule,
|
||||
NotificationsModule
|
||||
NotificationsModule,
|
||||
],
|
||||
declarations: DECLARATIONS,
|
||||
})
|
||||
|
Reference in New Issue
Block a user