mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge pull request #2759 from 4Science/CST-12109-WITHDRAWN-REINSTATE-requests
WITHDRAW / REINSTATE requests for an item
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 QUALITY_ASSURANCE_EDIT_PATH = 'quality-assurance';
|
||||||
export const PUBLICATION_CLAIMS_PATH = 'publication-claim';
|
export const PUBLICATION_CLAIMS_PATH = 'publication-claim';
|
||||||
|
|
||||||
export function getQualityAssuranceRoute(id: string) {
|
export function getQualityAssuranceEditRoute() {
|
||||||
return new URLCombiner(getNotificationsModuleRoute(), QUALITY_ASSURANCE_EDIT_PATH, id).toString();
|
return `/${QUALITY_ASSURANCE_EDIT_PATH}`;
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,9 @@ import { AdminQualityAssuranceTopicsPageResolver } from './admin-quality-assuran
|
|||||||
import { AdminQualityAssuranceEventsPageResolver } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.resolver';
|
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 { 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 { 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 { QualityAssuranceBreadcrumbResolver } from '../../core/breadcrumbs/quality-assurance-breadcrumb.resolver';
|
||||||
import { QualityAssuranceBreadcrumbService } from '../../core/breadcrumbs/quality-assurance-breadcrumb.service';
|
import { QualityAssuranceBreadcrumbService } from '../../core/breadcrumbs/quality-assurance-breadcrumb.service';
|
||||||
import {
|
import {
|
||||||
@@ -55,6 +58,21 @@ import {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
canActivate: [ AuthenticatedGuard ],
|
canActivate: [ AuthenticatedGuard ],
|
||||||
|
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/target/:targetId`,
|
||||||
|
component: AdminQualityAssuranceTopicsPageComponent,
|
||||||
|
pathMatch: 'full',
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: I18nBreadcrumbResolver,
|
||||||
|
openaireQualityAssuranceTopicsParams: AdminQualityAssuranceTopicsPageResolver
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'admin.quality-assurance.page.title',
|
||||||
|
breadcrumbKey: 'admin.quality-assurance',
|
||||||
|
showBreadcrumbsFluid: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
canActivate: [ SiteAdministratorGuard ],
|
||||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}`,
|
path: `${QUALITY_ASSURANCE_EDIT_PATH}`,
|
||||||
component: AdminQualityAssuranceSourcePageComponent,
|
component: AdminQualityAssuranceSourcePageComponent,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
import { getAdminModuleRoute } from '../app-routing-paths';
|
import { getAdminModuleRoute } from '../app-routing-paths';
|
||||||
|
import { getQualityAssuranceEditRoute } from './admin-notifications/admin-notifications-routing-paths';
|
||||||
|
|
||||||
export const REGISTRIES_MODULE_PATH = 'registries';
|
export const REGISTRIES_MODULE_PATH = 'registries';
|
||||||
export const NOTIFICATIONS_MODULE_PATH = 'notifications';
|
export const NOTIFICATIONS_MODULE_PATH = 'notifications';
|
||||||
@@ -11,3 +12,7 @@ export function getRegistriesModuleRoute() {
|
|||||||
export function getNotificationsModuleRoute() {
|
export function getNotificationsModuleRoute() {
|
||||||
return new URLCombiner(getAdminModuleRoute(), NOTIFICATIONS_MODULE_PATH).toString();
|
return new URLCombiner(getAdminModuleRoute(), NOTIFICATIONS_MODULE_PATH).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNotificatioQualityAssuranceRoute() {
|
||||||
|
return new URLCombiner(`/${NOTIFICATIONS_MODULE_PATH}`, getQualityAssuranceEditRoute()).toString();
|
||||||
|
}
|
||||||
|
@@ -6,57 +6,62 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
|
|||||||
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
|
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
|
||||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
|
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
|
||||||
import { REGISTRIES_MODULE_PATH, NOTIFICATIONS_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 { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component';
|
||||||
|
import {
|
||||||
|
SiteAdministratorGuard
|
||||||
|
} from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{
|
|
||||||
path: NOTIFICATIONS_MODULE_PATH,
|
|
||||||
loadChildren: () => import('./admin-notifications/admin-notifications.module')
|
|
||||||
.then((m) => m.AdminNotificationsModule),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: REGISTRIES_MODULE_PATH,
|
path: REGISTRIES_MODULE_PATH,
|
||||||
loadChildren: () => import('./admin-registries/admin-registries.module')
|
loadChildren: () => import('./admin-registries/admin-registries.module')
|
||||||
.then((m) => m.AdminRegistriesModule),
|
.then((m) => m.AdminRegistriesModule),
|
||||||
|
canActivate: [SiteAdministratorGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'search',
|
path: 'search',
|
||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
component: AdminSearchPageComponent,
|
component: AdminSearchPageComponent,
|
||||||
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' }
|
data: { title: 'admin.search.title', breadcrumbKey: 'admin.search' },
|
||||||
|
canActivate: [SiteAdministratorGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'workflow',
|
path: 'workflow',
|
||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
component: AdminWorkflowPageComponent,
|
component: AdminWorkflowPageComponent,
|
||||||
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' }
|
data: { title: 'admin.workflow.title', breadcrumbKey: 'admin.workflow' },
|
||||||
|
canActivate: [SiteAdministratorGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'curation-tasks',
|
path: 'curation-tasks',
|
||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
component: AdminCurationTasksComponent,
|
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',
|
path: 'metadata-import',
|
||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
component: MetadataImportPageComponent,
|
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',
|
path: 'batch-import',
|
||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
component: BatchImportPageComponent,
|
component: BatchImportPageComponent,
|
||||||
data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' }
|
data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' },
|
||||||
|
canActivate: [SiteAdministratorGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'system-wide-alert',
|
path: 'system-wide-alert',
|
||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
loadChildren: () => import('../system-wide-alert/system-wide-alert.module').then((m) => m.SystemWideAlertModule),
|
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]
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
|
@@ -132,3 +132,10 @@ export const SUBSCRIPTIONS_MODULE_PATH = 'subscriptions';
|
|||||||
export function getSubscriptionsModuleRoute() {
|
export function getSubscriptionsModuleRoute() {
|
||||||
return `/${SUBSCRIPTIONS_MODULE_PATH}`;
|
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 { NoPreloading, RouterModule } from '@angular/router';
|
|||||||
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||||
|
|
||||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||||
import {
|
|
||||||
SiteAdministratorGuard
|
|
||||||
} from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
|
||||||
import {
|
import {
|
||||||
ACCESS_CONTROL_MODULE_PATH,
|
ACCESS_CONTROL_MODULE_PATH,
|
||||||
ADMIN_MODULE_PATH,
|
ADMIN_MODULE_PATH,
|
||||||
@@ -41,6 +38,7 @@ import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
|||||||
import { SUGGESTION_MODULE_PATH } from './suggestions-page/suggestions-page-routing-paths';
|
import { SUGGESTION_MODULE_PATH } from './suggestions-page/suggestions-page-routing-paths';
|
||||||
import { MenuResolver } from './menu.resolver';
|
import { MenuResolver } from './menu.resolver';
|
||||||
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
|
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
|
||||||
|
import { NOTIFICATIONS_MODULE_PATH } from './admin/admin-routing-paths';
|
||||||
import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard';
|
import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -159,7 +157,13 @@ import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-c
|
|||||||
path: ADMIN_MODULE_PATH,
|
path: ADMIN_MODULE_PATH,
|
||||||
loadChildren: () => import('./admin/admin.module')
|
loadChildren: () => import('./admin/admin.module')
|
||||||
.then((m) => m.AdminModule),
|
.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: 'login',
|
path: 'login',
|
||||||
@@ -247,7 +251,7 @@ import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-c
|
|||||||
.then((m) => m.SubscriptionsPageRoutingModule),
|
.then((m) => m.SubscriptionsPageRoutingModule),
|
||||||
canActivate: [AuthenticatedGuard]
|
canActivate: [AuthenticatedGuard]
|
||||||
},
|
},
|
||||||
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
|
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
], {
|
], {
|
||||||
|
@@ -33,7 +33,7 @@ export class QualityAssuranceBreadcrumbService implements BreadcrumbsProviderSer
|
|||||||
*/
|
*/
|
||||||
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
|
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
|
||||||
const sourceId = key.split(':')[0];
|
const sourceId = key.split(':')[0];
|
||||||
const topicId = key.split(':')[1];
|
const topicId = key.split(':')[2];
|
||||||
|
|
||||||
if (topicId) {
|
if (topicId) {
|
||||||
return this.qualityAssuranceService.getTopic(topicId).pipe(
|
return this.qualityAssuranceService.getTopic(topicId).pipe(
|
||||||
@@ -41,7 +41,7 @@ export class QualityAssuranceBreadcrumbService implements BreadcrumbsProviderSer
|
|||||||
map((topic) => {
|
map((topic) => {
|
||||||
return [new Breadcrumb(this.translationService.instant(this.QUALITY_ASSURANCE_BREADCRUMB_KEY), url),
|
return [new Breadcrumb(this.translationService.instant(this.QUALITY_ASSURANCE_BREADCRUMB_KEY), url),
|
||||||
new Breadcrumb(sourceId, `${url}${sourceId}`),
|
new Breadcrumb(sourceId, `${url}${sourceId}`),
|
||||||
new Breadcrumb(topicId, undefined)];
|
new Breadcrumb(topicId.replace(/[!:]/g, '/'), undefined)];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -185,6 +185,7 @@ import { FlatBrowseDefinition } from './shared/flat-browse-definition.model';
|
|||||||
import { ValueListBrowseDefinition } from './shared/value-list-browse-definition.model';
|
import { ValueListBrowseDefinition } from './shared/value-list-browse-definition.model';
|
||||||
import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition';
|
import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition';
|
||||||
import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model';
|
import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model';
|
||||||
|
import { CorrectionTypeDataService } from './submission/correctiontype-data.service';
|
||||||
import { SuggestionTarget } from './suggestion-notifications/models/suggestion-target.model';
|
import { SuggestionTarget } from './suggestion-notifications/models/suggestion-target.model';
|
||||||
import { SuggestionSource } from './suggestion-notifications/models/suggestion-source.model';
|
import { SuggestionSource } from './suggestion-notifications/models/suggestion-source.model';
|
||||||
|
|
||||||
@@ -309,7 +310,8 @@ const PROVIDERS = [
|
|||||||
OrcidAuthService,
|
OrcidAuthService,
|
||||||
OrcidQueueDataService,
|
OrcidQueueDataService,
|
||||||
OrcidHistoryDataService,
|
OrcidHistoryDataService,
|
||||||
SupervisionOrderDataService
|
SupervisionOrderDataService,
|
||||||
|
CorrectionTypeDataService
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { find, take } from 'rxjs/operators';
|
import { find, switchMap, take } from 'rxjs/operators';
|
||||||
import { ReplaceOperation } from 'fast-json-patch';
|
import { ReplaceOperation } from 'fast-json-patch';
|
||||||
|
|
||||||
import { HALEndpointService } from '../../../shared/hal-endpoint.service';
|
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 { DefaultChangeAnalyzer } from '../../../data/default-change-analyzer.service';
|
||||||
import { hasValue } from '../../../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
import { DeleteByIDRequest, PostRequest } from '../../../data/request.models';
|
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.
|
* The service handling all Quality Assurance topic REST requests.
|
||||||
@@ -84,6 +89,16 @@ export class QualityAssuranceEventDataService extends IdentifiableDataService<Qu
|
|||||||
return this.searchData.searchBy('findByTopic', options, true, true, ...linksToFollow);
|
return this.searchData.searchBy('findByTopic', options, true, true, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service for retrieving Quality Assurance events by topic and target.
|
||||||
|
* @param options (Optional) The search options to use when retrieving the events.
|
||||||
|
* @param linksToFollow (Optional) The links to follow when retrieving the events.
|
||||||
|
* @returns An observable of the remote data containing the paginated list of Quality Assurance events.
|
||||||
|
*/
|
||||||
|
public searchEventsByTopic(options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<QualityAssuranceEventObject>[]): Observable<RemoteData<PaginatedList<QualityAssuranceEventObject>>> {
|
||||||
|
return this.searchData.searchBy('findByTopic', options, true, true, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear findByTopic requests from cache
|
* Clear findByTopic requests from cache
|
||||||
*/
|
*/
|
||||||
@@ -200,4 +215,38 @@ export class QualityAssuranceEventDataService extends IdentifiableDataService<Qu
|
|||||||
|
|
||||||
return this.rdbService.buildFromRequestUUID<QualityAssuranceEventObject>(requestId);
|
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;
|
type: string;
|
||||||
|
|
||||||
|
reason: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value suggested by Notifications
|
* The value suggested by Notifications
|
||||||
*/
|
*/
|
||||||
|
@@ -16,6 +16,7 @@ import { PaginatedList } from '../../../data/paginated-list.model';
|
|||||||
import { FindListOptions } from '../../../data/find-list-options.model';
|
import { FindListOptions } from '../../../data/find-list-options.model';
|
||||||
import { IdentifiableDataService } from '../../../data/base/identifiable-data.service';
|
import { IdentifiableDataService } from '../../../data/base/identifiable-data.service';
|
||||||
import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
||||||
|
import { SearchData, SearchDataImpl } from '../../../data/base/search-data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service handling all Quality Assurance source REST requests.
|
* The service handling all Quality Assurance source REST requests.
|
||||||
@@ -25,6 +26,9 @@ import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
|||||||
export class QualityAssuranceSourceDataService extends IdentifiableDataService<QualityAssuranceSourceObject> {
|
export class QualityAssuranceSourceDataService extends IdentifiableDataService<QualityAssuranceSourceObject> {
|
||||||
|
|
||||||
private findAllData: FindAllData<QualityAssuranceSourceObject>;
|
private findAllData: FindAllData<QualityAssuranceSourceObject>;
|
||||||
|
private searchAllData: SearchData<QualityAssuranceSourceObject>;
|
||||||
|
|
||||||
|
private searchByTargetMethod = 'byTarget';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize service variables
|
* Initialize service variables
|
||||||
@@ -43,6 +47,7 @@ export class QualityAssuranceSourceDataService extends IdentifiableDataService<Q
|
|||||||
) {
|
) {
|
||||||
super('qualityassurancesources', requestService, rdbService, objectCache, halService);
|
super('qualityassurancesources', requestService, rdbService, objectCache, halService);
|
||||||
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
|
this.searchAllData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,4 +89,16 @@ export class QualityAssuranceSourceDataService extends IdentifiableDataService<Q
|
|||||||
public getSource(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<QualityAssuranceSourceObject>[]): Observable<RemoteData<QualityAssuranceSourceObject>> {
|
public getSource(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<QualityAssuranceSourceObject>[]): Observable<RemoteData<QualityAssuranceSourceObject>> {
|
||||||
return this.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a paginated list of QualityAssuranceSourceObject objects that are associated with a given target object.
|
||||||
|
* @param options The options for the search query.
|
||||||
|
* @param useCachedVersionIfAvailable Whether to use a cached version of the data if available.
|
||||||
|
* @param reRequestOnStale Whether to re-request the data if the cached version is stale.
|
||||||
|
* @param linksToFollow The links to follow to retrieve the data.
|
||||||
|
* @returns An observable that emits a RemoteData object containing the paginated list of QualityAssuranceSourceObject objects.
|
||||||
|
*/
|
||||||
|
public getSourcesByTarget(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<QualityAssuranceSourceObject>[]): Observable<RemoteData<PaginatedList<QualityAssuranceSourceObject>>> {
|
||||||
|
return this.searchAllData.searchBy(this.searchByTargetMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -83,20 +83,27 @@ describe('QualityAssuranceTopicDataService', () => {
|
|||||||
|
|
||||||
spyOn((service as any).findAllData, 'findAll').and.callThrough();
|
spyOn((service as any).findAllData, 'findAll').and.callThrough();
|
||||||
spyOn((service as any), 'findById').and.callThrough();
|
spyOn((service as any), 'findById').and.callThrough();
|
||||||
|
spyOn((service as any).searchData, 'searchBy').and.callThrough();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getTopics', () => {
|
describe('searchTopicsByTarget', () => {
|
||||||
it('should call findListByHref', (done) => {
|
it('should call searchData.searchBy with the correct parameters', () => {
|
||||||
service.getTopics().subscribe(
|
const options = { elementsPerPage: 10 };
|
||||||
(res) => {
|
const useCachedVersionIfAvailable = true;
|
||||||
expect((service as any).findAllData.findAll).toHaveBeenCalledWith({}, true, true);
|
const reRequestOnStale = true;
|
||||||
}
|
|
||||||
|
service.searchTopicsByTarget(options, useCachedVersionIfAvailable, reRequestOnStale);
|
||||||
|
|
||||||
|
expect((service as any).searchData.searchBy).toHaveBeenCalledWith(
|
||||||
|
'byTarget',
|
||||||
|
options,
|
||||||
|
useCachedVersionIfAvailable,
|
||||||
|
reRequestOnStale
|
||||||
);
|
);
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a RemoteData<PaginatedList<QualityAssuranceTopicObject>> for the object with the given URL', () => {
|
it('should return a RemoteData<PaginatedList<QualityAssuranceTopicObject>> for the object with the given URL', () => {
|
||||||
const result = service.getTopics();
|
const result = service.searchTopicsByTarget();
|
||||||
const expected = cold('(a)', {
|
const expected = cold('(a)', {
|
||||||
a: paginatedListRD
|
a: paginatedListRD
|
||||||
});
|
});
|
||||||
|
@@ -15,6 +15,7 @@ import { FindListOptions } from '../../../data/find-list-options.model';
|
|||||||
import { IdentifiableDataService } from '../../../data/base/identifiable-data.service';
|
import { IdentifiableDataService } from '../../../data/base/identifiable-data.service';
|
||||||
import { dataService } from '../../../data/base/data-service.decorator';
|
import { dataService } from '../../../data/base/data-service.decorator';
|
||||||
import { QUALITY_ASSURANCE_TOPIC_OBJECT } from '../models/quality-assurance-topic-object.resource-type';
|
import { QUALITY_ASSURANCE_TOPIC_OBJECT } from '../models/quality-assurance-topic-object.resource-type';
|
||||||
|
import { SearchData, SearchDataImpl } from '../../../data/base/search-data';
|
||||||
import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,6 +26,10 @@ import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
|||||||
export class QualityAssuranceTopicDataService extends IdentifiableDataService<QualityAssuranceTopicObject> {
|
export class QualityAssuranceTopicDataService extends IdentifiableDataService<QualityAssuranceTopicObject> {
|
||||||
|
|
||||||
private findAllData: FindAllData<QualityAssuranceTopicObject>;
|
private findAllData: FindAllData<QualityAssuranceTopicObject>;
|
||||||
|
private searchData: SearchData<QualityAssuranceTopicObject>;
|
||||||
|
|
||||||
|
private searchByTargetMethod = 'byTarget';
|
||||||
|
private searchBySourceMethod = 'bySource';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize service variables
|
* Initialize service variables
|
||||||
@@ -43,23 +48,31 @@ export class QualityAssuranceTopicDataService extends IdentifiableDataService<Qu
|
|||||||
) {
|
) {
|
||||||
super('qualityassurancetopics', requestService, rdbService, objectCache, halService);
|
super('qualityassurancetopics', requestService, rdbService, objectCache, halService);
|
||||||
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
|
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the list of Quality Assurance topics.
|
* Search for Quality Assurance topics.
|
||||||
*
|
* @param options The search options.
|
||||||
* @param options Find list options object.
|
* @param useCachedVersionIfAvailable Whether to use cached version if available.
|
||||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
* @param reRequestOnStale Whether to re-request on stale.
|
||||||
* no valid cached version. Defaults to true
|
* @param linksToFollow The links to follow.
|
||||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
* @returns An observable of remote data containing a paginated list of Quality Assurance topics.
|
||||||
* requested after the response becomes stale
|
|
||||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
|
||||||
*
|
|
||||||
* @return Observable<RemoteData<PaginatedList<QualityAssuranceTopicObject>>>
|
|
||||||
* The list of Quality Assurance topics.
|
|
||||||
*/
|
*/
|
||||||
public getTopics(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<QualityAssuranceTopicObject>[]): Observable<RemoteData<PaginatedList<QualityAssuranceTopicObject>>> {
|
public searchTopicsByTarget(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<QualityAssuranceTopicObject>[]): Observable<RemoteData<PaginatedList<QualityAssuranceTopicObject>>> {
|
||||||
return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.searchData.searchBy(this.searchByTargetMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for quality assurance topics by source.
|
||||||
|
* @param options The search options.
|
||||||
|
* @param useCachedVersionIfAvailable Whether to use a cached version if available.
|
||||||
|
* @param reRequestOnStale Whether to re-request the data if it's stale.
|
||||||
|
* @param linksToFollow The links to follow.
|
||||||
|
* @returns An observable of the remote data containing the paginated list of quality assurance topics.
|
||||||
|
*/
|
||||||
|
public searchTopicsBySource(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<QualityAssuranceTopicObject>[]): Observable<RemoteData<PaginatedList<QualityAssuranceTopicObject>>> {
|
||||||
|
return this.searchData.searchBy(this.searchBySourceMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
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">
|
<ds-alert [type]="AlertTypeEnum.Warning">
|
||||||
<div class="d-flex justify-content-between flex-wrap">
|
<div class="d-flex justify-content-between flex-wrap">
|
||||||
<span class="align-self-center">{{'item.alerts.withdrawn' | translate}}</span>
|
<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 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>
|
</div>
|
||||||
</ds-alert>
|
</ds-alert>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,16 +4,41 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { By } from '@angular/platform-browser';
|
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', () => {
|
describe('ItemAlertsComponent', () => {
|
||||||
let component: ItemAlertsComponent;
|
let component: ItemAlertsComponent;
|
||||||
let fixture: ComponentFixture<ItemAlertsComponent>;
|
let fixture: ComponentFixture<ItemAlertsComponent>;
|
||||||
let item: Item;
|
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(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
authorizationService = jasmine.createSpyObj('authorizationService', ['isAuthorized']);
|
||||||
|
dsoWithdrawnReinstateModalService = jasmine.createSpyObj('dsoWithdrawnReinstateModalService', ['openCreateWithdrawnReinstateModal']);
|
||||||
|
correctionTypeDataService = jasmine.createSpyObj('correctionTypeDataService', ['findByItem']);
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ItemAlertsComponent],
|
declarations: [ItemAlertsComponent],
|
||||||
imports: [TranslateModule.forRoot()],
|
imports: [TranslateModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
|
{ provide: DsoWithdrawnReinstateModalService, useValue: dsoWithdrawnReinstateModalService },
|
||||||
|
{ provide: CorrectionTypeDataService, useValue: correctionTypeDataService }
|
||||||
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
@@ -21,7 +46,9 @@ describe('ItemAlertsComponent', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(ItemAlertsComponent);
|
fixture = TestBed.createComponent(ItemAlertsComponent);
|
||||||
|
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
component.item = itemMock;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -61,6 +88,7 @@ describe('ItemAlertsComponent', () => {
|
|||||||
isWithdrawn: true
|
isWithdrawn: true
|
||||||
});
|
});
|
||||||
component.item = item;
|
component.item = item;
|
||||||
|
(correctionTypeDataService.findByItem).and.returnValue(createSuccessfulRemoteDataObject$([]));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,6 +104,7 @@ describe('ItemAlertsComponent', () => {
|
|||||||
isWithdrawn: false
|
isWithdrawn: false
|
||||||
});
|
});
|
||||||
component.item = item;
|
component.item = item;
|
||||||
|
(correctionTypeDataService.findByItem).and.returnValue(createSuccessfulRemoteDataObject$([]));
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -84,4 +113,43 @@ describe('ItemAlertsComponent', () => {
|
|||||||
expect(privateWarning).toBeNull();
|
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 { Component, Input } from '@angular/core';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { AlertType } from '../../shared/alert/alert-type';
|
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({
|
@Component({
|
||||||
selector: 'ds-item-alerts',
|
selector: 'ds-item-alerts',
|
||||||
@@ -21,4 +27,37 @@ export class ItemAlertsComponent {
|
|||||||
* @type {AlertType}
|
* @type {AlertType}
|
||||||
*/
|
*/
|
||||||
public AlertTypeEnum = 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -60,6 +60,7 @@ import { ThemedItemAlertsComponent } from './alerts/themed-item-alerts.component
|
|||||||
import {
|
import {
|
||||||
ThemedFullFileSectionComponent
|
ThemedFullFileSectionComponent
|
||||||
} from './full/field-components/file-section/themed-full-file-section.component';
|
} from './full/field-components/file-section/themed-full-file-section.component';
|
||||||
|
import { QaEventNotificationComponent } from './simple/qa-event-notification/qa-event-notification.component';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -103,6 +104,7 @@ const DECLARATIONS = [
|
|||||||
ItemAlertsComponent,
|
ItemAlertsComponent,
|
||||||
ThemedItemAlertsComponent,
|
ThemedItemAlertsComponent,
|
||||||
BitstreamRequestACopyPageComponent,
|
BitstreamRequestACopyPageComponent,
|
||||||
|
QaEventNotificationComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -30,11 +30,15 @@ import { RelatedItemsComponent } from './simple/related-items/related-items-comp
|
|||||||
import {
|
import {
|
||||||
ThemedMetadataRepresentationListComponent
|
ThemedMetadataRepresentationListComponent
|
||||||
} from './simple/metadata-representation-list/themed-metadata-representation-list.component';
|
} 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';
|
import { ItemPageImgFieldComponent } from './simple/field-components/specific-field/img/item-page-img-field.component';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
ItemVersionsDeleteModalComponent,
|
ItemVersionsDeleteModalComponent,
|
||||||
ItemVersionsSummaryModalComponent,
|
ItemVersionsSummaryModalComponent,
|
||||||
|
ItemWithdrawnReinstateModalComponent
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="itemRD?.payload as item">
|
<div *ngIf="itemRD?.payload as item">
|
||||||
<ds-themed-item-alerts [item]="item"></ds-themed-item-alerts>
|
<ds-themed-item-alerts [item]="item"></ds-themed-item-alerts>
|
||||||
|
<ds-qa-event-notification [item]="item"></ds-qa-event-notification>
|
||||||
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
|
||||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||||
<ds-listable-object-component-loader *ngIf="!item.isWithdrawn || (isAdmin$|async)" [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
<ds-listable-object-component-loader *ngIf="!item.isWithdrawn || (isAdmin$|async)" [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
<button [routerLink]="[ getQualityAssuranceRoute(), (source.id | dsSplit: ':')[0], 'target', item.id]"
|
||||||
|
[queryParams]="{ forward: true }"
|
||||||
|
class="btn btn-primary align-self-center">
|
||||||
|
{{'item.qa-event-notification-info.check.button' | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
@@ -0,0 +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;
|
||||||
|
}
|
@@ -0,0 +1,73 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { QaEventNotificationComponent } from './qa-event-notification.component';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
|
import { createPaginatedList } from '../../../shared/testing/utils.test';
|
||||||
|
import { QualityAssuranceSourceObject } from '../../../core/notifications/qa/models/quality-assurance-source.model';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||||
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||||
|
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { HALEndpointService } from '../../../core/shared/hal-endpoint.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 = 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: () => objPL
|
||||||
|
};
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CommonModule, TranslateModule.forRoot()],
|
||||||
|
declarations: [QaEventNotificationComponent, SplitPipe],
|
||||||
|
providers: [
|
||||||
|
{ provide: QualityAssuranceSourceDataService, useValue: qualityAssuranceSourceDataServiceStub },
|
||||||
|
{ provide: RequestService, useValue: {} },
|
||||||
|
{ provide: NotificationsService, useValue: {} },
|
||||||
|
{ provide: HALEndpointService, useValue: new HALEndpointServiceStub('test') },
|
||||||
|
ObjectCacheService,
|
||||||
|
RemoteDataBuildService,
|
||||||
|
provideMockStore({})
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
fixture = TestBed.createComponent(QaEventNotificationComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.item = item;
|
||||||
|
component.sources$ = of([obj]);
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display sources if present', () => {
|
||||||
|
const alertElements = fixture.debugElement.queryAll(By.css('.alert'));
|
||||||
|
expect(alertElements.length).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the quality assurance route when getQualityAssuranceRoute is called', () => {
|
||||||
|
const route = component.getQualityAssuranceRoute();
|
||||||
|
expect(route).toBe('/notifications/quality-assurance');
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,76 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
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 { QualityAssuranceSourceDataService } from '../../../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||||
|
import { QualityAssuranceSourceObject } from '../../../core/notifications/qa/models/quality-assurance-source.model';
|
||||||
|
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',
|
||||||
|
templateUrl: './qa-event-notification.component.html',
|
||||||
|
styleUrls: ['./qa-event-notification.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
providers: [QualityAssuranceSourceDataService]
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component for displaying quality assurance event notifications for an item.
|
||||||
|
*/
|
||||||
|
export class QaEventNotificationComponent implements OnChanges {
|
||||||
|
/**
|
||||||
|
* The item to display quality assurance event notifications for.
|
||||||
|
*/
|
||||||
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An observable that emits an array of QualityAssuranceSourceObject.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
* Note: sourceId is composed as: id: "sourceName:<target>"
|
||||||
|
*/
|
||||||
|
getQualityAssuranceSources$(): Observable<QualityAssuranceSourceObject[]> {
|
||||||
|
const findListTopicOptions: FindListOptions = {
|
||||||
|
searchParams: [new RequestParam('target', this.item.uuid)]
|
||||||
|
};
|
||||||
|
return this.qualityAssuranceSourceDataService.getSourcesByTarget(findListTopicOptions, false)
|
||||||
|
.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
map((data: RemoteData<PaginatedList<QualityAssuranceSourceObject>>) => {
|
||||||
|
if (data.hasSucceeded) {
|
||||||
|
return data.payload.page;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}),
|
||||||
|
catchError(() => [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the quality assurance route.
|
||||||
|
* @returns The quality assurance route.
|
||||||
|
*/
|
||||||
|
getQualityAssuranceRoute(): string {
|
||||||
|
return getNotificatioQualityAssuranceRoute();
|
||||||
|
}
|
||||||
|
}
|
@@ -171,7 +171,8 @@ export class MenuResolver implements Resolve<boolean> {
|
|||||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanSubmit),
|
this.authorizationService.isAuthorized(FeatureID.CanSubmit),
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanEditItem),
|
this.authorizationService.isAuthorized(FeatureID.CanEditItem),
|
||||||
]).subscribe(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin, canSubmit, canEditItem]) => {
|
this.authorizationService.isAuthorized(FeatureID.CanSeeQA)
|
||||||
|
]).subscribe(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin, canSubmit, canEditItem, canSeeQa]) => {
|
||||||
const newSubMenuList = [
|
const newSubMenuList = [
|
||||||
{
|
{
|
||||||
id: 'new_community',
|
id: 'new_community',
|
||||||
@@ -362,6 +363,40 @@ export class MenuResolver implements Resolve<boolean> {
|
|||||||
icon: 'heartbeat',
|
icon: 'heartbeat',
|
||||||
index: 11
|
index: 11
|
||||||
},
|
},
|
||||||
|
/* Notifications */
|
||||||
|
{
|
||||||
|
id: 'notifications',
|
||||||
|
active: false,
|
||||||
|
visible: canSeeQa || isSiteAdmin,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.TEXT,
|
||||||
|
text: 'menu.section.notifications'
|
||||||
|
} as TextMenuItemModel,
|
||||||
|
icon: 'bell',
|
||||||
|
index: 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'notifications_quality-assurance',
|
||||||
|
parentID: 'notifications',
|
||||||
|
active: false,
|
||||||
|
visible: canSeeQa,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.LINK,
|
||||||
|
text: 'menu.section.quality-assurance',
|
||||||
|
link: '/notifications/quality-assurance'
|
||||||
|
} as LinkMenuItemModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'notifications_publication-claim',
|
||||||
|
parentID: 'notifications',
|
||||||
|
active: false,
|
||||||
|
visible: isSiteAdmin,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.LINK,
|
||||||
|
text: 'menu.section.notifications_publication-claim',
|
||||||
|
link: '/notifications/' + PUBLICATION_CLAIMS_PATH
|
||||||
|
} as LinkMenuItemModel,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||||
shouldPersistOnRouteChange: true
|
shouldPersistOnRouteChange: true
|
||||||
@@ -531,46 +566,9 @@ export class MenuResolver implements Resolve<boolean> {
|
|||||||
* Create menu sections dependent on whether or not the current user is a site administrator
|
* Create menu sections dependent on whether or not the current user is a site administrator
|
||||||
*/
|
*/
|
||||||
createSiteAdministratorMenuSections() {
|
createSiteAdministratorMenuSections() {
|
||||||
combineLatest([
|
this.authorizationService.isAuthorized(FeatureID.AdministratorOf)
|
||||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
.subscribe((authorized) => {
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanSeeQA)
|
|
||||||
])
|
|
||||||
.subscribe(([authorized, canSeeQA]) => {
|
|
||||||
const menuList = [
|
const menuList = [
|
||||||
/* Notifications */
|
|
||||||
{
|
|
||||||
id: 'notifications',
|
|
||||||
active: false,
|
|
||||||
visible: authorized && canSeeQA,
|
|
||||||
model: {
|
|
||||||
type: MenuItemType.TEXT,
|
|
||||||
text: 'menu.section.notifications'
|
|
||||||
} as TextMenuItemModel,
|
|
||||||
icon: 'bell',
|
|
||||||
index: 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'notifications_quality-assurance',
|
|
||||||
parentID: 'notifications',
|
|
||||||
active: false,
|
|
||||||
visible: authorized,
|
|
||||||
model: {
|
|
||||||
type: MenuItemType.LINK,
|
|
||||||
text: 'menu.section.quality-assurance',
|
|
||||||
link: '/admin/notifications/quality-assurance'
|
|
||||||
} as LinkMenuItemModel,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'notifications_publication-claim',
|
|
||||||
parentID: 'notifications',
|
|
||||||
active: false,
|
|
||||||
visible: authorized,
|
|
||||||
model: {
|
|
||||||
type: MenuItemType.LINK,
|
|
||||||
text: 'menu.section.notifications_publication-claim',
|
|
||||||
link: '/admin/notifications/' + PUBLICATION_CLAIMS_PATH
|
|
||||||
} as LinkMenuItemModel,
|
|
||||||
},
|
|
||||||
/* Admin Search */
|
/* Admin Search */
|
||||||
{
|
{
|
||||||
id: 'admin_search',
|
id: 'admin_search',
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<ds-my-dspace-new-submission *dsShowOnlyForRole="[roleTypeEnum.Submitter]"></ds-my-dspace-new-submission>
|
|
||||||
<ds-suggestions-notification></ds-suggestions-notification>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ds-themed-search *ngIf="configuration && context"
|
<ds-themed-search *ngIf="configuration && context"
|
||||||
|
@@ -15,6 +15,9 @@ import { MyDSpaceNewExternalDropdownComponent } from './my-dspace-new-submission
|
|||||||
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
|
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
|
||||||
import { SearchModule } from '../shared/search/search.module';
|
import { SearchModule } from '../shared/search/search.module';
|
||||||
import { UploadModule } from '../shared/upload/upload.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 { NotificationsModule } from '../notifications/notifications.module';
|
||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
@@ -23,7 +26,8 @@ const DECLARATIONS = [
|
|||||||
MyDSpaceNewSubmissionComponent,
|
MyDSpaceNewSubmissionComponent,
|
||||||
CollectionSelectorComponent,
|
CollectionSelectorComponent,
|
||||||
MyDSpaceNewSubmissionDropdownComponent,
|
MyDSpaceNewSubmissionDropdownComponent,
|
||||||
MyDSpaceNewExternalDropdownComponent
|
MyDSpaceNewExternalDropdownComponent,
|
||||||
|
MyDspaceQaEventsNotificationsComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
<ng-container *ngIf="(sources$ | async)?.length > 0">
|
||||||
|
<ng-container *ngFor="let source of sources$ | async">
|
||||||
|
<div
|
||||||
|
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 } }}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
[routerLink]="[getQualityAssuranceRoute(), source.id]"
|
||||||
|
[queryParams]="{ forward: true }"
|
||||||
|
class="btn btn-primary align-self-center"
|
||||||
|
>
|
||||||
|
{{ "mydspace.qa-event-notification-info.check.button" | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
@@ -0,0 +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;
|
||||||
|
}
|
@@ -0,0 +1,46 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
|
import { QualityAssuranceSourceDataService } from '../../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||||
|
import { getFirstCompletedRemoteData, getPaginatedListPayload, getRemoteDataPayload } from '../../core/shared/operators';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { QualityAssuranceSourceObject } from './../../core/notifications/qa/models/quality-assurance-source.model';
|
||||||
|
import { getNotificatioQualityAssuranceRoute } from '../../admin/admin-routing-paths';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-my-dspace-qa-events-notifications',
|
||||||
|
templateUrl: './my-dspace-qa-events-notifications.component.html',
|
||||||
|
styleUrls: ['./my-dspace-qa-events-notifications.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class MyDspaceQaEventsNotificationsComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* An Observable that emits an array of QualityAssuranceSourceObject.
|
||||||
|
*/
|
||||||
|
sources$: Observable<QualityAssuranceSourceObject[]> = of([]);
|
||||||
|
|
||||||
|
constructor(private qualityAssuranceSourceDataService: QualityAssuranceSourceDataService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.getSources();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieves the sources for Quality Assurance.
|
||||||
|
* @returns An Observable of the sources for Quality Assurance.
|
||||||
|
* @throws An error if the retrieval of Quality Assurance sources fails.
|
||||||
|
*/
|
||||||
|
getSources() {
|
||||||
|
this.sources$ = this.qualityAssuranceSourceDataService.getSources()
|
||||||
|
.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
getPaginatedListPayload(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the quality assurance route.
|
||||||
|
* @returns The quality assurance route.
|
||||||
|
*/
|
||||||
|
getQualityAssuranceRoute(): string {
|
||||||
|
return getNotificatioQualityAssuranceRoute();
|
||||||
|
}
|
||||||
|
}
|
@@ -271,8 +271,8 @@ describe('NotificationsStateService', () => {
|
|||||||
it('Should call store.dispatch', () => {
|
it('Should call store.dispatch', () => {
|
||||||
const elementsPerPage = 3;
|
const elementsPerPage = 3;
|
||||||
const currentPage = 1;
|
const currentPage = 1;
|
||||||
const action = new RetrieveAllTopicsAction(elementsPerPage, currentPage);
|
const action = new RetrieveAllTopicsAction(elementsPerPage, currentPage, 'source', 'target');
|
||||||
service.dispatchRetrieveQualityAssuranceTopics(elementsPerPage, currentPage);
|
service.dispatchRetrieveQualityAssuranceTopics(elementsPerPage, currentPage, 'source', 'target');
|
||||||
expect(serviceAsAny.store.dispatch).toHaveBeenCalledWith(action);
|
expect(serviceAsAny.store.dispatch).toHaveBeenCalledWith(action);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -118,8 +118,8 @@ export class NotificationsStateService {
|
|||||||
* @param currentPage
|
* @param currentPage
|
||||||
* The number of the current page.
|
* The number of the current page.
|
||||||
*/
|
*/
|
||||||
public dispatchRetrieveQualityAssuranceTopics(elementsPerPage: number, currentPage: number): void {
|
public dispatchRetrieveQualityAssuranceTopics(elementsPerPage: number, currentPage: number, sourceId: string, targteId?: string): void {
|
||||||
this.store.dispatch(new RetrieveAllTopicsAction(elementsPerPage, currentPage));
|
this.store.dispatch(new RetrieveAllTopicsAction(elementsPerPage, currentPage, sourceId, targteId));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quality Assurance source
|
// Quality Assurance source
|
||||||
|
@@ -26,6 +26,7 @@ import { QualityAssuranceSourceService } from './qa/source/quality-assurance-sou
|
|||||||
import {
|
import {
|
||||||
QualityAssuranceSourceDataService
|
QualityAssuranceSourceDataService
|
||||||
} from '../core/notifications/qa/source/quality-assurance-source-data.service';
|
} from '../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||||
|
import { EPersonDataComponent } from './qa/events/ePerson-data/ePerson-data.component';
|
||||||
import { PublicationClaimComponent } from '../suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component';
|
import { PublicationClaimComponent } from '../suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component';
|
||||||
import { SuggestionActionsComponent } from '../suggestion-notifications/suggestion-actions/suggestion-actions.component';
|
import { SuggestionActionsComponent } from '../suggestion-notifications/suggestion-actions/suggestion-actions.component';
|
||||||
import {
|
import {
|
||||||
@@ -65,6 +66,7 @@ const COMPONENTS = [
|
|||||||
QualityAssuranceTopicsComponent,
|
QualityAssuranceTopicsComponent,
|
||||||
QualityAssuranceEventsComponent,
|
QualityAssuranceEventsComponent,
|
||||||
QualityAssuranceSourceComponent,
|
QualityAssuranceSourceComponent,
|
||||||
|
EPersonDataComponent,
|
||||||
PublicationClaimComponent,
|
PublicationClaimComponent,
|
||||||
SuggestionActionsComponent,
|
SuggestionActionsComponent,
|
||||||
SuggestionListElementComponent,
|
SuggestionListElementComponent,
|
||||||
@@ -100,7 +102,7 @@ const PROVIDERS = [
|
|||||||
declarations: [
|
declarations: [
|
||||||
...COMPONENTS,
|
...COMPONENTS,
|
||||||
...DIRECTIVES,
|
...DIRECTIVES,
|
||||||
...ENTRY_COMPONENTS
|
...ENTRY_COMPONENTS,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
...PROVIDERS
|
...PROVIDERS
|
||||||
@@ -110,7 +112,7 @@ const PROVIDERS = [
|
|||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
...COMPONENTS,
|
...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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -33,12 +33,17 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="col" class="trust-col">{{'quality-assurance.event.table.trust' | translate}}</th>
|
<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 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">
|
<th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') !== -1" scope="col" class="content-col">
|
||||||
{{'quality-assurance.event.table.project-details' | translate}}
|
{{'quality-assurance.event.table.project-details' | translate}}
|
||||||
</th>
|
</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>
|
<th scope="col" class="button-col">{{'quality-assurance.event.table.actions' | translate}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -62,7 +67,8 @@
|
|||||||
</p>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td *ngIf="showTopic.indexOf('/SUBJECT') !== -1">
|
<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>
|
||||||
<td *ngIf="showTopic.indexOf('/ABSTRACT') !== -1">
|
<td *ngIf="showTopic.indexOf('/ABSTRACT') !== -1">
|
||||||
<p class="abstract-container" [class.show]="showMore">
|
<p class="abstract-container" [class.show]="showMore">
|
||||||
@@ -75,6 +81,23 @@
|
|||||||
{{ (showMore ? 'quality-assurance.event.table.less': 'quality-assurance.event.table.more') | translate }}
|
{{ (showMore ? 'quality-assurance.event.table.less': 'quality-assurance.event.table.more') | translate }}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</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">
|
<td *ngIf="showTopic.indexOf('/PROJECT') !== -1">
|
||||||
<p>
|
<p>
|
||||||
{{'quality-assurance.event.table.suggestedProject' | translate}}
|
{{'quality-assurance.event.table.suggestedProject' | translate}}
|
||||||
@@ -115,7 +138,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group button-width">
|
<div *ngIf="(isAdmin$ | async)" class="btn-group button-width">
|
||||||
<button *ngIf="showTopic.indexOf('/PROJECT') !== -1"
|
<button *ngIf="showTopic.indexOf('/PROJECT') !== -1"
|
||||||
class="btn btn-outline-success btn-sm button-width"
|
class="btn btn-outline-success btn-sm button-width"
|
||||||
ngbTooltip="{{'quality-assurance.event.action.import' | translate}}"
|
ngbTooltip="{{'quality-assurance.event.action.import' | translate}}"
|
||||||
@@ -155,6 +178,16 @@
|
|||||||
<i class="fas fa-trash-alt"></i>
|
<i class="fas fa-trash-alt"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -164,14 +197,6 @@
|
|||||||
</ds-pagination>
|
</ds-pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row text-right">
|
|
||||||
<div class="col-md-12">
|
|
||||||
<a class="btn btn-outline-secondary" [routerLink]="['/admin/notifications/quality-assurance']">
|
|
||||||
<i class="fas fa-angle-double-left"></i>
|
|
||||||
{{'quality-assurance.events.back' | translate}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #acceptModal let-modal>
|
<ng-template #acceptModal let-modal>
|
||||||
@@ -225,3 +250,20 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</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>
|
||||||
|
@@ -42,6 +42,8 @@ import { SortDirection, SortOptions } from '../../../core/cache/models/sort-opti
|
|||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||||
|
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', () => {
|
describe('QualityAssuranceEventsComponent test suite', () => {
|
||||||
let fixture: ComponentFixture<QualityAssuranceEventsComponent>;
|
let fixture: ComponentFixture<QualityAssuranceEventsComponent>;
|
||||||
@@ -118,6 +120,8 @@ describe('QualityAssuranceEventsComponent test suite', () => {
|
|||||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
{ provide: TranslateService, useValue: getMockTranslateService() },
|
{ provide: TranslateService, useValue: getMockTranslateService() },
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
|
{ provide: ItemDataService, useValue: {} },
|
||||||
|
{ provide: AuthorizationDataService, useValue: {} },
|
||||||
QualityAssuranceEventsComponent
|
QualityAssuranceEventsComponent
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -30,6 +30,9 @@ import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
|||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||||
|
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||||
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
import { environment } from '../../../../environments/environment';
|
import { environment } from '../../../../environments/environment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,6 +79,11 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
* @type {string}
|
* @type {string}
|
||||||
*/
|
*/
|
||||||
public topic: string;
|
public topic: string;
|
||||||
|
/**
|
||||||
|
* The sourceId of the Quality Assurance events.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
sourceId: string;
|
||||||
/**
|
/**
|
||||||
* The rejected/ignore reason.
|
* The rejected/ignore reason.
|
||||||
* @type {string}
|
* @type {string}
|
||||||
@@ -86,6 +94,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
* @type {Observable<boolean>}
|
* @type {Observable<boolean>}
|
||||||
*/
|
*/
|
||||||
public isEventPageLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
public isEventPageLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The modal reference.
|
* The modal reference.
|
||||||
* @type {any}
|
* @type {any}
|
||||||
@@ -110,6 +119,11 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
protected subs: Subscription[] = [];
|
protected subs: Subscription[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable that emits a boolean value indicating whether the user is an admin.
|
||||||
|
*/
|
||||||
|
isAdmin$: Observable<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the component variables.
|
* Initialize the component variables.
|
||||||
* @param {ActivatedRoute} activatedRoute
|
* @param {ActivatedRoute} activatedRoute
|
||||||
@@ -125,7 +139,8 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
private notificationsService: NotificationsService,
|
private notificationsService: NotificationsService,
|
||||||
private qualityAssuranceEventRestService: QualityAssuranceEventDataService,
|
private qualityAssuranceEventRestService: QualityAssuranceEventDataService,
|
||||||
private paginationService: PaginationService,
|
private paginationService: PaginationService,
|
||||||
private translateService: TranslateService
|
private translateService: TranslateService,
|
||||||
|
private authorizationService: AuthorizationDataService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,10 +149,11 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isEventPageLoading.next(true);
|
this.isEventPageLoading.next(true);
|
||||||
|
this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf);
|
||||||
this.activatedRoute.paramMap.pipe(
|
this.activatedRoute.paramMap.pipe(
|
||||||
tap((params) => {
|
tap((params) => {
|
||||||
this.sourceUrlForProjectSearch = environment.qualityAssuranceConfig.sourceUrlMapForProjectSearch[params.get('sourceId')];
|
this.sourceUrlForProjectSearch = environment.qualityAssuranceConfig.sourceUrlMapForProjectSearch[params.get('sourceId')];
|
||||||
|
this.sourceId = params.get('sourceId');
|
||||||
}),
|
}),
|
||||||
map((params) => params.get('topicId')),
|
map((params) => params.get('topicId')),
|
||||||
take(1),
|
take(1),
|
||||||
@@ -147,10 +163,17 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
this.topic = id;
|
this.topic = id;
|
||||||
return this.getQualityAssuranceEvents();
|
return this.getQualityAssuranceEvents();
|
||||||
})
|
})
|
||||||
).subscribe((events: QualityAssuranceEventData[]) => {
|
).subscribe(
|
||||||
|
{
|
||||||
|
next: (events: QualityAssuranceEventData[]) => {
|
||||||
this.eventsUpdated$.next(events);
|
this.eventsUpdated$.next(events);
|
||||||
this.isEventPageLoading.next(false);
|
this.isEventPageLoading.next(false);
|
||||||
});
|
},
|
||||||
|
error: (error) => {
|
||||||
|
this.isEventPageLoading.next(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,6 +183,8 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
return (this.showTopic.indexOf('/PROJECT') !== -1 ||
|
return (this.showTopic.indexOf('/PROJECT') !== -1 ||
|
||||||
this.showTopic.indexOf('/PID') !== -1 ||
|
this.showTopic.indexOf('/PID') !== -1 ||
|
||||||
this.showTopic.indexOf('/SUBJECT') !== -1 ||
|
this.showTopic.indexOf('/SUBJECT') !== -1 ||
|
||||||
|
this.showTopic.indexOf('/WITHDRAWN') !== -1 ||
|
||||||
|
this.showTopic.indexOf('/REINSTATE') !== -1 ||
|
||||||
this.showTopic.indexOf('/ABSTRACT') !== -1
|
this.showTopic.indexOf('/ABSTRACT') !== -1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -244,8 +269,14 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
public executeAction(action: string, eventData: QualityAssuranceEventData): void {
|
public executeAction(action: string, eventData: QualityAssuranceEventData): void {
|
||||||
eventData.isRunning = true;
|
eventData.isRunning = true;
|
||||||
|
let operation;
|
||||||
|
if (action === 'UNDO') {
|
||||||
|
operation = this.delete(eventData);
|
||||||
|
} else {
|
||||||
|
operation = this.qualityAssuranceEventRestService.patchEvent(action, eventData.event, eventData.reason);
|
||||||
|
}
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
this.qualityAssuranceEventRestService.patchEvent(action, eventData.event, eventData.reason).pipe(
|
operation.pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
switchMap((rd: RemoteData<QualityAssuranceEventObject>) => {
|
switchMap((rd: RemoteData<QualityAssuranceEventObject>) => {
|
||||||
if (rd.hasSucceeded) {
|
if (rd.hasSucceeded) {
|
||||||
@@ -362,7 +393,7 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
switchMap((rd: RemoteData<PaginatedList<QualityAssuranceEventObject>>) => {
|
switchMap((rd: RemoteData<PaginatedList<QualityAssuranceEventObject>>) => {
|
||||||
if (rd.hasSucceeded) {
|
if (rd.hasSucceeded) {
|
||||||
this.totalElements$.next(rd.payload.totalElements);
|
this.totalElements$.next(rd.payload.totalElements);
|
||||||
if (rd.payload.totalElements > 0) {
|
if (rd.payload?.page?.length > 0) {
|
||||||
return this.fetchEvents(rd.payload.page);
|
return this.fetchEvents(rd.payload.page);
|
||||||
} else {
|
} else {
|
||||||
return of([]);
|
return of([]);
|
||||||
@@ -431,4 +462,13 @@ export class QualityAssuranceEventsComponent implements OnInit, OnDestroy {
|
|||||||
last()
|
last()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a quality assurance event.
|
||||||
|
* @param qaEvent The quality assurance event to delete.
|
||||||
|
* @returns An Observable of RemoteData containing NoContent.
|
||||||
|
*/
|
||||||
|
delete(qaEvent: QualityAssuranceEventData): Observable<RemoteData<NoContent>> {
|
||||||
|
return this.qualityAssuranceEventRestService.deleteQAEvent(qaEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,12 +34,12 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let sourceElement of (sources$ | async); let i = index">
|
<tr *ngFor="let sourceElement of (sources$ | async); let i = index">
|
||||||
<td>{{sourceElement.id}}</td>
|
<td>{{sourceElement.id}}</td>
|
||||||
<td>{{sourceElement.lastEvent}}</td>
|
<td>{{sourceElement.lastEvent | date: 'dd/MM/yyyy hh:mm' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group edit-field">
|
<div class="btn-group edit-field">
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-primary btn-sm"
|
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]">
|
[routerLink]="[sourceElement.id]">
|
||||||
<span class="badge badge-info">{{sourceElement.totalEvents}}</span>
|
<span class="badge badge-info">{{sourceElement.totalEvents}}</span>
|
||||||
<i class="fas fa-info fa-fw"></i>
|
<i class="fas fa-info fa-fw"></i>
|
||||||
|
@@ -25,6 +25,8 @@ export class RetrieveAllTopicsAction implements Action {
|
|||||||
payload: {
|
payload: {
|
||||||
elementsPerPage: number;
|
elementsPerPage: number;
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
|
source: string;
|
||||||
|
target?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,10 +37,12 @@ export class RetrieveAllTopicsAction implements Action {
|
|||||||
* @param currentPage
|
* @param currentPage
|
||||||
* The page number to retrieve
|
* The page number to retrieve
|
||||||
*/
|
*/
|
||||||
constructor(elementsPerPage: number, currentPage: number) {
|
constructor(elementsPerPage: number, currentPage: number, source: string, target?: string) {
|
||||||
this.payload = {
|
this.payload = {
|
||||||
elementsPerPage,
|
elementsPerPage,
|
||||||
currentPage
|
currentPage,
|
||||||
|
source,
|
||||||
|
target
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,11 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<h2 class="border-bottom pb-2">{{'quality-assurance.title'| translate}}</h2>
|
<h2 class="border-bottom pb-2">{{'quality-assurance.title'| translate}}</h2>
|
||||||
<ds-alert [type]="'alert-info'">{{'quality-assurance.topics.description'| translate:{source: sourceId} }}</ds-alert>
|
<ds-alert *ngIf="!targetId" [type]="'alert-info'">{{'quality-assurance.topics.description'| translate:{source: sourceId} }}</ds-alert>
|
||||||
|
<ds-alert *ngIf="targetId" [type]="'alert-info'">
|
||||||
|
{{'quality-assurance.topics.description-with-target'| translate:{source: sourceId} }}
|
||||||
|
<a [routerLink]="itemPageUrl">{{(getTargetItemTitle() | async)}}</a>
|
||||||
|
</ds-alert>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -15,7 +19,7 @@
|
|||||||
[collectionSize]="(totalElements$ | async)"
|
[collectionSize]="(totalElements$ | async)"
|
||||||
[hideGear]="false"
|
[hideGear]="false"
|
||||||
[hideSortOptions]="true"
|
[hideSortOptions]="true"
|
||||||
(paginationChange)="getQualityAssuranceTopics()">
|
(paginationChange)="getQualityAssuranceTopics(sourceId, targetId)">
|
||||||
|
|
||||||
<ds-loading class="container" *ngIf="(isTopicsProcessing() | async)" message="'quality-assurance.loading' | translate"></ds-loading>
|
<ds-loading class="container" *ngIf="(isTopicsProcessing() | async)" message="'quality-assurance.loading' | translate"></ds-loading>
|
||||||
<ng-container *ngIf="!(isTopicsProcessing() | async)">
|
<ng-container *ngIf="!(isTopicsProcessing() | async)">
|
||||||
@@ -34,13 +38,13 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let topicElement of (topics$ | async); let i = index">
|
<tr *ngFor="let topicElement of (topics$ | async); let i = index">
|
||||||
<td>{{topicElement.name}}</td>
|
<td>{{topicElement.name}}</td>
|
||||||
<td>{{topicElement.lastEvent}}</td>
|
<td>{{topicElement.lastEvent | date: 'dd/MM/yyyy hh:mm' }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group edit-field">
|
<div class="btn-group edit-field">
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-primary btn-sm"
|
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]="[topicElement.id]">
|
[routerLink]="[getQualityAssuranceRoute(), sourceId, topicElement.id]">
|
||||||
<span class="badge badge-info">{{topicElement.totalEvents}}</span>
|
<span class="badge badge-info">{{topicElement.totalEvents}}</span>
|
||||||
<i class="fas fa-info fa-fw"></i>
|
<i class="fas fa-info fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@@ -16,7 +16,7 @@ import { NotificationsStateService } from '../../notifications-state.service';
|
|||||||
import { cold } from 'jasmine-marbles';
|
import { cold } from 'jasmine-marbles';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { QualityAssuranceTopicsService } from './quality-assurance-topics.service';
|
import { ItemDataService } from 'src/app/core/data/item-data.service';
|
||||||
|
|
||||||
describe('QualityAssuranceTopicsComponent test suite', () => {
|
describe('QualityAssuranceTopicsComponent test suite', () => {
|
||||||
let fixture: ComponentFixture<QualityAssuranceTopicsComponent>;
|
let fixture: ComponentFixture<QualityAssuranceTopicsComponent>;
|
||||||
@@ -44,14 +44,14 @@ describe('QualityAssuranceTopicsComponent test suite', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: NotificationsStateService, useValue: mockNotificationsStateService },
|
{ provide: NotificationsStateService, useValue: mockNotificationsStateService },
|
||||||
{ provide: ActivatedRoute, useValue: { data: observableOf(activatedRouteParams), snapshot: {
|
{ provide: ActivatedRoute, useValue: { data: observableOf(activatedRouteParams), snapshot: {
|
||||||
paramMap: {
|
params: {
|
||||||
get: () => 'openaire',
|
sourceId: 'openaire',
|
||||||
|
targetId: null
|
||||||
},
|
},
|
||||||
}}},
|
}}},
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
|
{ provide: ItemDataService, useValue: {} },
|
||||||
QualityAssuranceTopicsComponent,
|
QualityAssuranceTopicsComponent,
|
||||||
// tslint:disable-next-line: no-empty
|
|
||||||
{ provide: QualityAssuranceTopicsService, useValue: { setSourceId: (sourceId: string) => { } }}
|
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents().then(() => {
|
}).compileComponents().then(() => {
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subscription } from 'rxjs';
|
||||||
import { distinctUntilChanged, take } from 'rxjs/operators';
|
import { distinctUntilChanged, map, take, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { SortOptions } from '../../../core/cache/models/sort-options.model';
|
import { SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||||
import {
|
import {
|
||||||
@@ -14,8 +14,12 @@ import {
|
|||||||
AdminQualityAssuranceTopicsPageParams
|
AdminQualityAssuranceTopicsPageParams
|
||||||
} from '../../../admin/admin-notifications/admin-quality-assurance-topics-page/admin-quality-assurance-topics-page-resolver.service';
|
} from '../../../admin/admin-notifications/admin-quality-assurance-topics-page/admin-quality-assurance-topics-page-resolver.service';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { QualityAssuranceTopicsService } from './quality-assurance-topics.service';
|
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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to display the Quality Assurance topic list.
|
* Component to display the Quality Assurance topic list.
|
||||||
@@ -25,7 +29,7 @@ import { QualityAssuranceTopicsService } from './quality-assurance-topics.servic
|
|||||||
templateUrl: './quality-assurance-topics.component.html',
|
templateUrl: './quality-assurance-topics.component.html',
|
||||||
styleUrls: ['./quality-assurance-topics.component.scss'],
|
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.
|
* The pagination system configuration for HTML listing.
|
||||||
* @type {PaginationComponentOptions}
|
* @type {PaginationComponentOptions}
|
||||||
@@ -60,6 +64,17 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public sourceId: string;
|
public sourceId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This property represents a targetId (item-id) which is used to retrive a topic
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
public targetId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The URL of the item page.
|
||||||
|
*/
|
||||||
|
public itemPageUrl: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the component variables.
|
* Initialize the component variables.
|
||||||
* @param {PaginationService} paginationService
|
* @param {PaginationService} paginationService
|
||||||
@@ -70,18 +85,27 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private paginationService: PaginationService,
|
private paginationService: PaginationService,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
|
private itemService: ItemDataService,
|
||||||
private notificationsStateService: NotificationsStateService,
|
private notificationsStateService: NotificationsStateService,
|
||||||
private qualityAssuranceTopicsService: QualityAssuranceTopicsService
|
private router: Router,
|
||||||
) {
|
) {
|
||||||
|
this.sourceId = this.activatedRoute.snapshot.params.sourceId;
|
||||||
|
this.targetId = this.activatedRoute.snapshot.params.targetId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component initialization.
|
* Component initialization.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.sourceId = this.activatedRoute.snapshot.paramMap.get('sourceId');
|
this.topics$ = this.notificationsStateService.getQualityAssuranceTopics().pipe(
|
||||||
this.qualityAssuranceTopicsService.setSourceId(this.sourceId);
|
tap((topics: QualityAssuranceTopicObject[]) => {
|
||||||
this.topics$ = this.notificationsStateService.getQualityAssuranceTopics();
|
const forward = this.activatedRoute.snapshot.queryParams?.forward === 'true';
|
||||||
|
if (topics.length === 1 && forward) {
|
||||||
|
// If there is only one topic, navigate to the first topic automatically
|
||||||
|
this.router.navigate([this.getQualityAssuranceRoute(), this.sourceId, topics[0].id]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
this.totalElements$ = this.notificationsStateService.getQualityAssuranceTopicsTotals();
|
this.totalElements$ = this.notificationsStateService.getQualityAssuranceTopicsTotals();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +117,7 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
|||||||
this.notificationsStateService.isQualityAssuranceTopicsLoaded().pipe(
|
this.notificationsStateService.isQualityAssuranceTopicsLoaded().pipe(
|
||||||
take(1)
|
take(1)
|
||||||
).subscribe(() => {
|
).subscribe(() => {
|
||||||
this.getQualityAssuranceTopics();
|
this.getQualityAssuranceTopics(this.sourceId, this.targetId);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -121,15 +145,17 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Dispatch the Quality Assurance topics retrival.
|
* Dispatch the Quality Assurance topics retrival.
|
||||||
*/
|
*/
|
||||||
public getQualityAssuranceTopics(): void {
|
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(),
|
distinctUntilChanged(),
|
||||||
).subscribe((options: PaginationComponentOptions) => {
|
).subscribe((options: PaginationComponentOptions) => {
|
||||||
this.notificationsStateService.dispatchRetrieveQualityAssuranceTopics(
|
this.notificationsStateService.dispatchRetrieveQualityAssuranceTopics(
|
||||||
options.pageSize,
|
options.pageSize,
|
||||||
options.currentPage
|
options.currentPage,
|
||||||
|
source,
|
||||||
|
target
|
||||||
);
|
);
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -150,6 +176,40 @@ export class QualityAssuranceTopicsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
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'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
getItemPageRoute(item: Item): string {
|
||||||
|
return getItemPageRoute(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the quality assurance route.
|
||||||
|
* @returns The quality assurance route.
|
||||||
|
*/
|
||||||
|
getQualityAssuranceRoute(): string {
|
||||||
|
return getNotificatioQualityAssuranceRoute();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unsubscribe from all subscriptions.
|
* Unsubscribe from all subscriptions.
|
||||||
*/
|
*/
|
||||||
|
@@ -37,7 +37,9 @@ export class QualityAssuranceTopicsEffects {
|
|||||||
switchMap(([action, currentState]: [RetrieveAllTopicsAction, any]) => {
|
switchMap(([action, currentState]: [RetrieveAllTopicsAction, any]) => {
|
||||||
return this.qualityAssuranceTopicService.getTopics(
|
return this.qualityAssuranceTopicService.getTopics(
|
||||||
action.payload.elementsPerPage,
|
action.payload.elementsPerPage,
|
||||||
action.payload.currentPage
|
action.payload.currentPage,
|
||||||
|
action.payload.source,
|
||||||
|
action.payload.target
|
||||||
).pipe(
|
).pipe(
|
||||||
map((topics: PaginatedList<QualityAssuranceTopicObject>) =>
|
map((topics: PaginatedList<QualityAssuranceTopicObject>) =>
|
||||||
new AddTopicsAction(topics.page, topics.totalPages, topics.currentPage, topics.totalElements)
|
new AddTopicsAction(topics.page, topics.totalPages, topics.currentPage, topics.totalElements)
|
||||||
|
@@ -29,7 +29,7 @@ describe('qualityAssuranceTopicsReducer test suite', () => {
|
|||||||
const expectedState = qualityAssuranceTopicInitialState;
|
const expectedState = qualityAssuranceTopicInitialState;
|
||||||
expectedState.processing = true;
|
expectedState.processing = true;
|
||||||
|
|
||||||
const action = new RetrieveAllTopicsAction(elementPerPage, currentPage);
|
const action = new RetrieveAllTopicsAction(elementPerPage, currentPage, 'ENRICH!MORE!ABSTRACT');
|
||||||
const newState = qualityAssuranceTopicsReducer(qualityAssuranceTopicInitialState, action);
|
const newState = qualityAssuranceTopicsReducer(qualityAssuranceTopicInitialState, action);
|
||||||
|
|
||||||
expect(newState).toEqual(expectedState);
|
expect(newState).toEqual(expectedState);
|
||||||
|
@@ -42,30 +42,30 @@ describe('QualityAssuranceTopicsService', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
restService = TestBed.inject(QualityAssuranceTopicDataService);
|
restService = TestBed.inject(QualityAssuranceTopicDataService);
|
||||||
restServiceAsAny = restService;
|
restServiceAsAny = restService;
|
||||||
restServiceAsAny.getTopics.and.returnValue(observableOf(paginatedListRD));
|
restServiceAsAny.searchTopicsBySource.and.returnValue(observableOf(paginatedListRD));
|
||||||
|
restServiceAsAny.searchTopicsByTarget.and.returnValue(observableOf(paginatedListRD));
|
||||||
service = new QualityAssuranceTopicsService(restService);
|
service = new QualityAssuranceTopicsService(restService);
|
||||||
serviceAsAny = service;
|
serviceAsAny = service;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getTopics', () => {
|
describe('getTopics', () => {
|
||||||
it('Should proxy the call to qualityAssuranceTopicRestService.getTopics', () => {
|
it('should proxy the call to qualityAssuranceTopicRestService.searchTopicsBySource', () => {
|
||||||
const sortOptions = new SortOptions('name', SortDirection.ASC);
|
const sortOptions = new SortOptions('name', SortDirection.ASC);
|
||||||
const findListOptions: FindListOptions = {
|
const findListOptions: FindListOptions = {
|
||||||
elementsPerPage: elementsPerPage,
|
elementsPerPage: elementsPerPage,
|
||||||
currentPage: currentPage,
|
currentPage: currentPage,
|
||||||
sort: sortOptions,
|
sort: sortOptions,
|
||||||
searchParams: [new RequestParam('source', 'ENRICH!MORE!ABSTRACT')]
|
searchParams: [new RequestParam('source', 'openaire')]
|
||||||
};
|
};
|
||||||
service.setSourceId('ENRICH!MORE!ABSTRACT');
|
service.getTopics(elementsPerPage, currentPage, 'openaire');
|
||||||
const result = service.getTopics(elementsPerPage, currentPage);
|
expect((service as any).qualityAssuranceTopicRestService.searchTopicsBySource).toHaveBeenCalledWith(findListOptions);
|
||||||
expect((service as any).qualityAssuranceTopicRestService.getTopics).toHaveBeenCalledWith(findListOptions);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should return a paginated list of Quality Assurance topics', () => {
|
it('should return a paginated list of Quality Assurance topics', () => {
|
||||||
const expected = cold('(a|)', {
|
const expected = cold('(a|)', {
|
||||||
a: paginatedList
|
a: paginatedList
|
||||||
});
|
});
|
||||||
const result = service.getTopics(elementsPerPage, currentPage);
|
const result = service.getTopics(elementsPerPage, currentPage, 'openaire');
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -13,6 +13,7 @@ import {
|
|||||||
import { RequestParam } from '../../../core/cache/models/request-param.model';
|
import { RequestParam } from '../../../core/cache/models/request-param.model';
|
||||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service handling all Quality Assurance topic requests to the REST service.
|
* The service handling all Quality Assurance topic requests to the REST service.
|
||||||
@@ -28,10 +29,6 @@ export class QualityAssuranceTopicsService {
|
|||||||
private qualityAssuranceTopicRestService: QualityAssuranceTopicDataService
|
private qualityAssuranceTopicRestService: QualityAssuranceTopicDataService
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
/**
|
|
||||||
* sourceId used to get topics
|
|
||||||
*/
|
|
||||||
sourceId: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the list of Quality Assurance topics managing pagination and errors.
|
* Return the list of Quality Assurance topics managing pagination and errors.
|
||||||
@@ -43,17 +40,25 @@ export class QualityAssuranceTopicsService {
|
|||||||
* @return Observable<PaginatedList<QualityAssuranceTopicObject>>
|
* @return Observable<PaginatedList<QualityAssuranceTopicObject>>
|
||||||
* The list of Quality Assurance topics.
|
* The list of Quality Assurance topics.
|
||||||
*/
|
*/
|
||||||
public getTopics(elementsPerPage, currentPage): Observable<PaginatedList<QualityAssuranceTopicObject>> {
|
public getTopics(elementsPerPage, currentPage, source: string, target?: string): Observable<PaginatedList<QualityAssuranceTopicObject>> {
|
||||||
const sortOptions = new SortOptions('name', SortDirection.ASC);
|
const sortOptions = new SortOptions('name', SortDirection.ASC);
|
||||||
|
|
||||||
const findListOptions: FindListOptions = {
|
const findListOptions: FindListOptions = {
|
||||||
elementsPerPage: elementsPerPage,
|
elementsPerPage: elementsPerPage,
|
||||||
currentPage: currentPage,
|
currentPage: currentPage,
|
||||||
sort: sortOptions,
|
sort: sortOptions,
|
||||||
searchParams: [new RequestParam('source', this.sourceId)]
|
searchParams: [new RequestParam('source', source)]
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.qualityAssuranceTopicRestService.getTopics(findListOptions).pipe(
|
let request$: Observable<RemoteData<PaginatedList<QualityAssuranceTopicObject>>>;
|
||||||
|
|
||||||
|
if (hasValue(target)) {
|
||||||
|
findListOptions.searchParams.push(new RequestParam('target', target));
|
||||||
|
request$ = this.qualityAssuranceTopicRestService.searchTopicsByTarget(findListOptions);
|
||||||
|
} else {
|
||||||
|
request$ = this.qualityAssuranceTopicRestService.searchTopicsBySource(findListOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request$.pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
map((rd: RemoteData<PaginatedList<QualityAssuranceTopicObject>>) => {
|
map((rd: RemoteData<PaginatedList<QualityAssuranceTopicObject>>) => {
|
||||||
if (rd.hasSucceeded) {
|
if (rd.hasSucceeded) {
|
||||||
@@ -64,12 +69,4 @@ export class QualityAssuranceTopicsService {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* set sourceId which is used to get topics
|
|
||||||
* @param sourceId string
|
|
||||||
*/
|
|
||||||
setSourceId(sourceId: string) {
|
|
||||||
this.sourceId = sourceId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -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 { Community } from '../../core/shared/community.model';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import flatten from 'lodash/flatten';
|
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', () => {
|
describe('DSOEditMenuResolver', () => {
|
||||||
|
|
||||||
@@ -39,6 +43,8 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
let researcherProfileService;
|
let researcherProfileService;
|
||||||
let notificationsService;
|
let notificationsService;
|
||||||
let translate;
|
let translate;
|
||||||
|
let dsoWithdrawnReinstateModalService;
|
||||||
|
let correctionsDataService;
|
||||||
|
|
||||||
const dsoRoute = (dso: DSpaceObject) => {
|
const dsoRoute = (dso: DSpaceObject) => {
|
||||||
return {
|
return {
|
||||||
@@ -141,6 +147,14 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
error: {},
|
error: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dsoWithdrawnReinstateModalService = jasmine.createSpyObj('dsoWithdrawnReinstateModalService', {
|
||||||
|
openCreateWithdrawnReinstateModal: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
correctionsDataService = jasmine.createSpyObj('correctionsDataService', {
|
||||||
|
findByItem: observableOf([])
|
||||||
|
});
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule],
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule],
|
||||||
declarations: [AdminSidebarComponent],
|
declarations: [AdminSidebarComponent],
|
||||||
@@ -152,6 +166,9 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
{provide: ResearcherProfileDataService, useValue: researcherProfileService},
|
{provide: ResearcherProfileDataService, useValue: researcherProfileService},
|
||||||
{provide: TranslateService, useValue: translate},
|
{provide: TranslateService, useValue: translate},
|
||||||
{provide: NotificationsService, useValue: notificationsService},
|
{provide: NotificationsService, useValue: notificationsService},
|
||||||
|
{provide: DsoWithdrawnReinstateModalService, useValue: dsoWithdrawnReinstateModalService},
|
||||||
|
{provide: AuthService, useValue: new AuthServiceMock()},
|
||||||
|
{provide: CorrectionTypeDataService, useValue: correctionsDataService},
|
||||||
{
|
{
|
||||||
provide: NgbModal, useValue: {
|
provide: NgbModal, useValue: {
|
||||||
open: () => {/*comment*/
|
open: () => {/*comment*/
|
||||||
@@ -350,7 +367,7 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
route = dsoRoute(testItem);
|
route = dsoRoute(testItem);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return Item-specific entries', (done) => {
|
it('should return Item-specific entries', () => {
|
||||||
const result = resolver.getDsoMenus(testObject, route, state);
|
const result = resolver.getDsoMenus(testObject, route, state);
|
||||||
combineLatest(result).pipe(map(flatten)).subscribe((menu) => {
|
combineLatest(result).pipe(map(flatten)).subscribe((menu) => {
|
||||||
const orcidEntry = menu.find(entry => entry.id === 'orcid-dso');
|
const orcidEntry = menu.find(entry => entry.id === 'orcid-dso');
|
||||||
@@ -371,20 +388,18 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
expect(claimEntry.active).toBeFalse();
|
expect(claimEntry.active).toBeFalse();
|
||||||
expect(claimEntry.visible).toBeFalse();
|
expect(claimEntry.visible).toBeFalse();
|
||||||
expect(claimEntry.model.type).toEqual(MenuItemType.ONCLICK);
|
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);
|
const result = resolver.getDsoMenus(testObject, route, state);
|
||||||
combineLatest(result).pipe(map(flatten)).subscribe((menu) => {
|
combineLatest(result).pipe(map(flatten)).subscribe((menu) => {
|
||||||
const subscribeEntry = menu.find(entry => entry.id === 'subscribe');
|
const subscribeEntry = menu.find(entry => entry.id === 'subscribe');
|
||||||
expect(subscribeEntry).toBeFalsy();
|
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);
|
const result = resolver.getDsoMenus(testObject, route, state);
|
||||||
combineLatest(result).pipe(map(flatten)).subscribe((menu) => {
|
combineLatest(result).pipe(map(flatten)).subscribe((menu) => {
|
||||||
const editEntry = menu.find(entry => entry.id === 'edit-dso');
|
const editEntry = menu.find(entry => entry.id === 'edit-dso');
|
||||||
@@ -395,7 +410,6 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
expect((editEntry.model as LinkMenuItemModel).link).toEqual(
|
expect((editEntry.model as LinkMenuItemModel).link).toEqual(
|
||||||
'/items/test-item-uuid/edit/metadata'
|
'/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 { Item } from '../../core/shared/item.model';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { OnClickMenuItemModel } from '../menu/menu-item/models/onclick.model';
|
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 { map, switchMap } from 'rxjs/operators';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
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 { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service';
|
||||||
import { NotificationsService } from '../notifications/notifications.service';
|
import { NotificationsService } from '../notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
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 { SubscriptionModalComponent } from '../subscriptions/subscription-modal/subscription-modal.component';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
@@ -42,6 +49,9 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
|||||||
protected researcherProfileService: ResearcherProfileDataService,
|
protected researcherProfileService: ResearcherProfileDataService,
|
||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
protected translate: TranslateService,
|
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[]> {
|
protected getItemMenu(dso): Observable<MenuSection[]> {
|
||||||
if (dso instanceof Item) {
|
if (dso instanceof Item) {
|
||||||
|
const findListTopicOptions: FindListOptions = {
|
||||||
|
searchParams: [new RequestParam('target', dso.uuid)]
|
||||||
|
};
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self),
|
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self),
|
||||||
this.dsoVersioningModalService.isNewVersionButtonDisabled(dso),
|
this.dsoVersioningModalService.isNewVersionButtonDisabled(dso),
|
||||||
this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create'),
|
this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create'),
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, dso.self),
|
this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, dso.self),
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanClaimItem, dso.self),
|
this.authorizationService.isAuthorized(FeatureID.CanClaimItem, dso.self),
|
||||||
|
this.correctionTypeDataService.findByItem(dso.uuid, false).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
getRemoteDataPayload())
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([canCreateVersion, disableVersioning, versionTooltip, canSynchronizeWithOrcid, canClaimItem]) => {
|
map(([canCreateVersion, disableVersioning, versionTooltip, canSynchronizeWithOrcid, canClaimItem, correction]) => {
|
||||||
const isPerson = this.getDsoType(dso) === 'person';
|
const isPerson = this.getDsoType(dso) === 'person';
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -174,6 +190,34 @@ export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection
|
|||||||
icon: 'hand-paper',
|
icon: 'hand-paper',
|
||||||
index: 3
|
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;
|
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,
|
funder: null,
|
||||||
fundingProgram: null,
|
fundingProgram: null,
|
||||||
jurisdiction: null,
|
jurisdiction: null,
|
||||||
title: null
|
title: null,
|
||||||
|
reason: 'Missing PID'
|
||||||
},
|
},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
@@ -1519,7 +1520,8 @@ export const qualityAssuranceEventObjectMissingPid2: QualityAssuranceEventObject
|
|||||||
funder: null,
|
funder: null,
|
||||||
fundingProgram: null,
|
fundingProgram: null,
|
||||||
jurisdiction: null,
|
jurisdiction: null,
|
||||||
title: null
|
title: null,
|
||||||
|
reason: 'Missing PID'
|
||||||
},
|
},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
@@ -1556,7 +1558,8 @@ export const qualityAssuranceEventObjectMissingPid3: QualityAssuranceEventObject
|
|||||||
funder: null,
|
funder: null,
|
||||||
fundingProgram: null,
|
fundingProgram: null,
|
||||||
jurisdiction: null,
|
jurisdiction: null,
|
||||||
title: null
|
title: null,
|
||||||
|
reason: 'Missing PID'
|
||||||
},
|
},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
@@ -1593,7 +1596,8 @@ export const qualityAssuranceEventObjectMissingPid4: QualityAssuranceEventObject
|
|||||||
funder: null,
|
funder: null,
|
||||||
fundingProgram: null,
|
fundingProgram: null,
|
||||||
jurisdiction: null,
|
jurisdiction: null,
|
||||||
title: null
|
title: null,
|
||||||
|
reason: 'Missing DOI'
|
||||||
},
|
},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
@@ -1630,7 +1634,8 @@ export const qualityAssuranceEventObjectMissingPid5: QualityAssuranceEventObject
|
|||||||
funder: null,
|
funder: null,
|
||||||
fundingProgram: null,
|
fundingProgram: null,
|
||||||
jurisdiction: null,
|
jurisdiction: null,
|
||||||
title: null
|
title: null,
|
||||||
|
reason: 'Missing PID'
|
||||||
},
|
},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
@@ -1667,7 +1672,8 @@ export const qualityAssuranceEventObjectMissingPid6: QualityAssuranceEventObject
|
|||||||
funder: null,
|
funder: null,
|
||||||
fundingProgram: null,
|
fundingProgram: null,
|
||||||
jurisdiction: null,
|
jurisdiction: null,
|
||||||
title: null
|
title: null,
|
||||||
|
reason: 'Missing PID'
|
||||||
},
|
},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
@@ -1704,7 +1710,8 @@ export const qualityAssuranceEventObjectMissingAbstract: QualityAssuranceEventOb
|
|||||||
funder: null,
|
funder: null,
|
||||||
fundingProgram: null,
|
fundingProgram: null,
|
||||||
jurisdiction: null,
|
jurisdiction: null,
|
||||||
title: null
|
title: null,
|
||||||
|
reason: 'Missing abstract'
|
||||||
},
|
},
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
@@ -1741,6 +1748,7 @@ export const qualityAssuranceEventObjectMissingProjectFound: QualityAssuranceEve
|
|||||||
funder: 'EC',
|
funder: 'EC',
|
||||||
fundingProgram: 'H2020',
|
fundingProgram: 'H2020',
|
||||||
jurisdiction: 'EU',
|
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'
|
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: {
|
_links: {
|
||||||
@@ -1778,7 +1786,8 @@ export const qualityAssuranceEventObjectMissingProjectNotFound: QualityAssurance
|
|||||||
funder: 'EC',
|
funder: 'EC',
|
||||||
fundingProgram: 'H2021',
|
fundingProgram: 'H2021',
|
||||||
jurisdiction: 'EU',
|
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: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
@@ -1838,8 +1847,10 @@ export function getMockNotificationsStateService(): any {
|
|||||||
*/
|
*/
|
||||||
export function getMockQualityAssuranceTopicRestService(): QualityAssuranceTopicDataService {
|
export function getMockQualityAssuranceTopicRestService(): QualityAssuranceTopicDataService {
|
||||||
return jasmine.createSpyObj('QualityAssuranceTopicDataService', {
|
return jasmine.createSpyObj('QualityAssuranceTopicDataService', {
|
||||||
getTopics: jasmine.createSpy('getTopics'),
|
|
||||||
getTopic: jasmine.createSpy('getTopic'),
|
getTopic: jasmine.createSpy('getTopic'),
|
||||||
|
searchTopicsByTarget: jasmine.createSpy('searchTopicsByTarget'),
|
||||||
|
searchTopicsBySource: jasmine.createSpy('searchTopicsBySource'),
|
||||||
|
clearFindAllTopicsRequests: jasmine.createSpy('clearFindAllTopicsRequests'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -279,8 +279,11 @@ import {
|
|||||||
} from '../item-page/simple/field-components/specific-field/title/themed-item-page-field.component';
|
} from '../item-page/simple/field-components/specific-field/title/themed-item-page-field.component';
|
||||||
import { BitstreamListItemComponent } from './object-list/bitstream-list-item/bitstream-list-item.component';
|
import { BitstreamListItemComponent } from './object-list/bitstream-list-item/bitstream-list-item.component';
|
||||||
import { NgxPaginationModule } from 'ngx-pagination';
|
import { NgxPaginationModule } from 'ngx-pagination';
|
||||||
|
import { SplitPipe } from './utils/split.pipe';
|
||||||
import { ThemedUserMenuComponent } from './auth-nav-menu/user-menu/themed-user-menu.component';
|
import { ThemedUserMenuComponent } from './auth-nav-menu/user-menu/themed-user-menu.component';
|
||||||
import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component';
|
import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component';
|
||||||
|
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 { DynamicComponentLoaderDirective } from './abstract-component-loader/dynamic-component-loader.directive';
|
||||||
import { StartsWithLoaderComponent } from './starts-with/starts-with-loader.component';
|
import { StartsWithLoaderComponent } from './starts-with/starts-with-loader.component';
|
||||||
|
|
||||||
@@ -322,7 +325,8 @@ const PIPES = [
|
|||||||
ObjNgFor,
|
ObjNgFor,
|
||||||
BrowserOnlyPipe,
|
BrowserOnlyPipe,
|
||||||
MarkdownPipe,
|
MarkdownPipe,
|
||||||
ShortNumberPipe
|
ShortNumberPipe,
|
||||||
|
SplitPipe
|
||||||
];
|
];
|
||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
@@ -472,7 +476,9 @@ const ENTRY_COMPONENTS = [
|
|||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
TruncatableService,
|
TruncatableService,
|
||||||
MockAdminGuard,
|
MockAdminGuard,
|
||||||
AbstractTrackableComponent
|
AbstractTrackableComponent,
|
||||||
|
QualityAssuranceEventDataService,
|
||||||
|
QualityAssuranceSourceDataService
|
||||||
];
|
];
|
||||||
|
|
||||||
const DIRECTIVES = [
|
const DIRECTIVES = [
|
||||||
|
16
src/app/shared/utils/split.pipe.ts
Normal file
16
src/app/shared/utils/split.pipe.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -11,9 +11,10 @@ import { hasValue, isNotEmpty } from '../shared/empty.util';
|
|||||||
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
|
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
|
||||||
import {
|
import {
|
||||||
getAllSucceededRemoteDataPayload,
|
getAllSucceededRemoteDataPayload,
|
||||||
getFinishedRemoteData, getFirstCompletedRemoteData,
|
getFinishedRemoteData,
|
||||||
|
getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload,
|
getFirstSucceededRemoteDataPayload,
|
||||||
getFirstSucceededRemoteListPayload
|
getFirstSucceededRemoteListPayload,
|
||||||
} from '../core/shared/operators';
|
} from '../core/shared/operators';
|
||||||
import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model';
|
import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model';
|
||||||
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
||||||
|
@@ -534,7 +534,7 @@
|
|||||||
|
|
||||||
"admin.quality-assurance.page.title": "Quality Assurance",
|
"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.",
|
"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.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.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",
|
"item.edit.authorizations.title": "Edit item's Policies",
|
||||||
@@ -2432,6 +2436,14 @@
|
|||||||
|
|
||||||
"item.truncatable-part.show-less": "Collapse",
|
"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.header": "Delete Supervision Order",
|
||||||
|
|
||||||
"workflow-item.search.result.delete-supervision.modal.info": "Are you sure you want to 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.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.version.hasDraft": "A new version cannot be created because there is an inprogress submission in the version history",
|
||||||
|
|
||||||
"item.page.claim.button": "Claim",
|
"item.page.claim.button": "Claim",
|
||||||
@@ -2670,6 +2686,12 @@
|
|||||||
|
|
||||||
"item.version.create.modal.header": "New version",
|
"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": "Create a new version for this item",
|
||||||
|
|
||||||
"item.version.create.modal.text.startingFrom": "starting from version {{version}}",
|
"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.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.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.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",
|
"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",
|
"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.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.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}}",
|
"item.version.create.notification.success": "New version has been created with version number {{version}}",
|
||||||
@@ -3218,6 +3268,8 @@
|
|||||||
|
|
||||||
"quality-assurance.topics.description": "Below you can see all the topics received from the subscriptions to {{source}}.",
|
"quality-assurance.topics.description": "Below you can see all the topics received from the subscriptions to {{source}}.",
|
||||||
|
|
||||||
|
"quality-assurance.topics.description-with-target": "Below you can see all the topics received from the subscriptions to {{source}} in regards to the",
|
||||||
|
|
||||||
"quality-assurance.source.description": "Below you can see all the notification's sources.",
|
"quality-assurance.source.description": "Below you can see all the notification's sources.",
|
||||||
|
|
||||||
"quality-assurance.topics": "Current Topics",
|
"quality-assurance.topics": "Current Topics",
|
||||||
@@ -3232,7 +3284,9 @@
|
|||||||
|
|
||||||
"quality-assurance.table.actions": "Actions",
|
"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.",
|
"quality-assurance.noTopics": "No topics found.",
|
||||||
|
|
||||||
@@ -3260,12 +3314,16 @@
|
|||||||
|
|
||||||
"quality-assurance.event.table.project-details": "Project details",
|
"quality-assurance.event.table.project-details": "Project details",
|
||||||
|
|
||||||
|
"quality-assurance.event.table.reasons": "Reasons",
|
||||||
|
|
||||||
"quality-assurance.event.table.actions": "Actions",
|
"quality-assurance.event.table.actions": "Actions",
|
||||||
|
|
||||||
"quality-assurance.event.action.accept": "Accept suggestion",
|
"quality-assurance.event.action.accept": "Accept suggestion",
|
||||||
|
|
||||||
"quality-assurance.event.action.ignore": "Ignore 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.reject": "Reject suggestion",
|
||||||
|
|
||||||
"quality-assurance.event.action.import": "Import project and accept suggestion",
|
"quality-assurance.event.action.import": "Import project and accept suggestion",
|
||||||
@@ -3294,6 +3352,8 @@
|
|||||||
|
|
||||||
"quality-assurance.events.back": "Back to topics",
|
"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.less": "Show less",
|
||||||
|
|
||||||
"quality-assurance.event.table.more": "Show more",
|
"quality-assurance.event.table.more": "Show more",
|
||||||
@@ -3306,6 +3366,8 @@
|
|||||||
|
|
||||||
"quality-assurance.event.ignore.description": "This operation can't be undone. Ignore this suggestion?",
|
"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.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.",
|
"quality-assurance.event.accept.description": "No DSpace project selected. A new project will be created based on the suggestion data.",
|
||||||
|
@@ -3535,6 +3535,22 @@
|
|||||||
// "item.truncatable-part.show-less": "Collapse",
|
// "item.truncatable-part.show-less": "Collapse",
|
||||||
"item.truncatable-part.show-less": "Riduci",
|
"item.truncatable-part.show-less": "Riduci",
|
||||||
|
|
||||||
|
// "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 suggestions related to your account",
|
||||||
|
|
||||||
|
// "item.qa-event-notification-info.check.button": "View",
|
||||||
|
// TODO New key - Add a translation
|
||||||
|
"item.qa-event-notification-info.check.button": "View",
|
||||||
|
|
||||||
|
// "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 suggestions related to your account",
|
||||||
|
|
||||||
|
// "mydspace.qa-event-notification-info.check.button": "View",
|
||||||
|
// TODO New key - Add a translation
|
||||||
|
"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": "Delete Supervision Order",
|
||||||
"workflow-item.search.result.delete-supervision.modal.header": "Elimina l'ordine di supervisione",
|
"workflow-item.search.result.delete-supervision.modal.header": "Elimina l'ordine di supervisione",
|
||||||
|
|
||||||
|
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 |
BIN
src/assets/images/qa-coar-notify-logo.png
Normal file
BIN
src/assets/images/qa-coar-notify-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
BIN
src/assets/images/qa-openaire-logo.png
Normal file
BIN
src/assets/images/qa-openaire-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
@@ -108,6 +108,8 @@
|
|||||||
|
|
||||||
--ds-item-page-img-field-default-inline-height: 24px;
|
--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-nb-processes-badge-size: 0.5em;
|
||||||
--ds-process-overview-table-id-column-width: 120px;
|
--ds-process-overview-table-id-column-width: 120px;
|
||||||
--ds-process-overview-table-name-column-width: auto;
|
--ds-process-overview-table-name-column-width: auto;
|
||||||
|
@@ -305,7 +305,7 @@ const DECLARATIONS = [
|
|||||||
NgxGalleryModule,
|
NgxGalleryModule,
|
||||||
FormModule,
|
FormModule,
|
||||||
RequestCopyModule,
|
RequestCopyModule,
|
||||||
NotificationsModule
|
NotificationsModule,
|
||||||
],
|
],
|
||||||
declarations: DECLARATIONS,
|
declarations: DECLARATIONS,
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user