mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 10:34:15 +00:00
[CST-5249] Migration of OPENAIRE correction service
This commit is contained in:
@@ -0,0 +1 @@
|
||||
<ds-openaire-broker-events></ds-openaire-broker-events>
|
@@ -0,0 +1,26 @@
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AdminNotificationsOpenaireEventsPageComponent } from './admin-notifications-openaire-events-page.component';
|
||||
|
||||
describe('AdminNotificationsOpenaireEventsPageComponent', () => {
|
||||
let component: AdminNotificationsOpenaireEventsPageComponent;
|
||||
let fixture: ComponentFixture<AdminNotificationsOpenaireEventsPageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AdminNotificationsOpenaireEventsPageComponent ],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AdminNotificationsOpenaireEventsPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create AdminNotificationsOpenaireEventsPageComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-notification-openaire-events-page',
|
||||
templateUrl: './admin-notifications-openaire-events-page.component.html'
|
||||
})
|
||||
export class AdminNotificationsOpenaireEventsPageComponent {
|
||||
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Interface for the route parameters.
|
||||
*/
|
||||
export interface AdminNotificationsOpenaireEventsPageParams {
|
||||
pageId?: string;
|
||||
pageSize?: number;
|
||||
currentPage?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a resolver that retrieve the route data before the route is activated.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AdminNotificationsOpenaireEventsPageResolver implements Resolve<AdminNotificationsOpenaireEventsPageParams> {
|
||||
|
||||
/**
|
||||
* Method for resolving the parameters in the current route.
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns AdminNotificationsOpenaireEventsPageParams Emits the route parameters
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): AdminNotificationsOpenaireEventsPageParams {
|
||||
return {
|
||||
pageId: route.queryParams.pageId,
|
||||
pageSize: parseInt(route.queryParams.pageSize, 10),
|
||||
currentPage: parseInt(route.queryParams.page, 10)
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Interface for the route parameters.
|
||||
*/
|
||||
export interface AdminNotificationsOpenaireTopicsPageParams {
|
||||
pageId?: string;
|
||||
pageSize?: number;
|
||||
currentPage?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a resolver that retrieve the route data before the route is activated.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AdminNotificationsOpenaireTopicsPageResolver implements Resolve<AdminNotificationsOpenaireTopicsPageParams> {
|
||||
|
||||
/**
|
||||
* Method for resolving the parameters in the current route.
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns AdminNotificationsOpenaireTopicsPageParams Emits the route parameters
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): AdminNotificationsOpenaireTopicsPageParams {
|
||||
return {
|
||||
pageId: route.queryParams.pageId,
|
||||
pageSize: parseInt(route.queryParams.pageSize, 10),
|
||||
currentPage: parseInt(route.queryParams.page, 10)
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
<ds-openaire-broker-topic></ds-openaire-broker-topic>
|
@@ -0,0 +1,26 @@
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { AdminNotificationsOpenaireTopicsPageComponent } from './admin-notifications-openaire-topics-page.component';
|
||||
|
||||
describe('AdminNotificationsOpenaireTopicsPageComponent', () => {
|
||||
let component: AdminNotificationsOpenaireTopicsPageComponent;
|
||||
let fixture: ComponentFixture<AdminNotificationsOpenaireTopicsPageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AdminNotificationsOpenaireTopicsPageComponent ],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AdminNotificationsOpenaireTopicsPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create AdminNotificationsOpenaireTopicsPageComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-notification-openairebroker-page',
|
||||
templateUrl: './admin-notifications-openaire-topics-page.component.html'
|
||||
})
|
||||
export class AdminNotificationsOpenaireTopicsPageComponent {
|
||||
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||
import { getNotificationsModuleRoute } from '../admin-routing-paths';
|
||||
|
||||
export const NOTIFICATIONS_EDIT_PATH = 'openaire-broker';
|
||||
|
||||
export function getNotificationsOpenairebrokerRoute(id: string) {
|
||||
return new URLCombiner(getNotificationsModuleRoute(), NOTIFICATIONS_EDIT_PATH, id).toString();
|
||||
}
|
@@ -0,0 +1,60 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { AuthenticatedGuard } from '../../core/auth/authenticated.guard';
|
||||
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||
import { NOTIFICATIONS_EDIT_PATH } from './admin-notifications-routing-paths';
|
||||
import { AdminNotificationsOpenaireTopicsPageComponent } from './admin-notifications-openaire-topics-page/admin-notifications-openaire-topics-page.component';
|
||||
import { AdminNotificationsOpenaireEventsPageComponent } from './admin-notifications-openaire-events-page/admin-notifications-openaire-events-page.component';
|
||||
import { AdminNotificationsOpenaireTopicsPageResolver } from './admin-notifications-openaire-topics-page/admin-notifications-openaire-topics-page-resolver.service';
|
||||
import { AdminNotificationsOpenaireEventsPageResolver } from './admin-notifications-openaire-events-page/admin-notifications-openaire-events-page.resolver';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: `${NOTIFICATIONS_EDIT_PATH}`,
|
||||
component: AdminNotificationsOpenaireTopicsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
openaireBrokerTopicsParams: AdminNotificationsOpenaireTopicsPageResolver
|
||||
},
|
||||
data: {
|
||||
title: 'admin.notifications.openairebroker.page.title',
|
||||
breadcrumbKey: 'admin.notifications.openairebroker',
|
||||
showBreadcrumbsFluid: false
|
||||
}
|
||||
},
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: `${NOTIFICATIONS_EDIT_PATH}/:id`,
|
||||
component: AdminNotificationsOpenaireEventsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
openaireBrokerEventsParams: AdminNotificationsOpenaireEventsPageResolver
|
||||
},
|
||||
data: {
|
||||
title: 'admin.notifications.openaireevent.page.title',
|
||||
breadcrumbKey: 'admin.notifications.openaireevent',
|
||||
showBreadcrumbsFluid: false
|
||||
}
|
||||
}
|
||||
])
|
||||
],
|
||||
providers: [
|
||||
I18nBreadcrumbResolver,
|
||||
I18nBreadcrumbsService,
|
||||
AdminNotificationsOpenaireTopicsPageResolver,
|
||||
AdminNotificationsOpenaireEventsPageResolver
|
||||
]
|
||||
})
|
||||
/**
|
||||
* Routing module for the Notifications section of the admin sidebar
|
||||
*/
|
||||
export class AdminNotificationsRoutingModule {
|
||||
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CoreModule } from '../../core/core.module';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { AdminNotificationsRoutingModule } from './admin-notifications-routing.module';
|
||||
import { AdminNotificationsOpenaireTopicsPageComponent } from './admin-notifications-openaire-topics-page/admin-notifications-openaire-topics-page.component';
|
||||
import { AdminNotificationsOpenaireEventsPageComponent } from './admin-notifications-openaire-events-page/admin-notifications-openaire-events-page.component';
|
||||
import { OpenaireModule } from '../../openaire/openaire.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
CoreModule.forRoot(),
|
||||
AdminNotificationsRoutingModule,
|
||||
OpenaireModule
|
||||
],
|
||||
declarations: [
|
||||
AdminNotificationsOpenaireTopicsPageComponent,
|
||||
AdminNotificationsOpenaireEventsPageComponent
|
||||
],
|
||||
entryComponents: []
|
||||
})
|
||||
/**
|
||||
* This module handles all components related to the notifications pages
|
||||
*/
|
||||
export class AdminNotificationsModule {
|
||||
|
||||
}
|
@@ -2,7 +2,12 @@ import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||
import { getAdminModuleRoute } from '../app-routing-paths';
|
||||
|
||||
export const REGISTRIES_MODULE_PATH = 'registries';
|
||||
export const NOTIFICATIONS_MODULE_PATH = 'notifications';
|
||||
|
||||
export function getRegistriesModuleRoute() {
|
||||
return new URLCombiner(getAdminModuleRoute(), REGISTRIES_MODULE_PATH).toString();
|
||||
}
|
||||
|
||||
export function getNotificationsModuleRoute() {
|
||||
return new URLCombiner(getAdminModuleRoute(), NOTIFICATIONS_MODULE_PATH).toString();
|
||||
}
|
||||
|
@@ -6,11 +6,16 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
|
||||
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
|
||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
|
||||
import { REGISTRIES_MODULE_PATH } from './admin-routing-paths';
|
||||
import { REGISTRIES_MODULE_PATH, NOTIFICATIONS_MODULE_PATH } from './admin-routing-paths';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: NOTIFICATIONS_MODULE_PATH,
|
||||
loadChildren: () => import('./admin-notifications/admin-notifications.module')
|
||||
.then((m) => m.AdminNotificationsModule),
|
||||
},
|
||||
{
|
||||
path: REGISTRIES_MODULE_PATH,
|
||||
loadChildren: () => import('./admin-registries/admin-registries.module')
|
||||
|
@@ -276,7 +276,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// icon: 'chart-bar',
|
||||
// index: 8
|
||||
// index: 9
|
||||
// },
|
||||
|
||||
/* Control Panel */
|
||||
@@ -291,7 +291,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// icon: 'cogs',
|
||||
// index: 9
|
||||
// index: 10
|
||||
// },
|
||||
|
||||
/* Processes */
|
||||
@@ -305,7 +305,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
link: '/processes'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'terminal',
|
||||
index: 10
|
||||
index: 11
|
||||
},
|
||||
];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, {
|
||||
@@ -464,6 +464,29 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
createSiteAdministratorMenuSections() {
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf).subscribe((authorized) => {
|
||||
const menuList = [
|
||||
/* Notifications */
|
||||
{
|
||||
id: 'notifications',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.notifications'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'bell',
|
||||
index: 4
|
||||
},
|
||||
{
|
||||
id: 'notifications_openair_broker',
|
||||
parentID: 'notifications',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.notifications_openaire_broker',
|
||||
link: '/admin/notifications/openaire-broker'
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
/* Admin Search */
|
||||
{
|
||||
id: 'admin_search',
|
||||
@@ -475,7 +498,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
link: '/admin/search'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'search',
|
||||
index: 5
|
||||
index: 6
|
||||
},
|
||||
/* Registries */
|
||||
{
|
||||
@@ -487,7 +510,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
text: 'menu.section.registries'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'list',
|
||||
index: 6
|
||||
index: 7
|
||||
},
|
||||
{
|
||||
id: 'registries_metadata',
|
||||
@@ -523,7 +546,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
link: 'admin/curation-tasks'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'filter',
|
||||
index: 7
|
||||
index: 8
|
||||
},
|
||||
|
||||
/* Workflow */
|
||||
@@ -537,7 +560,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
link: '/admin/workflow'
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'user-check',
|
||||
index: 11
|
||||
index: 12
|
||||
},
|
||||
];
|
||||
|
||||
@@ -600,7 +623,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
text: 'menu.section.access_control'
|
||||
} as TextMenuItemModel,
|
||||
icon: 'key',
|
||||
index: 4
|
||||
index: 5
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -162,6 +162,8 @@ import { SearchConfig } from './shared/search/search-filters/search-config.model
|
||||
import { SequenceService } from './shared/sequence.service';
|
||||
import { GroupDataService } from './eperson/group-data.service';
|
||||
import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model';
|
||||
import { OpenaireBrokerTopicObject } from './openaire/broker/models/openaire-broker-topic.model';
|
||||
import { OpenaireBrokerEventObject } from './openaire/broker/models/openaire-broker-event.model';
|
||||
|
||||
/**
|
||||
* When not in production, endpoint responses can be mocked for testing purposes
|
||||
@@ -343,6 +345,8 @@ export const models =
|
||||
ShortLivedToken,
|
||||
Registration,
|
||||
UsageReport,
|
||||
OpenaireBrokerTopicObject,
|
||||
OpenaireBrokerEventObject,
|
||||
Root,
|
||||
SearchConfig,
|
||||
SubmissionAccessesModel
|
||||
|
@@ -38,7 +38,7 @@ import {
|
||||
FindListOptions,
|
||||
PatchRequest,
|
||||
PutRequest,
|
||||
DeleteRequest
|
||||
DeleteRequest, DeleteByIDRequest, PostRequest
|
||||
} from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { RestRequestMethod } from './rest-request-method';
|
||||
@@ -579,6 +579,53 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
return result$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a post on an endpoint related item with ID. Ex.: endpoint/<itemId>/related?item=<relatedItemId>
|
||||
* @param itemId The item id
|
||||
* @param relatedItemId The related item Id
|
||||
* @param body The optional POST body
|
||||
* @return the RestResponse as an Observable
|
||||
*/
|
||||
public postOnRelated(itemId: string, relatedItemId: string, body?: any) {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
const hrefObs = this.getIDHrefObs(itemId);
|
||||
|
||||
hrefObs.pipe(
|
||||
take(1)
|
||||
).subscribe((href: string) => {
|
||||
const request = new PostRequest(requestId, href + '/related?item=' + relatedItemId, body);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.send(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildFromRequestUUID<T>(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a delete on an endpoint related item. Ex.: endpoint/<itemId>/related
|
||||
* @param itemId The item id
|
||||
* @return the RestResponse as an Observable
|
||||
*/
|
||||
public deleteOnRelated(itemId: string): Observable<RemoteData<NoContent>> {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
const hrefObs = this.getIDHrefObs(itemId);
|
||||
|
||||
hrefObs.pipe(
|
||||
find((href: string) => hasValue(href)),
|
||||
map((href: string) => {
|
||||
const request = new DeleteByIDRequest(requestId, href + '/related', itemId);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.send(request);
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing DSpace Object on the server
|
||||
* @param objectId The id of the object to be removed
|
||||
|
@@ -0,0 +1,246 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||
|
||||
import { RequestService } from '../../../data/request.service';
|
||||
import { buildPaginatedList } from '../../../data/paginated-list.model';
|
||||
import { RequestEntry } from '../../../data/request.reducer';
|
||||
import { FindListOptions } from '../../../data/request.models';
|
||||
import { RemoteDataBuildService } from '../../../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../../../cache/object-cache.service';
|
||||
import { RestResponse } from '../../../cache/response.models';
|
||||
import { PageInfo } from '../../../shared/page-info.model';
|
||||
import { HALEndpointService } from '../../../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { createSuccessfulRemoteDataObject } from '../../../../shared/remote-data.utils';
|
||||
import { OpenaireBrokerEventRestService } from './openaire-broker-event-rest.service';
|
||||
import {
|
||||
openaireBrokerEventObjectMissingPid,
|
||||
openaireBrokerEventObjectMissingPid2,
|
||||
openaireBrokerEventObjectMissingProjectFound
|
||||
} from '../../../../shared/mocks/openaire.mock';
|
||||
import { ReplaceOperation } from 'fast-json-patch';
|
||||
|
||||
describe('OpenaireBrokerEventRestService', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: OpenaireBrokerEventRestService;
|
||||
let serviceASAny: any;
|
||||
let responseCacheEntry: RequestEntry;
|
||||
let responseCacheEntryB: RequestEntry;
|
||||
let responseCacheEntryC: RequestEntry;
|
||||
let requestService: RequestService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: HALEndpointService;
|
||||
let notificationsService: NotificationsService;
|
||||
let http: HttpClient;
|
||||
let comparator: any;
|
||||
|
||||
const endpointURL = 'https://rest.api/rest/api/integration/nbtopics';
|
||||
const requestUUID = '8b3c913a-5a4b-438b-9181-be1a5b4a1c8a';
|
||||
const topic = 'ENRICH!MORE!PID';
|
||||
|
||||
const pageInfo = new PageInfo();
|
||||
const array = [ openaireBrokerEventObjectMissingPid, openaireBrokerEventObjectMissingPid2 ];
|
||||
const paginatedList = buildPaginatedList(pageInfo, array);
|
||||
const brokerEventObjectRD = createSuccessfulRemoteDataObject(openaireBrokerEventObjectMissingPid);
|
||||
const brokerEventObjectMissingProjectRD = createSuccessfulRemoteDataObject(openaireBrokerEventObjectMissingProjectFound);
|
||||
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||
|
||||
const status = 'ACCEPTED';
|
||||
const operation: ReplaceOperation<string>[] = [
|
||||
{
|
||||
path: '/status',
|
||||
op: 'replace',
|
||||
value: status
|
||||
}
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
responseCacheEntry = new RequestEntry();
|
||||
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: requestUUID,
|
||||
send: true,
|
||||
removeByHrefSubstring: {},
|
||||
getByHref: jasmine.createSpy('getByHref'),
|
||||
getByUUID: jasmine.createSpy('getByUUID')
|
||||
});
|
||||
|
||||
responseCacheEntryB = new RequestEntry();
|
||||
responseCacheEntryB.request = { href: 'https://rest.api/' } as any;
|
||||
responseCacheEntryB.response = new RestResponse(true, 201, 'Created');
|
||||
|
||||
responseCacheEntryC = new RequestEntry();
|
||||
responseCacheEntryC.request = { href: 'https://rest.api/' } as any;
|
||||
responseCacheEntryC.response = new RestResponse(true, 204, 'No Content');
|
||||
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: cold('(a)', {
|
||||
a: brokerEventObjectRD
|
||||
}),
|
||||
buildList: cold('(a)', {
|
||||
a: paginatedListRD
|
||||
}),
|
||||
buildFromRequestUUID: jasmine.createSpy('buildFromRequestUUID')
|
||||
});
|
||||
|
||||
objectCache = {} as ObjectCacheService;
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: cold('a|', { a: endpointURL })
|
||||
});
|
||||
|
||||
notificationsService = {} as NotificationsService;
|
||||
http = {} as HttpClient;
|
||||
comparator = {} as any;
|
||||
|
||||
service = new OpenaireBrokerEventRestService(
|
||||
requestService,
|
||||
rdbService,
|
||||
objectCache,
|
||||
halService,
|
||||
notificationsService,
|
||||
http,
|
||||
comparator
|
||||
);
|
||||
|
||||
serviceASAny = service;
|
||||
|
||||
spyOn(serviceASAny.dataService, 'searchBy').and.callThrough();
|
||||
spyOn(serviceASAny.dataService, 'findById').and.callThrough();
|
||||
spyOn(serviceASAny.dataService, 'patch').and.callThrough();
|
||||
spyOn(serviceASAny.dataService, 'postOnRelated').and.callThrough();
|
||||
spyOn(serviceASAny.dataService, 'deleteOnRelated').and.callThrough();
|
||||
});
|
||||
|
||||
describe('getEventsByTopic', () => {
|
||||
beforeEach(() => {
|
||||
serviceASAny.requestService.getByHref.and.returnValue(observableOf(responseCacheEntry));
|
||||
serviceASAny.requestService.getByUUID.and.returnValue(observableOf(responseCacheEntry));
|
||||
serviceASAny.rdbService.buildFromRequestUUID.and.returnValue(observableOf(brokerEventObjectRD));
|
||||
});
|
||||
|
||||
it('should proxy the call to dataservice.searchBy', () => {
|
||||
const options: FindListOptions = {
|
||||
searchParams: [
|
||||
{
|
||||
fieldName: 'topic',
|
||||
fieldValue: topic
|
||||
}
|
||||
]
|
||||
};
|
||||
service.getEventsByTopic(topic);
|
||||
expect(serviceASAny.dataService.searchBy).toHaveBeenCalledWith('findByTopic', options, true, true);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<PaginatedList<OpenaireBrokerEventObject>> for the object with the given Topic', () => {
|
||||
const result = service.getEventsByTopic(topic);
|
||||
const expected = cold('(a)', {
|
||||
a: paginatedListRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEvent', () => {
|
||||
beforeEach(() => {
|
||||
serviceASAny.requestService.getByHref.and.returnValue(observableOf(responseCacheEntry));
|
||||
serviceASAny.requestService.getByUUID.and.returnValue(observableOf(responseCacheEntry));
|
||||
serviceASAny.rdbService.buildFromRequestUUID.and.returnValue(observableOf(brokerEventObjectRD));
|
||||
});
|
||||
|
||||
it('should proxy the call to dataservice.findById', () => {
|
||||
service.getEvent(openaireBrokerEventObjectMissingPid.id).subscribe(
|
||||
(res) => {
|
||||
expect(serviceASAny.dataService.findById).toHaveBeenCalledWith(openaireBrokerEventObjectMissingPid.id, true, true);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<OpenaireBrokerEventObject> for the object with the given URL', () => {
|
||||
const result = service.getEvent(openaireBrokerEventObjectMissingPid.id);
|
||||
const expected = cold('(a)', {
|
||||
a: brokerEventObjectRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('patchEvent', () => {
|
||||
beforeEach(() => {
|
||||
serviceASAny.requestService.getByHref.and.returnValue(observableOf(responseCacheEntry));
|
||||
serviceASAny.requestService.getByUUID.and.returnValue(observableOf(responseCacheEntry));
|
||||
serviceASAny.rdbService.buildFromRequestUUID.and.returnValue(observableOf(brokerEventObjectRD));
|
||||
});
|
||||
|
||||
it('should proxy the call to dataservice.patch', () => {
|
||||
service.patchEvent(status, openaireBrokerEventObjectMissingPid).subscribe(
|
||||
(res) => {
|
||||
expect(serviceASAny.dataService.patch).toHaveBeenCalledWith(openaireBrokerEventObjectMissingPid, operation);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return a RemoteData with HTTP 200', () => {
|
||||
const result = service.patchEvent(status, openaireBrokerEventObjectMissingPid);
|
||||
const expected = cold('(a|)', {
|
||||
a: createSuccessfulRemoteDataObject(openaireBrokerEventObjectMissingPid)
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('boundProject', () => {
|
||||
beforeEach(() => {
|
||||
serviceASAny.requestService.getByHref.and.returnValue(observableOf(responseCacheEntryB));
|
||||
serviceASAny.requestService.getByUUID.and.returnValue(observableOf(responseCacheEntryB));
|
||||
serviceASAny.rdbService.buildFromRequestUUID.and.returnValue(observableOf(brokerEventObjectMissingProjectRD));
|
||||
});
|
||||
|
||||
it('should proxy the call to dataservice.postOnRelated', () => {
|
||||
service.boundProject(openaireBrokerEventObjectMissingProjectFound.id, requestUUID).subscribe(
|
||||
(res) => {
|
||||
expect(serviceASAny.dataService.postOnRelated).toHaveBeenCalledWith(openaireBrokerEventObjectMissingProjectFound.id, requestUUID);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return a RestResponse with HTTP 201', () => {
|
||||
const result = service.boundProject(openaireBrokerEventObjectMissingProjectFound.id, requestUUID);
|
||||
const expected = cold('(a|)', {
|
||||
a: createSuccessfulRemoteDataObject(openaireBrokerEventObjectMissingProjectFound)
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeProject', () => {
|
||||
beforeEach(() => {
|
||||
serviceASAny.requestService.getByHref.and.returnValue(observableOf(responseCacheEntryC));
|
||||
serviceASAny.requestService.getByUUID.and.returnValue(observableOf(responseCacheEntryC));
|
||||
serviceASAny.rdbService.buildFromRequestUUID.and.returnValue(observableOf(createSuccessfulRemoteDataObject({})));
|
||||
});
|
||||
|
||||
it('should proxy the call to dataservice.deleteOnRelated', () => {
|
||||
service.removeProject(openaireBrokerEventObjectMissingProjectFound.id).subscribe(
|
||||
(res) => {
|
||||
expect(serviceASAny.dataService.deleteOnRelated).toHaveBeenCalledWith(openaireBrokerEventObjectMissingProjectFound.id);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return a RestResponse with HTTP 204', () => {
|
||||
const result = service.removeProject(openaireBrokerEventObjectMissingProjectFound.id);
|
||||
const expected = cold('(a|)', {
|
||||
a: createSuccessfulRemoteDataObject({})
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,185 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { CoreState } from '../../../core.reducers';
|
||||
import { HALEndpointService } from '../../../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { RemoteDataBuildService } from '../../../cache/builders/remote-data-build.service';
|
||||
import { RestResponse } from '../../../cache/response.models';
|
||||
import { ObjectCacheService } from '../../../cache/object-cache.service';
|
||||
import { dataService } from '../../../cache/builders/build-decorators';
|
||||
import { RequestService } from '../../../data/request.service';
|
||||
import { FindListOptions } from '../../../data/request.models';
|
||||
import { DataService } from '../../../data/data.service';
|
||||
import { ChangeAnalyzer } from '../../../data/change-analyzer';
|
||||
import { DefaultChangeAnalyzer } from '../../../data/default-change-analyzer.service';
|
||||
import { RemoteData } from '../../../data/remote-data';
|
||||
import { OpenaireBrokerEventObject } from '../models/openaire-broker-event.model';
|
||||
import { OPENAIRE_BROKER_EVENT_OBJECT } from '../models/openaire-broker-event-object.resource-type';
|
||||
import { FollowLinkConfig } from '../../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../../data/paginated-list.model';
|
||||
import { ReplaceOperation } from 'fast-json-patch';
|
||||
import { NoContent } from '../../../shared/NoContent.model';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
*/
|
||||
class DataServiceImpl extends DataService<OpenaireBrokerEventObject> {
|
||||
/**
|
||||
* The REST endpoint.
|
||||
*/
|
||||
protected linkPath = 'nbevents';
|
||||
|
||||
/**
|
||||
* Initialize service variables
|
||||
* @param {RequestService} requestService
|
||||
* @param {RemoteDataBuildService} rdbService
|
||||
* @param {Store<CoreState>} store
|
||||
* @param {ObjectCacheService} objectCache
|
||||
* @param {HALEndpointService} halService
|
||||
* @param {NotificationsService} notificationsService
|
||||
* @param {HttpClient} http
|
||||
* @param {ChangeAnalyzer<OpenaireBrokerEventObject>} comparator
|
||||
*/
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: ChangeAnalyzer<OpenaireBrokerEventObject>) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The service handling all OpenAIRE Broker topic REST requests.
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(OPENAIRE_BROKER_EVENT_OBJECT)
|
||||
export class OpenaireBrokerEventRestService {
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
*/
|
||||
private dataService: DataServiceImpl;
|
||||
|
||||
/**
|
||||
* Initialize service variables
|
||||
* @param {RequestService} requestService
|
||||
* @param {RemoteDataBuildService} rdbService
|
||||
* @param {ObjectCacheService} objectCache
|
||||
* @param {HALEndpointService} halService
|
||||
* @param {NotificationsService} notificationsService
|
||||
* @param {HttpClient} http
|
||||
* @param {DefaultChangeAnalyzer<OpenaireBrokerEventObject>} comparator
|
||||
*/
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<OpenaireBrokerEventObject>) {
|
||||
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of OpenAIRE Broker events by topic.
|
||||
*
|
||||
* @param topic
|
||||
* The OpenAIRE Broker topic
|
||||
* @param options
|
||||
* Find list options object.
|
||||
* @param linksToFollow
|
||||
* List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
||||
* @return Observable<RemoteData<PaginatedList<OpenaireBrokerEventObject>>>
|
||||
* The list of OpenAIRE Broker events.
|
||||
*/
|
||||
public getEventsByTopic(topic: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<OpenaireBrokerEventObject>[]): Observable<RemoteData<PaginatedList<OpenaireBrokerEventObject>>> {
|
||||
options.searchParams = [
|
||||
{
|
||||
fieldName: 'topic',
|
||||
fieldValue: topic
|
||||
}
|
||||
];
|
||||
return this.dataService.searchBy('findByTopic', options, true, true, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear findByTopic requests from cache
|
||||
*/
|
||||
public clearFindByTopicRequests() {
|
||||
this.requestService.removeByHrefSubstring('findByTopic');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a single OpenAIRE Broker event.
|
||||
*
|
||||
* @param id
|
||||
* The OpenAIRE Broker event id
|
||||
* @param linksToFollow
|
||||
* List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return Observable<RemoteData<OpenaireBrokerEventObject>>
|
||||
* The OpenAIRE Broker event.
|
||||
*/
|
||||
public getEvent(id: string, ...linksToFollow: FollowLinkConfig<OpenaireBrokerEventObject>[]): Observable<RemoteData<OpenaireBrokerEventObject>> {
|
||||
return this.dataService.findById(id, true, true, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the new status of an OpenAIRE Broker event.
|
||||
*
|
||||
* @param status
|
||||
* The new status
|
||||
* @param dso OpenaireBrokerEventObject
|
||||
* The event item
|
||||
* @param reason
|
||||
* The optional reason (not used for now; for future implementation)
|
||||
* @return Observable<RestResponse>
|
||||
* The REST response.
|
||||
*/
|
||||
public patchEvent(status, dso, reason?: string): Observable<RemoteData<OpenaireBrokerEventObject>> {
|
||||
const operation: ReplaceOperation<string>[] = [
|
||||
{
|
||||
path: '/status',
|
||||
op: 'replace',
|
||||
value: status
|
||||
}
|
||||
];
|
||||
return this.dataService.patch(dso, operation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bound a project to an OpenAIRE Broker event publication.
|
||||
*
|
||||
* @param itemId
|
||||
* The Id of the OpenAIRE Broker event
|
||||
* @param projectId
|
||||
* The project Id to bound
|
||||
* @return Observable<RestResponse>
|
||||
* The REST response.
|
||||
*/
|
||||
public boundProject(itemId: string, projectId: string): Observable<RemoteData<OpenaireBrokerEventObject>> {
|
||||
return this.dataService.postOnRelated(itemId, projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a project from an OpenAIRE Broker event publication.
|
||||
*
|
||||
* @param itemId
|
||||
* The Id of the OpenAIRE Broker event
|
||||
* @return Observable<RestResponse>
|
||||
* The REST response.
|
||||
*/
|
||||
public removeProject(itemId: string): Observable<RemoteData<NoContent>> {
|
||||
return this.dataService.deleteOnRelated(itemId);
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
import { ResourceType } from '../../../shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for the OpenAIRE Broker event
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
export const OPENAIRE_BROKER_EVENT_OBJECT = new ResourceType('nbevent');
|
@@ -0,0 +1,157 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
|
||||
import { CacheableObject } from '../../../cache/object-cache.reducer';
|
||||
import { OPENAIRE_BROKER_EVENT_OBJECT } from './openaire-broker-event-object.resource-type';
|
||||
import { excludeFromEquals } from '../../../utilities/equals.decorators';
|
||||
import { ResourceType } from '../../../shared/resource-type';
|
||||
import { HALLink } from '../../../shared/hal-link.model';
|
||||
import { Item } from '../../../shared/item.model';
|
||||
import { ITEM } from '../../../shared/item.resource-type';
|
||||
import { link, typedObject } from '../../../cache/builders/build-decorators';
|
||||
import { RemoteData } from '../../../data/remote-data';
|
||||
|
||||
/**
|
||||
* The interface representing the OpenAIRE Broker event message
|
||||
*/
|
||||
export interface OpenaireBrokerEventMessageObject {
|
||||
/**
|
||||
* The type of 'value'
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* The value suggested by OpenAIRE
|
||||
*/
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* The abstract suggested by OpenAIRE
|
||||
*/
|
||||
abstract: string;
|
||||
|
||||
/**
|
||||
* The project acronym suggested by OpenAIRE
|
||||
*/
|
||||
acronym: string;
|
||||
|
||||
/**
|
||||
* The project code suggested by OpenAIRE
|
||||
*/
|
||||
code: string;
|
||||
|
||||
/**
|
||||
* The project funder suggested by OpenAIRE
|
||||
*/
|
||||
funder: string;
|
||||
|
||||
/**
|
||||
* The project program suggested by OpenAIRE
|
||||
*/
|
||||
fundingProgram?: string;
|
||||
|
||||
/**
|
||||
* The project jurisdiction suggested by OpenAIRE
|
||||
*/
|
||||
jurisdiction: string;
|
||||
|
||||
/**
|
||||
* The project title suggested by OpenAIRE
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* The OpenAIRE ID.
|
||||
*/
|
||||
openaireId: string;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface representing the OpenAIRE Broker event model
|
||||
*/
|
||||
@typedObject
|
||||
export class OpenaireBrokerEventObject implements CacheableObject {
|
||||
/**
|
||||
* A string representing the kind of object, e.g. community, item, …
|
||||
*/
|
||||
static type = OPENAIRE_BROKER_EVENT_OBJECT;
|
||||
|
||||
/**
|
||||
* The OpenAIRE Broker event uuid inside DSpace
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The universally unique identifier of this OpenAIRE Broker event
|
||||
*/
|
||||
@autoserializeAs(String, 'id')
|
||||
uuid: string;
|
||||
|
||||
/**
|
||||
* The OpenAIRE Broker event original id (ex.: the source archive OAI-PMH identifier)
|
||||
*/
|
||||
@autoserialize
|
||||
originalId: string;
|
||||
|
||||
/**
|
||||
* The title of the article to which the suggestion refers
|
||||
*/
|
||||
@autoserialize
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* Reliability of the suggestion (of the data inside 'message')
|
||||
*/
|
||||
@autoserialize
|
||||
trust: number;
|
||||
|
||||
/**
|
||||
* The timestamp OpenAIRE Broker event was saved in DSpace
|
||||
*/
|
||||
@autoserialize
|
||||
eventDate: string;
|
||||
|
||||
/**
|
||||
* The OpenAIRE Broker event status (ACCEPTED, REJECTED, DISCARDED, PENDING)
|
||||
*/
|
||||
@autoserialize
|
||||
status: string;
|
||||
|
||||
/**
|
||||
* The suggestion data. Data may vary depending on the topic
|
||||
*/
|
||||
@autoserialize
|
||||
message: OpenaireBrokerEventMessageObject;
|
||||
|
||||
/**
|
||||
* The type of this ConfigObject
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
type: ResourceType;
|
||||
|
||||
/**
|
||||
* The links to all related resources returned by the rest api.
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink,
|
||||
target: HALLink,
|
||||
related: HALLink
|
||||
};
|
||||
|
||||
/**
|
||||
* The related publication DSpace item
|
||||
* Will be undefined unless the {@item HALLink} has been resolved.
|
||||
*/
|
||||
@link(ITEM)
|
||||
target?: Observable<RemoteData<Item>>;
|
||||
|
||||
/**
|
||||
* The related project for this Event
|
||||
* Will be undefined unless the {@related HALLink} has been resolved.
|
||||
*/
|
||||
@link(ITEM)
|
||||
related?: Observable<RemoteData<Item>>;
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
import { ResourceType } from '../../../shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for the OpenAIRE Broker topic
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
export const OPENAIRE_BROKER_TOPIC_OBJECT = new ResourceType('nbtopic');
|
@@ -0,0 +1,58 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
|
||||
import { CacheableObject } from '../../../cache/object-cache.reducer';
|
||||
import { OPENAIRE_BROKER_TOPIC_OBJECT } from './openaire-broker-topic-object.resource-type';
|
||||
import { excludeFromEquals } from '../../../utilities/equals.decorators';
|
||||
import { ResourceType } from '../../../shared/resource-type';
|
||||
import { HALLink } from '../../../shared/hal-link.model';
|
||||
import { typedObject } from '../../../cache/builders/build-decorators';
|
||||
|
||||
/**
|
||||
* The interface representing the OpenAIRE Broker topic model
|
||||
*/
|
||||
@typedObject
|
||||
export class OpenaireBrokerTopicObject implements CacheableObject {
|
||||
/**
|
||||
* A string representing the kind of object, e.g. community, item, …
|
||||
*/
|
||||
static type = OPENAIRE_BROKER_TOPIC_OBJECT;
|
||||
|
||||
/**
|
||||
* The OpenAIRE Broker topic id
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The OpenAIRE Broker topic name to display
|
||||
*/
|
||||
@autoserialize
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The date of the last udate from OpenAIRE
|
||||
*/
|
||||
@autoserialize
|
||||
lastEvent: string;
|
||||
|
||||
/**
|
||||
* The total number of suggestions provided by OpenAIRE for this topic
|
||||
*/
|
||||
@autoserialize
|
||||
totalEvents: number;
|
||||
|
||||
/**
|
||||
* The type of this ConfigObject
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
type: ResourceType;
|
||||
|
||||
/**
|
||||
* The links to all related resources returned by the rest api.
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink,
|
||||
};
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||
|
||||
import { RequestService } from '../../../data/request.service';
|
||||
import { buildPaginatedList } from '../../../data/paginated-list.model';
|
||||
import { RequestEntry } from '../../../data/request.reducer';
|
||||
import { RemoteDataBuildService } from '../../../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../../../cache/object-cache.service';
|
||||
import { RestResponse } from '../../../cache/response.models';
|
||||
import { PageInfo } from '../../../shared/page-info.model';
|
||||
import { HALEndpointService } from '../../../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { createSuccessfulRemoteDataObject } from '../../../../shared/remote-data.utils';
|
||||
import { OpenaireBrokerTopicRestService } from './openaire-broker-topic-rest.service';
|
||||
import {
|
||||
openaireBrokerTopicObjectMoreAbstract,
|
||||
openaireBrokerTopicObjectMorePid
|
||||
} from '../../../../shared/mocks/openaire.mock';
|
||||
|
||||
describe('OpenaireBrokerTopicRestService', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: OpenaireBrokerTopicRestService;
|
||||
let responseCacheEntry: RequestEntry;
|
||||
let requestService: RequestService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: HALEndpointService;
|
||||
let notificationsService: NotificationsService;
|
||||
let http: HttpClient;
|
||||
let comparator: any;
|
||||
|
||||
const endpointURL = 'https://rest.api/rest/api/integration/nbtopics';
|
||||
const requestUUID = '8b3c913a-5a4b-438b-9181-be1a5b4a1c8a';
|
||||
|
||||
const pageInfo = new PageInfo();
|
||||
const array = [ openaireBrokerTopicObjectMorePid, openaireBrokerTopicObjectMoreAbstract ];
|
||||
const paginatedList = buildPaginatedList(pageInfo, array);
|
||||
const brokerTopicObjectRD = createSuccessfulRemoteDataObject(openaireBrokerTopicObjectMorePid);
|
||||
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
responseCacheEntry = new RequestEntry();
|
||||
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: requestUUID,
|
||||
send: true,
|
||||
removeByHrefSubstring: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: observableOf(responseCacheEntry),
|
||||
});
|
||||
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: cold('(a)', {
|
||||
a: brokerTopicObjectRD
|
||||
}),
|
||||
buildList: cold('(a)', {
|
||||
a: paginatedListRD
|
||||
}),
|
||||
});
|
||||
|
||||
objectCache = {} as ObjectCacheService;
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: cold('a|', { a: endpointURL })
|
||||
});
|
||||
|
||||
notificationsService = {} as NotificationsService;
|
||||
http = {} as HttpClient;
|
||||
comparator = {} as any;
|
||||
|
||||
service = new OpenaireBrokerTopicRestService(
|
||||
requestService,
|
||||
rdbService,
|
||||
objectCache,
|
||||
halService,
|
||||
notificationsService,
|
||||
http,
|
||||
comparator
|
||||
);
|
||||
|
||||
spyOn((service as any).dataService, 'findAllByHref').and.callThrough();
|
||||
spyOn((service as any).dataService, 'findByHref').and.callThrough();
|
||||
});
|
||||
|
||||
describe('getTopics', () => {
|
||||
it('should proxy the call to dataservice.findAllByHref', (done) => {
|
||||
service.getTopics().subscribe(
|
||||
(res) => {
|
||||
expect((service as any).dataService.findAllByHref).toHaveBeenCalledWith(endpointURL, {}, true, true);
|
||||
}
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return a RemoteData<PaginatedList<OpenaireBrokerTopicObject>> for the object with the given URL', () => {
|
||||
const result = service.getTopics();
|
||||
const expected = cold('(a)', {
|
||||
a: paginatedListRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTopic', () => {
|
||||
it('should proxy the call to dataservice.findByHref', (done) => {
|
||||
service.getTopic(openaireBrokerTopicObjectMorePid.id).subscribe(
|
||||
(res) => {
|
||||
expect((service as any).dataService.findByHref).toHaveBeenCalledWith(endpointURL + '/' + openaireBrokerTopicObjectMorePid.id, true, true);
|
||||
}
|
||||
);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should return a RemoteData<OpenaireBrokerTopicObject> for the object with the given URL', () => {
|
||||
const result = service.getTopic(openaireBrokerTopicObjectMorePid.id);
|
||||
const expected = cold('(a)', {
|
||||
a: brokerTopicObjectRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,133 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
import { mergeMap, take } from 'rxjs/operators';
|
||||
|
||||
import { CoreState } from '../../../core.reducers';
|
||||
import { HALEndpointService } from '../../../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { RemoteDataBuildService } from '../../../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../../../cache/object-cache.service';
|
||||
import { dataService } from '../../../cache/builders/build-decorators';
|
||||
import { RequestService } from '../../../data/request.service';
|
||||
import { FindListOptions } from '../../../data/request.models';
|
||||
import { DataService } from '../../../data/data.service';
|
||||
import { ChangeAnalyzer } from '../../../data/change-analyzer';
|
||||
import { DefaultChangeAnalyzer } from '../../../data/default-change-analyzer.service';
|
||||
import { RemoteData } from '../../../data/remote-data';
|
||||
import { OpenaireBrokerTopicObject } from '../models/openaire-broker-topic.model';
|
||||
import { OPENAIRE_BROKER_TOPIC_OBJECT } from '../models/openaire-broker-topic-object.resource-type';
|
||||
import { FollowLinkConfig } from '../../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../../data/paginated-list.model';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
*/
|
||||
class DataServiceImpl extends DataService<OpenaireBrokerTopicObject> {
|
||||
/**
|
||||
* The REST endpoint.
|
||||
*/
|
||||
protected linkPath = 'nbtopics';
|
||||
|
||||
/**
|
||||
* Initialize service variables
|
||||
* @param {RequestService} requestService
|
||||
* @param {RemoteDataBuildService} rdbService
|
||||
* @param {Store<CoreState>} store
|
||||
* @param {ObjectCacheService} objectCache
|
||||
* @param {HALEndpointService} halService
|
||||
* @param {NotificationsService} notificationsService
|
||||
* @param {HttpClient} http
|
||||
* @param {ChangeAnalyzer<OpenaireBrokerTopicObject>} comparator
|
||||
*/
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: ChangeAnalyzer<OpenaireBrokerTopicObject>) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The service handling all OpenAIRE Broker topic REST requests.
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(OPENAIRE_BROKER_TOPIC_OBJECT)
|
||||
export class OpenaireBrokerTopicRestService {
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
*/
|
||||
private dataService: DataServiceImpl;
|
||||
|
||||
/**
|
||||
* Initialize service variables
|
||||
* @param {RequestService} requestService
|
||||
* @param {RemoteDataBuildService} rdbService
|
||||
* @param {ObjectCacheService} objectCache
|
||||
* @param {HALEndpointService} halService
|
||||
* @param {NotificationsService} notificationsService
|
||||
* @param {HttpClient} http
|
||||
* @param {DefaultChangeAnalyzer<OpenaireBrokerTopicObject>} comparator
|
||||
*/
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<OpenaireBrokerTopicObject>) {
|
||||
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of OpenAIRE Broker topics.
|
||||
*
|
||||
* @param options
|
||||
* Find list options object.
|
||||
* @param linksToFollow
|
||||
* List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
||||
* @return Observable<RemoteData<PaginatedList<OpenaireBrokerTopicObject>>>
|
||||
* The list of OpenAIRE Broker topics.
|
||||
*/
|
||||
public getTopics(options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<OpenaireBrokerTopicObject>[]): Observable<RemoteData<PaginatedList<OpenaireBrokerTopicObject>>> {
|
||||
return this.dataService.getBrowseEndpoint(options, 'nbtopics').pipe(
|
||||
take(1),
|
||||
mergeMap((href: string) => this.dataService.findAllByHref(href, options, true, true, ...linksToFollow)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear FindAll topics requests from cache
|
||||
*/
|
||||
public clearFindAllTopicsRequests() {
|
||||
this.requestService.setStaleByHrefSubstring('nbtopics');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a single OpenAIRE Broker topic.
|
||||
*
|
||||
* @param id
|
||||
* The OpenAIRE Broker topic id
|
||||
* @param linksToFollow
|
||||
* List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
||||
* @return Observable<RemoteData<OpenaireBrokerTopicObject>>
|
||||
* The OpenAIRE Broker topic.
|
||||
*/
|
||||
public getTopic(id: string, ...linksToFollow: FollowLinkConfig<OpenaireBrokerTopicObject>[]): Observable<RemoteData<OpenaireBrokerTopicObject>> {
|
||||
const options = {};
|
||||
return this.dataService.getBrowseEndpoint(options, 'nbtopics').pipe(
|
||||
take(1),
|
||||
mergeMap((href: string) => this.dataService.findByHref(href + '/' + id, true, true, ...linksToFollow))
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,207 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2 class="border-bottom pb-2">{{'openaire.events.title'| translate}}</h2>
|
||||
<p>{{'openaire.broker.events.description'| translate}}</p>
|
||||
<p>
|
||||
<a class="btn btn-outline-secondary" [routerLink]="['/admin/notifications/openaire-broker']">
|
||||
<i class="fas fa-angle-double-left"></i>
|
||||
{{'openaire.broker.events.back' | translate}}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h3 class="border-bottom pb-2">
|
||||
{{'openaire.broker.events.topic' | translate}} {{this.showTopic}}
|
||||
</h3>
|
||||
|
||||
<ds-loading class="container" *ngIf="(isEventPageLoading | async)" message="{{'openaire.broker.loading' | translate}}"></ds-loading>
|
||||
|
||||
<ds-pagination *ngIf="!(isEventPageLoading | async)"
|
||||
[paginationOptions]="paginationConfig"
|
||||
[collectionSize]="(totalElements$ | async)"
|
||||
[sortOptions]="paginationSortConfig"
|
||||
(paginationChange)="getOpenaireBrokerEvents()">
|
||||
|
||||
<ds-loading class="container" *ngIf="(isEventLoading | async)" message="{{'openaire.broker.loading' | translate}}"></ds-loading>
|
||||
<ng-container *ngIf="!(isEventLoading | async)">
|
||||
<div *ngIf="(eventsUpdated$|async)?.length == 0" class="alert alert-info w-100 mb-2 mt-2" role="alert">
|
||||
{{'openaire.broker.noEvents' | translate}}
|
||||
</div>
|
||||
<div *ngIf="(eventsUpdated$|async)?.length != 0" class="table-responsive mt-2">
|
||||
<table id="events" class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{'openaire.broker.event.table.trust' | translate}}</th>
|
||||
<th scope="col">{{'openaire.broker.event.table.publication' | translate}}</th>
|
||||
<th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') == -1" scope="col">{{'openaire.broker.event.table.details' | translate}}</th>
|
||||
<th *ngIf="hasDetailColumn() && showTopic.indexOf('/PROJECT') !== -1" scope="col">{{'openaire.broker.event.table.project-details' | translate}}</th>
|
||||
<th scope="col" class="button-rows">{{'openaire.broker.event.table.actions' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let eventElement of (eventsUpdated$ | async); let i = index">
|
||||
<td>{{eventElement?.event?.trust}}
|
||||
</td>
|
||||
<td><a *ngIf="eventElement?.target"
|
||||
target="_blank"
|
||||
[routerLink]="['/items', eventElement?.target?.id]">{{eventElement.title}}</a>
|
||||
<span *ngIf="!eventElement?.target">{{eventElement.title}}</span>
|
||||
</td>
|
||||
<td *ngIf="showTopic.indexOf('/PID') !== -1">
|
||||
<p><span class="small">{{'openaire.broker.event.table.pidtype' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.type}}</span></p>
|
||||
<p><span class="small">{{'openaire.broker.event.table.pidvalue' | translate}}</span><br>
|
||||
<a *ngIf="hasPIDHref(eventElement.event.message); else noPID" href="{{getPIDHref(eventElement.event.message)}}" target="_blank">
|
||||
{{eventElement.event.message.value}}
|
||||
</a>
|
||||
<ng-template #noPID><span class="badge badge-info">{{eventElement.event.message.value}}</span></ng-template>
|
||||
</p>
|
||||
</td>
|
||||
<td *ngIf="showTopic.indexOf('/SUBJECT') !== -1">
|
||||
<p><span class="small">{{'openaire.broker.event.table.subjectValue' | translate}}</span><br><span class="badge badge-info">{{eventElement.event.message.value}}</span></p>
|
||||
</td>
|
||||
<td *ngIf="showTopic.indexOf('/ABSTRACT') !== -1">
|
||||
<p class="abstract-container" [class.show]="showMore">
|
||||
<span class="small">{{'openaire.broker.event.table.abstract' | translate}}</span><br>
|
||||
<span class="text-ellipsis">{{eventElement.event.message.abstract}}</span>
|
||||
</p>
|
||||
<button class="btn btn-outline-primary btn-sm" (click)="showMore = !showMore">
|
||||
<i *ngIf="!showMore" class="fas fa-angle-down"></i>
|
||||
<i *ngIf="showMore" class="fas fa-angle-up"></i>
|
||||
{{ (showMore ? 'openaire.broker.event.table.less': 'openaire.broker.event.table.more') | translate }}
|
||||
</button>
|
||||
</td>
|
||||
<td *ngIf="showTopic.indexOf('/PROJECT') !== -1">
|
||||
<p>
|
||||
{{'openaire.broker.event.table.suggestedProject' | translate}}
|
||||
</p>
|
||||
<p>
|
||||
<span class="small">{{'openaire.broker.event.table.project' | translate}}</span><br>
|
||||
<a href="https://explore.openaire.eu/search/project?projectId={{ eventElement.event.message.openaireId}}" target="_blank">{{eventElement.event.message.title}}</a>
|
||||
</p>
|
||||
<p>
|
||||
<span *ngIf="eventElement.event.message.acronym"><span class="small">{{'openaire.broker.event.table.acronym' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.acronym}}</span><br></span>
|
||||
<span *ngIf="eventElement.event.message.code"><span class="small">{{'openaire.broker.event.table.code' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.code}}</span><br></span>
|
||||
<span *ngIf="eventElement.event.message.funder"><span class="small">{{'openaire.broker.event.table.funder' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.funder}}</span><br></span>
|
||||
<span *ngIf="eventElement.event.message.fundingProgram"><span class="small">{{'openaire.broker.event.table.fundingProgram' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.fundingProgram}}</span><br></span>
|
||||
<span *ngIf="eventElement.event.message.jurisdiction"><span class="small">{{'openaire.broker.event.table.jurisdiction' | translate}}</span> <span class="badge badge-info">{{eventElement.event.message.jurisdiction}}</span></span>
|
||||
</p>
|
||||
<hr>
|
||||
<div>
|
||||
{{(eventElement.hasProject ? 'openaire.broker.event.project.found' : 'openaire.broker.event.project.notFound') | translate}}
|
||||
<a target="_blank" *ngIf="eventElement.hasProject" title="{{eventElement.projectTitle}}" [routerLink]="['/items', eventElement.projectId]">{{eventElement.handle}}</a>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-outline-primary btn-sm"
|
||||
[disabled]="eventElement.isRunning"
|
||||
(click)="openModalLookup(eventElement); $event.stopPropagation();">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
<button *ngIf="eventElement.hasProject"
|
||||
class="btn btn-outline-danger btn-sm"
|
||||
[disabled]="eventElement.isRunning"
|
||||
(click)="removeProject(eventElement)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group-vertical button-width">
|
||||
<button *ngIf="showTopic.indexOf('/PROJECT') !== -1"
|
||||
class="btn btn-outline-success btn-sm button-width"
|
||||
[disabled]="eventElement.isRunning"
|
||||
(click)="modalChoice('ACCEPTED', eventElement, acceptModal)">
|
||||
<i class="fas fa-check"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.import' | translate}}</span>
|
||||
</button>
|
||||
<button *ngIf="showTopic.indexOf('/PROJECT') == -1" class="btn btn-outline-success btn-sm button-width" [disabled]="eventElement.isRunning" (click)="executeAction('ACCEPTED', eventElement)">
|
||||
<i class="fas fa-check"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.accept' | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-outline-dark btn-sm button-width" [disabled]="eventElement.isRunning" (click)="openModal('DISCARDED', eventElement, ignoreModal)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.ignore' | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger btn-sm button-width" [disabled]="eventElement.isRunning" (click)="openModal('REJECTED', eventElement, rejectModal)">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.reject' | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ds-pagination>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<a class="btn btn-outline-secondary" [routerLink]="['/admin/notifications/openaire-broker']">
|
||||
<i class="fas fa-angle-double-left"></i>
|
||||
{{'openaire.broker.events.back' | translate}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #acceptModal let-modal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="acceptModal">{{'openaire.broker.event.sure' | translate}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'openaire.broker.event.accept.description' | translate}}</p>
|
||||
|
||||
<button class="btn btn-outline-success float-left" (click)="modal.close('do')">
|
||||
<i class="fas fa-check"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.import' | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary float-right" (click)="modal.close('cancel')">
|
||||
<i class="fas fa-close"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.cancel' | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #ignoreModal let-modal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="ignoreModal">{{'openaire.broker.event.sure' | translate}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'openaire.broker.event.ignore.description' | translate}}</p>
|
||||
|
||||
<!-- textarea class="form-control mb-2" [(ngModel)]="selectedReason" placeholder="{{'openaire.broker.event.reason' |translate}}"></textarea -->
|
||||
|
||||
<button class="btn btn-outline-danger float-left" (click)="modal.close('do')">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.ignore' | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary float-right" (click)="modal.close('cancel')">
|
||||
<i class="fas fa-close"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.cancel' | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #rejectModal let-modal>
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="rejectModal">{{'openaire.broker.event.sure' | translate}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{'openaire.broker.event.reject.description' | translate}}</p>
|
||||
|
||||
<!-- textarea class="form-control mb-2" [(ngModel)]="selectedReason" placeholder="{{'openaire.broker.event.reason' |translate}}"></textarea -->
|
||||
|
||||
<button class="btn btn-outline-danger float-left" (click)="modal.close('do')">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.reject' | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary float-right" (click)="modal.close('cancel')">
|
||||
<i class="fas fa-close"></i>
|
||||
<span class="d-none d-sm-inline">{{'openaire.broker.event.action.cancel' | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ng-template>
|
@@ -0,0 +1,332 @@
|
||||
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { OpenaireBrokerEventRestService } from '../../../core/openaire/broker/events/openaire-broker-event-rest.service';
|
||||
import { OpenaireBrokerEventsComponent } from './openaire-broker-events.component';
|
||||
import {
|
||||
getMockOpenaireBrokerEventRestService,
|
||||
ItemMockPid10,
|
||||
ItemMockPid8,
|
||||
ItemMockPid9,
|
||||
openaireBrokerEventObjectMissingProjectFound,
|
||||
openaireBrokerEventObjectMissingProjectNotFound,
|
||||
OpenaireMockDspaceObject
|
||||
} from '../../../shared/mocks/openaire.mock';
|
||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock';
|
||||
import { createTestComponent } from '../../../shared/testing/utils.test';
|
||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { OpenaireBrokerEventObject } from '../../../core/openaire/broker/models/openaire-broker-event.model';
|
||||
import { OpenaireBrokerEventData } from '../project-entry-import-modal/project-entry-import-modal.component';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import {
|
||||
createNoContentRemoteDataObject$,
|
||||
createSuccessfulRemoteDataObject,
|
||||
createSuccessfulRemoteDataObject$
|
||||
} from '../../../shared/remote-data.utils';
|
||||
import { FindListOptions } from '../../../core/data/request.models';
|
||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
|
||||
describe('OpenaireBrokerEventsComponent test suite', () => {
|
||||
let fixture: ComponentFixture<OpenaireBrokerEventsComponent>;
|
||||
let comp: OpenaireBrokerEventsComponent;
|
||||
let compAsAny: any;
|
||||
let scheduler: TestScheduler;
|
||||
|
||||
const modalStub = {
|
||||
open: () => ( {result: new Promise((res, rej) => 'do')} ),
|
||||
close: () => null,
|
||||
dismiss: () => null
|
||||
};
|
||||
const openaireBrokerEventRestServiceStub: any = getMockOpenaireBrokerEventRestService();
|
||||
const activatedRouteParams = {
|
||||
openaireBrokerEventsParams: {
|
||||
currentPage: 0,
|
||||
pageSize: 10
|
||||
}
|
||||
};
|
||||
const activatedRouteParamsMap = {
|
||||
id: 'ENRICH!MISSING!PROJECT'
|
||||
};
|
||||
|
||||
const events: OpenaireBrokerEventObject[] = [
|
||||
openaireBrokerEventObjectMissingProjectFound,
|
||||
openaireBrokerEventObjectMissingProjectNotFound
|
||||
];
|
||||
const paginationService = new PaginationServiceStub();
|
||||
|
||||
function getOpenAireBrokerEventData1(): OpenaireBrokerEventData {
|
||||
return {
|
||||
event: openaireBrokerEventObjectMissingProjectFound,
|
||||
id: openaireBrokerEventObjectMissingProjectFound.id,
|
||||
title: openaireBrokerEventObjectMissingProjectFound.title,
|
||||
hasProject: true,
|
||||
projectTitle: openaireBrokerEventObjectMissingProjectFound.message.title,
|
||||
projectId: ItemMockPid10.id,
|
||||
handle: ItemMockPid10.handle,
|
||||
reason: null,
|
||||
isRunning: false,
|
||||
target: ItemMockPid8
|
||||
};
|
||||
}
|
||||
|
||||
function getOpenAireBrokerEventData2(): OpenaireBrokerEventData {
|
||||
return {
|
||||
event: openaireBrokerEventObjectMissingProjectNotFound,
|
||||
id: openaireBrokerEventObjectMissingProjectNotFound.id,
|
||||
title: openaireBrokerEventObjectMissingProjectNotFound.title,
|
||||
hasProject: false,
|
||||
projectTitle: null,
|
||||
projectId: null,
|
||||
handle: null,
|
||||
reason: null,
|
||||
isRunning: false,
|
||||
target: ItemMockPid9
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
declarations: [
|
||||
OpenaireBrokerEventsComponent,
|
||||
TestComponent,
|
||||
],
|
||||
providers: [
|
||||
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub(activatedRouteParamsMap, activatedRouteParams) },
|
||||
{ provide: OpenaireBrokerEventRestService, useValue: openaireBrokerEventRestServiceStub },
|
||||
{ provide: NgbModal, useValue: modalStub },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||
{ provide: TranslateService, useValue: getMockTranslateService() },
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
OpenaireBrokerEventsComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents().then();
|
||||
scheduler = getTestScheduler();
|
||||
}));
|
||||
|
||||
// First test to check the correct component creation
|
||||
describe('', () => {
|
||||
let testComp: TestComponent;
|
||||
let testFixture: ComponentFixture<TestComponent>;
|
||||
|
||||
// synchronous beforeEach
|
||||
beforeEach(() => {
|
||||
const html = `
|
||||
<ds-openaire-broker-event></ds-openaire-broker-event>`;
|
||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||
testComp = testFixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testFixture.destroy();
|
||||
});
|
||||
|
||||
it('should create OpenaireBrokerEventsComponent', inject([OpenaireBrokerEventsComponent], (app: OpenaireBrokerEventsComponent) => {
|
||||
expect(app).toBeDefined();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Main tests', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(OpenaireBrokerEventsComponent);
|
||||
comp = fixture.componentInstance;
|
||||
compAsAny = comp;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
comp = null;
|
||||
compAsAny = null;
|
||||
});
|
||||
|
||||
describe('setEventUpdated', () => {
|
||||
it('should update events', () => {
|
||||
const expected = [
|
||||
getOpenAireBrokerEventData1(),
|
||||
getOpenAireBrokerEventData2()
|
||||
];
|
||||
scheduler.schedule(() => {
|
||||
compAsAny.setEventUpdated(events);
|
||||
});
|
||||
scheduler.flush();
|
||||
|
||||
expect(comp.eventsUpdated$.value).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('modalChoice', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(comp, 'executeAction');
|
||||
spyOn(comp, 'openModal');
|
||||
});
|
||||
|
||||
it('should call executeAction if a project is present', () => {
|
||||
const action = 'ACCEPTED';
|
||||
comp.modalChoice(action, getOpenAireBrokerEventData1(), modalStub);
|
||||
expect(comp.executeAction).toHaveBeenCalledWith(action, getOpenAireBrokerEventData1());
|
||||
});
|
||||
|
||||
it('should call openModal if a project is not present', () => {
|
||||
const action = 'ACCEPTED';
|
||||
comp.modalChoice(action, getOpenAireBrokerEventData2(), modalStub);
|
||||
expect(comp.openModal).toHaveBeenCalledWith(action, getOpenAireBrokerEventData2(), modalStub);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openModal', () => {
|
||||
it('should call modalService.open', () => {
|
||||
const action = 'ACCEPTED';
|
||||
comp.selectedReason = null;
|
||||
spyOn(compAsAny.modalService, 'open').and.returnValue({ result: new Promise((res, rej) => 'do' ) });
|
||||
spyOn(comp, 'executeAction');
|
||||
|
||||
comp.openModal(action, getOpenAireBrokerEventData1(), modalStub);
|
||||
expect(compAsAny.modalService.open).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('openModalLookup', () => {
|
||||
it('should call modalService.open', () => {
|
||||
spyOn(comp, 'boundProject');
|
||||
spyOn(compAsAny.modalService, 'open').and.returnValue(
|
||||
{
|
||||
componentInstance: {
|
||||
externalSourceEntry: null,
|
||||
label: null,
|
||||
importedObject: observableOf({
|
||||
indexableObject: OpenaireMockDspaceObject
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
scheduler.schedule(() => {
|
||||
comp.openModalLookup(getOpenAireBrokerEventData1());
|
||||
});
|
||||
scheduler.flush();
|
||||
|
||||
expect(compAsAny.modalService.open).toHaveBeenCalled();
|
||||
expect(compAsAny.boundProject).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('executeAction', () => {
|
||||
it('should call getOpenaireBrokerEvents on 200 response from REST', () => {
|
||||
const action = 'ACCEPTED';
|
||||
spyOn(compAsAny, 'getOpenaireBrokerEvents');
|
||||
openaireBrokerEventRestServiceStub.patchEvent.and.returnValue(createSuccessfulRemoteDataObject$({}));
|
||||
|
||||
scheduler.schedule(() => {
|
||||
comp.executeAction(action, getOpenAireBrokerEventData1());
|
||||
});
|
||||
scheduler.flush();
|
||||
|
||||
expect(compAsAny.getOpenaireBrokerEvents).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('boundProject', () => {
|
||||
it('should populate the project data inside "eventData"', () => {
|
||||
const eventData = getOpenAireBrokerEventData2();
|
||||
const projectId = 'UUID-23943-34u43-38344';
|
||||
const projectName = 'Test Project';
|
||||
const projectHandle = '1000/1000';
|
||||
openaireBrokerEventRestServiceStub.boundProject.and.returnValue(createSuccessfulRemoteDataObject$({}));
|
||||
|
||||
scheduler.schedule(() => {
|
||||
comp.boundProject(eventData, projectId, projectName, projectHandle);
|
||||
});
|
||||
scheduler.flush();
|
||||
|
||||
expect(eventData.hasProject).toEqual(true);
|
||||
expect(eventData.projectId).toEqual(projectId);
|
||||
expect(eventData.projectTitle).toEqual(projectName);
|
||||
expect(eventData.handle).toEqual(projectHandle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeProject', () => {
|
||||
it('should remove the project data inside "eventData"', () => {
|
||||
const eventData = getOpenAireBrokerEventData1();
|
||||
openaireBrokerEventRestServiceStub.removeProject.and.returnValue(createNoContentRemoteDataObject$());
|
||||
|
||||
scheduler.schedule(() => {
|
||||
comp.removeProject(eventData);
|
||||
});
|
||||
scheduler.flush();
|
||||
|
||||
expect(eventData.hasProject).toEqual(false);
|
||||
expect(eventData.projectId).toBeNull();
|
||||
expect(eventData.projectTitle).toBeNull();
|
||||
expect(eventData.handle).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOpenaireBrokerEvents', () => {
|
||||
it('should call the "openaireBrokerEventRestService.getEventsByTopic" to take data and "setEventUpdated" to populate eventData', () => {
|
||||
comp.paginationConfig = new PaginationComponentOptions();
|
||||
comp.paginationConfig.currentPage = 1;
|
||||
comp.paginationConfig.pageSize = 20;
|
||||
comp.paginationSortConfig = new SortOptions('trust', SortDirection.DESC);
|
||||
comp.topic = activatedRouteParamsMap.id;
|
||||
const options: FindListOptions = Object.assign(new FindListOptions(), {
|
||||
currentPage: comp.paginationConfig.currentPage,
|
||||
elementsPerPage: comp.paginationConfig.pageSize
|
||||
});
|
||||
|
||||
const pageInfo = new PageInfo({
|
||||
elementsPerPage: comp.paginationConfig.pageSize,
|
||||
totalElements: 0,
|
||||
totalPages: 1,
|
||||
currentPage: comp.paginationConfig.currentPage
|
||||
});
|
||||
const array = [
|
||||
openaireBrokerEventObjectMissingProjectFound,
|
||||
openaireBrokerEventObjectMissingProjectNotFound,
|
||||
];
|
||||
const paginatedList = buildPaginatedList(pageInfo, array);
|
||||
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||
openaireBrokerEventRestServiceStub.getEventsByTopic.and.returnValue(observableOf(paginatedListRD));
|
||||
spyOn(compAsAny, 'setEventUpdated');
|
||||
|
||||
scheduler.schedule(() => {
|
||||
compAsAny.getOpenaireBrokerEvents();
|
||||
});
|
||||
scheduler.flush();
|
||||
|
||||
expect(compAsAny.openaireBrokerEventRestService.getEventsByTopic).toHaveBeenCalledWith(
|
||||
activatedRouteParamsMap.id,
|
||||
options,
|
||||
followLink('target'),followLink('related')
|
||||
);
|
||||
expect(compAsAny.setEventUpdated).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// declare a test component
|
||||
@Component({
|
||||
selector: 'ds-test-cmp',
|
||||
template: ``
|
||||
})
|
||||
class TestComponent {
|
||||
|
||||
}
|
@@ -0,0 +1,464 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, from, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||
import { distinctUntilChanged, map, mergeMap, scan, switchMap, take } from 'rxjs/operators';
|
||||
|
||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { FindListOptions } from '../../../core/data/request.models';
|
||||
import {
|
||||
OpenaireBrokerEventMessageObject,
|
||||
OpenaireBrokerEventObject
|
||||
} from '../../../core/openaire/broker/models/openaire-broker-event.model';
|
||||
import { OpenaireBrokerEventRestService } from '../../../core/openaire/broker/events/openaire-broker-event-rest.service';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { Metadata } from '../../../core/shared/metadata.utils';
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { ItemSearchResult } from '../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import {
|
||||
OpenaireBrokerEventData,
|
||||
ProjectEntryImportModalComponent
|
||||
} from '../project-entry-import-modal/project-entry-import-modal.component';
|
||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
import { combineLatest } from 'rxjs/internal/observable/combineLatest';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
|
||||
/**
|
||||
* Component to display the OpenAIRE Broker event list.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-openaire-broker-events',
|
||||
templateUrl: './openaire-broker-events.component.html',
|
||||
styleUrls: ['./openaire-broker-events.scomponent.scss'],
|
||||
})
|
||||
export class OpenaireBrokerEventsComponent implements OnInit {
|
||||
/**
|
||||
* The pagination system configuration for HTML listing.
|
||||
* @type {PaginationComponentOptions}
|
||||
*/
|
||||
public paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'bep',
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
pageSizeOptions: [5, 10, 20, 40, 60]
|
||||
});
|
||||
/**
|
||||
* The OpenAIRE Broker event list sort options.
|
||||
* @type {SortOptions}
|
||||
*/
|
||||
public paginationSortConfig: SortOptions = new SortOptions('trust', SortDirection.DESC);
|
||||
/**
|
||||
* Array to save the presence of a project inside an OpenAIRE Broker event.
|
||||
* @type {OpenaireBrokerEventData[]>}
|
||||
*/
|
||||
public eventsUpdated$: BehaviorSubject<OpenaireBrokerEventData[]> = new BehaviorSubject([]);
|
||||
/**
|
||||
* The total number of OpenAIRE Broker events.
|
||||
* @type {Observable<number>}
|
||||
*/
|
||||
public totalElements$: Observable<number>;
|
||||
/**
|
||||
* The topic of the OpenAIRE Broker events; suitable for displaying.
|
||||
* @type {string}
|
||||
*/
|
||||
public showTopic: string;
|
||||
/**
|
||||
* The topic of the OpenAIRE Broker events; suitable for HTTP calls.
|
||||
* @type {string}
|
||||
*/
|
||||
public topic: string;
|
||||
/**
|
||||
* The rejected/ignore reason.
|
||||
* @type {string}
|
||||
*/
|
||||
public selectedReason: string;
|
||||
/**
|
||||
* Contains the information about the loading status of the page.
|
||||
* @type {Observable<boolean>}
|
||||
*/
|
||||
public isEventPageLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
/**
|
||||
* Contains the information about the loading status of the events inside the pagination component.
|
||||
* @type {Observable<boolean>}
|
||||
*/
|
||||
public isEventLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
/**
|
||||
* The modal reference.
|
||||
* @type {any}
|
||||
*/
|
||||
public modalRef: any;
|
||||
/**
|
||||
* Used to store the status of the 'Show more' button of the abstracts.
|
||||
* @type {boolean}
|
||||
*/
|
||||
public showMore = false;
|
||||
/**
|
||||
* The FindListOptions object
|
||||
*/
|
||||
protected defaultConfig: FindListOptions = Object.assign(new FindListOptions(), {sort: this.paginationSortConfig});
|
||||
/**
|
||||
* Array to track all the component subscriptions. Useful to unsubscribe them with 'onDestroy'.
|
||||
* @type {Array}
|
||||
*/
|
||||
protected subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* Initialize the component variables.
|
||||
* @param {ActivatedRoute} activatedRoute
|
||||
* @param {NgbModal} modalService
|
||||
* @param {NotificationsService} notificationsService
|
||||
* @param {OpenaireBrokerEventRestService} openaireBrokerEventRestService
|
||||
* @param {PaginationService} paginationService
|
||||
* @param {TranslateService} translateService
|
||||
*/
|
||||
constructor(
|
||||
private activatedRoute: ActivatedRoute,
|
||||
private modalService: NgbModal,
|
||||
private notificationsService: NotificationsService,
|
||||
private openaireBrokerEventRestService: OpenaireBrokerEventRestService,
|
||||
private paginationService: PaginationService,
|
||||
private translateService: TranslateService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Component initialization.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.isEventPageLoading.next(true);
|
||||
|
||||
this.activatedRoute.paramMap.pipe(
|
||||
map((params) => params.get('id')),
|
||||
take(1)
|
||||
).subscribe((id: string) => {
|
||||
const regEx = /!/g;
|
||||
this.showTopic = id.replace(regEx, '/');
|
||||
this.topic = id;
|
||||
this.isEventPageLoading.next(false);
|
||||
this.getOpenaireBrokerEvents();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if table have a detail column
|
||||
*/
|
||||
public hasDetailColumn(): boolean {
|
||||
return (this.showTopic.indexOf('/PROJECT') !== -1 ||
|
||||
this.showTopic.indexOf('/PID') !== -1 ||
|
||||
this.showTopic.indexOf('/SUBJECT') !== -1 ||
|
||||
this.showTopic.indexOf('/ABSTRACT') !== -1
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a modal or run the executeAction directly based on the presence of the project.
|
||||
*
|
||||
* @param {string} action
|
||||
* the action (can be: ACCEPTED, REJECTED, DISCARDED, PENDING)
|
||||
* @param {OpenaireBrokerEventData} eventData
|
||||
* the OpenAIRE Broker event data
|
||||
* @param {any} content
|
||||
* Reference to the modal
|
||||
*/
|
||||
public modalChoice(action: string, eventData: OpenaireBrokerEventData, content: any): void {
|
||||
if (eventData.hasProject) {
|
||||
this.executeAction(action, eventData);
|
||||
} else {
|
||||
this.openModal(action, eventData, content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the selected modal and performs the action if needed.
|
||||
*
|
||||
* @param {string} action
|
||||
* the action (can be: ACCEPTED, REJECTED, DISCARDED, PENDING)
|
||||
* @param {OpenaireBrokerEventData} eventData
|
||||
* the OpenAIRE Broker event data
|
||||
* @param {any} content
|
||||
* Reference to the modal
|
||||
*/
|
||||
public openModal(action: string, eventData: OpenaireBrokerEventData, content: any): void {
|
||||
this.modalService.open(content, { ariaLabelledBy: 'modal-basic-title' }).result.then(
|
||||
(result) => {
|
||||
if (result === 'do') {
|
||||
eventData.reason = this.selectedReason;
|
||||
this.executeAction(action, eventData);
|
||||
}
|
||||
this.selectedReason = null;
|
||||
},
|
||||
(_reason) => {
|
||||
this.selectedReason = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a modal where the user can select the project.
|
||||
*
|
||||
* @param {OpenaireBrokerEventData} eventData
|
||||
* the OpenAIRE Broker event item data
|
||||
*/
|
||||
public openModalLookup(eventData: OpenaireBrokerEventData): void {
|
||||
this.modalRef = this.modalService.open(ProjectEntryImportModalComponent, {
|
||||
size: 'lg'
|
||||
});
|
||||
const modalComp = this.modalRef.componentInstance;
|
||||
modalComp.externalSourceEntry = eventData;
|
||||
modalComp.label = 'project';
|
||||
this.subs.push(
|
||||
modalComp.importedObject.pipe(take(1))
|
||||
.subscribe((object: ItemSearchResult) => {
|
||||
const projectTitle = Metadata.first(object.indexableObject.metadata, 'dc.title');
|
||||
this.boundProject(
|
||||
eventData,
|
||||
object.indexableObject.id,
|
||||
projectTitle.value,
|
||||
object.indexableObject.handle
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the choosen action calling the REST service.
|
||||
*
|
||||
* @param {string} action
|
||||
* the action (can be: ACCEPTED, REJECTED, DISCARDED, PENDING)
|
||||
* @param {OpenaireBrokerEventData} eventData
|
||||
* the OpenAIRE Broker event data
|
||||
*/
|
||||
public executeAction(action: string, eventData: OpenaireBrokerEventData): void {
|
||||
eventData.isRunning = true;
|
||||
this.subs.push(
|
||||
this.openaireBrokerEventRestService.patchEvent(action, eventData.event, eventData.reason).pipe(getFirstCompletedRemoteData())
|
||||
.subscribe((rd: RemoteData<OpenaireBrokerEventObject>) => {
|
||||
if (rd.isSuccess && rd.statusCode === 200) {
|
||||
this.notificationsService.success(
|
||||
this.translateService.instant('openaire.broker.event.action.saved')
|
||||
);
|
||||
this.getOpenaireBrokerEvents();
|
||||
} else {
|
||||
this.notificationsService.error(
|
||||
this.translateService.instant('openaire.broker.event.action.error')
|
||||
);
|
||||
}
|
||||
eventData.isRunning = false;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bound a project to the publication described in the OpenAIRE Broker event calling the REST service.
|
||||
*
|
||||
* @param {OpenaireBrokerEventData} eventData
|
||||
* the OpenAIRE Broker event item data
|
||||
* @param {string} projectId
|
||||
* the project Id to bound
|
||||
* @param {string} projectTitle
|
||||
* the project title
|
||||
* @param {string} projectHandle
|
||||
* the project handle
|
||||
*/
|
||||
public boundProject(eventData: OpenaireBrokerEventData, projectId: string, projectTitle: string, projectHandle: string): void {
|
||||
eventData.isRunning = true;
|
||||
this.subs.push(
|
||||
this.openaireBrokerEventRestService.boundProject(eventData.id, projectId).pipe(getFirstCompletedRemoteData())
|
||||
.subscribe((rd: RemoteData<OpenaireBrokerEventObject>) => {
|
||||
if (rd.isSuccess) {
|
||||
this.notificationsService.success(
|
||||
this.translateService.instant('openaire.broker.event.project.bounded')
|
||||
);
|
||||
eventData.hasProject = true;
|
||||
eventData.projectTitle = projectTitle;
|
||||
eventData.handle = projectHandle;
|
||||
eventData.projectId = projectId;
|
||||
} else {
|
||||
this.notificationsService.error(
|
||||
this.translateService.instant('openaire.broker.event.project.error')
|
||||
);
|
||||
}
|
||||
eventData.isRunning = false;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the bounded project from the publication described in the OpenAIRE Broker event calling the REST service.
|
||||
*
|
||||
* @param {OpenaireBrokerEventData} eventData
|
||||
* the OpenAIRE Broker event data
|
||||
*/
|
||||
public removeProject(eventData: OpenaireBrokerEventData): void {
|
||||
eventData.isRunning = true;
|
||||
this.subs.push(
|
||||
this.openaireBrokerEventRestService.removeProject(eventData.id).pipe(getFirstCompletedRemoteData())
|
||||
.subscribe((rd: RemoteData<OpenaireBrokerEventObject>) => {
|
||||
if (rd.isSuccess) {
|
||||
this.notificationsService.success(
|
||||
this.translateService.instant('openaire.broker.event.project.removed')
|
||||
);
|
||||
eventData.hasProject = false;
|
||||
eventData.projectTitle = null;
|
||||
eventData.handle = null;
|
||||
eventData.projectId = null;
|
||||
} else {
|
||||
this.notificationsService.error(
|
||||
this.translateService.instant('openaire.broker.event.project.error')
|
||||
);
|
||||
}
|
||||
eventData.isRunning = false;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event has a valid href.
|
||||
* @param event
|
||||
*/
|
||||
public hasPIDHref(event: OpenaireBrokerEventMessageObject): boolean {
|
||||
return this.getPIDHref(event) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event pid href.
|
||||
* @param event
|
||||
*/
|
||||
public getPIDHref(event: OpenaireBrokerEventMessageObject): string {
|
||||
return this.computePIDHref(event);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dispatch the OpenAIRE Broker events retrival.
|
||||
*/
|
||||
public getOpenaireBrokerEvents(): void {
|
||||
this.paginationService.getFindListOptions(this.paginationConfig.id, this.defaultConfig).pipe(
|
||||
distinctUntilChanged(),
|
||||
switchMap((options: FindListOptions) => this.openaireBrokerEventRestService.getEventsByTopic(
|
||||
this.topic,
|
||||
options,
|
||||
followLink('target'), followLink('related')
|
||||
)),
|
||||
getFirstCompletedRemoteData(),
|
||||
).subscribe((rd: RemoteData<PaginatedList<OpenaireBrokerEventObject>>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
this.isEventLoading.next(false);
|
||||
this.totalElements$ = observableOf(rd.payload.totalElements);
|
||||
this.setEventUpdated(rd.payload.page);
|
||||
} else {
|
||||
throw new Error('Can\'t retrieve OpenAIRE Broker events from the Broker events REST service');
|
||||
}
|
||||
this.openaireBrokerEventRestService.clearFindByTopicRequests();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from all subscriptions.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the project status for the OpenAIRE Broker events.
|
||||
*
|
||||
* @param {OpenaireBrokerEventObject[]} events
|
||||
* the OpenAIRE Broker event item
|
||||
*/
|
||||
protected setEventUpdated(events: OpenaireBrokerEventObject[]): void {
|
||||
this.subs.push(
|
||||
from(events).pipe(
|
||||
mergeMap((event: OpenaireBrokerEventObject) => {
|
||||
const related$ = event.related.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
const target$ = event.target.pipe(
|
||||
getFirstCompletedRemoteData()
|
||||
);
|
||||
return combineLatest([related$, target$]).pipe(
|
||||
map(([relatedItemRD, targetItemRD]: [RemoteData<Item>, RemoteData<Item>]) => {
|
||||
const data: OpenaireBrokerEventData = {
|
||||
event: event,
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
hasProject: false,
|
||||
projectTitle: null,
|
||||
projectId: null,
|
||||
handle: null,
|
||||
reason: null,
|
||||
isRunning: false,
|
||||
target: (targetItemRD?.hasSucceeded) ? targetItemRD.payload : null,
|
||||
};
|
||||
if (relatedItemRD?.hasSucceeded && relatedItemRD?.payload?.id) {
|
||||
data.hasProject = true;
|
||||
data.projectTitle = event.message.title;
|
||||
data.projectId = relatedItemRD?.payload?.id;
|
||||
data.handle = relatedItemRD?.payload?.handle;
|
||||
}
|
||||
return data;
|
||||
})
|
||||
);
|
||||
}),
|
||||
scan((acc: any, value: any) => [...acc, value], []),
|
||||
take(events.length)
|
||||
).subscribe(
|
||||
(eventsReduced) => {
|
||||
this.eventsUpdated$.next(eventsReduced);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
protected computePIDHref(event: OpenaireBrokerEventMessageObject) {
|
||||
const type = event.type.toLowerCase();
|
||||
const pid = event.value;
|
||||
let prefix = null;
|
||||
switch (type) {
|
||||
case 'arxiv': {
|
||||
prefix = 'https://arxiv.org/abs/';
|
||||
break;
|
||||
}
|
||||
case 'handle': {
|
||||
prefix = 'https://hdl.handle.net/';
|
||||
break;
|
||||
}
|
||||
case 'urn': {
|
||||
prefix = '';
|
||||
break;
|
||||
}
|
||||
case 'doi': {
|
||||
prefix = 'https://doi.org/';
|
||||
break;
|
||||
}
|
||||
case 'pmc': {
|
||||
prefix = 'https://www.ncbi.nlm.nih.gov/pmc/articles/';
|
||||
break;
|
||||
}
|
||||
case 'pmid': {
|
||||
prefix = 'https://pubmed.ncbi.nlm.nih.gov/';
|
||||
break;
|
||||
}
|
||||
case 'ncid': {
|
||||
prefix = 'https://ci.nii.ac.jp/ncid/';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prefix === null) {
|
||||
return null;
|
||||
}
|
||||
return prefix + pid;
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
.button-rows {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.button-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.abstract-container {
|
||||
height: 76px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.text-ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.show {
|
||||
overflow: visible;
|
||||
height: auto;
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title" id="modal-title">{{ (labelPrefix + label + '.title') | translate }}</h4>
|
||||
<button type="button" class="close" aria-label="Close button" aria-describedby="modal-title"
|
||||
(click)="close()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<small>{{ (labelPrefix + label + '.publication' | translate) }}</small>
|
||||
<div class="mb-3">
|
||||
<div class="text-truncate">
|
||||
<a target="_blank" href="/items/{{(externalSourceEntry.event.target|async)?.payload?.id}}">
|
||||
{{externalSourceEntry.title}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="externalSourceEntry.projectTitle">
|
||||
<small>{{ (labelPrefix + label + '.bountToLocal' |translate) }}</small>
|
||||
<div class="mb-3">
|
||||
<div class="text-truncate">
|
||||
<a target="_blank" href="/items/{{externalSourceEntry.projectId}}">
|
||||
{{externalSourceEntry.projectTitle}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4>{{ (labelPrefix + label + '.select' | translate) }}</h4>
|
||||
|
||||
<div id="project-entities" class="mb-3">
|
||||
|
||||
<div id="project-search" class="input-group mb-3">
|
||||
<input type="text" class="form-control" (keyup.enter)="search(projectTitle)" [(ngModel)]="projectTitle" placeholder="{{labelPrefix + label + '.placeholder' |translate}}" aria-label="" aria-describedby="">
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-outline-secondary" [disabled]="projectTitle === ''" (click)="projectTitle = ''">{{(labelPrefix + label + '.clear'|translate)}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="projectTitle === ''" (click)="search(projectTitle)">{{(labelPrefix + label + '.search'|translate)}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ds-loading *ngIf="(isLoading$ | async)" message="{{'loading.search-results' | translate}}"></ds-loading>
|
||||
<ds-search-results *ngIf="(localEntitiesRD$ | async)?.payload?.page?.length > 0 && !(isLoading$ | async)"
|
||||
[searchResults]="(localEntitiesRD$ | async)"
|
||||
[sortConfig]="this.searchOptions?.sort"
|
||||
[searchConfig]="this.searchOptions"
|
||||
[selectable]="true"
|
||||
[disableHeader]="true"
|
||||
[hidePaginationDetail]="false"
|
||||
[selectionConfig]="{ repeatable: false, listId: entityListId }"
|
||||
[linkType]="linkTypes.ExternalLink"
|
||||
[context]="context"
|
||||
(deselectObject)="deselectEntity()"
|
||||
(selectObject)="selectEntity($event)">
|
||||
</ds-search-results>
|
||||
|
||||
<div *ngIf="(localEntitiesRD$ | async)?.payload?.page?.length < 1 && !(isLoading$ | async)">
|
||||
<ds-alert [type]="'alert-info'">
|
||||
<p class="lead mb-0">{{(labelPrefix + label + '.notFound' | translate)}}</p>
|
||||
</ds-alert>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div>
|
||||
<button type="button" class="btn btn-outline-secondary" (click)="close()">{{ (labelPrefix + label + '.cancel' | translate) }}</button>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" [disabled]="selectedImportType === importType.None" (click)="bound()">{{ (labelPrefix + label + '.bound' | translate) }}</button>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,3 @@
|
||||
.modal-footer {
|
||||
justify-content: space-between;
|
||||
}
|
@@ -0,0 +1,210 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { createTestComponent } from '../../../shared/testing/utils.test';
|
||||
import { ImportType, ProjectEntryImportModalComponent } from './project-entry-import-modal.component';
|
||||
import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { getMockSearchService } from '../../../shared/mocks/search-service.mock';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
import {
|
||||
ItemMockPid10,
|
||||
openaireBrokerEventObjectMissingProjectFound,
|
||||
OpenaireMockDspaceObject
|
||||
} from '../../../shared/mocks/openaire.mock';
|
||||
|
||||
const eventData = {
|
||||
event: openaireBrokerEventObjectMissingProjectFound,
|
||||
id: openaireBrokerEventObjectMissingProjectFound.id,
|
||||
title: openaireBrokerEventObjectMissingProjectFound.title,
|
||||
hasProject: true,
|
||||
projectTitle: openaireBrokerEventObjectMissingProjectFound.message.title,
|
||||
projectId: ItemMockPid10.id,
|
||||
handle: ItemMockPid10.handle,
|
||||
reason: null,
|
||||
isRunning: false
|
||||
};
|
||||
|
||||
const searchString = 'Test project to search';
|
||||
const pagination = Object.assign(
|
||||
new PaginationComponentOptions(), {
|
||||
id: 'openaire-project-bound',
|
||||
pageSize: 3
|
||||
}
|
||||
);
|
||||
const searchOptions = Object.assign(new PaginatedSearchOptions(
|
||||
{
|
||||
configuration: 'funding',
|
||||
query: searchString,
|
||||
pagination: pagination
|
||||
}
|
||||
));
|
||||
const pageInfo = new PageInfo({
|
||||
elementsPerPage: 3,
|
||||
totalElements: 1,
|
||||
totalPages: 1,
|
||||
currentPage: 1
|
||||
});
|
||||
const array = [
|
||||
OpenaireMockDspaceObject,
|
||||
];
|
||||
const paginatedList = buildPaginatedList(pageInfo, array);
|
||||
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||
|
||||
describe('ProjectEntryImportModalComponent test suite', () => {
|
||||
let fixture: ComponentFixture<ProjectEntryImportModalComponent>;
|
||||
let comp: ProjectEntryImportModalComponent;
|
||||
let compAsAny: any;
|
||||
|
||||
const modalStub = jasmine.createSpyObj('modal', ['close', 'dismiss']);
|
||||
const uuid = '123e4567-e89b-12d3-a456-426614174003';
|
||||
const searchServiceStub: any = getMockSearchService();
|
||||
|
||||
|
||||
beforeEach(async (() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
declarations: [
|
||||
ProjectEntryImportModalComponent,
|
||||
TestComponent,
|
||||
],
|
||||
providers: [
|
||||
{ provide: NgbActiveModal, useValue: modalStub },
|
||||
{ provide: SearchService, useValue: searchServiceStub },
|
||||
{ provide: SelectableListService, useValue: jasmine.createSpyObj('selectableListService', ['deselect', 'select', 'deselectAll']) },
|
||||
ProjectEntryImportModalComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents().then();
|
||||
}));
|
||||
|
||||
// First test to check the correct component creation
|
||||
describe('', () => {
|
||||
let testComp: TestComponent;
|
||||
let testFixture: ComponentFixture<TestComponent>;
|
||||
|
||||
// synchronous beforeEach
|
||||
beforeEach(() => {
|
||||
searchServiceStub.search.and.returnValue(observableOf(paginatedListRD));
|
||||
const html = `
|
||||
<ds-project-entry-import-modal [externalSourceEntry]="eventData"></ds-project-entry-import-modal>`;
|
||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||
testComp = testFixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testFixture.destroy();
|
||||
});
|
||||
|
||||
it('should create ProjectEntryImportModalComponent', inject([ProjectEntryImportModalComponent], (app: ProjectEntryImportModalComponent) => {
|
||||
expect(app).toBeDefined();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Main tests', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProjectEntryImportModalComponent);
|
||||
comp = fixture.componentInstance;
|
||||
compAsAny = comp;
|
||||
|
||||
});
|
||||
|
||||
describe('close', () => {
|
||||
it('should close the modal', () => {
|
||||
comp.close();
|
||||
expect(modalStub.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('search', () => {
|
||||
it('should call SearchService.search', () => {
|
||||
|
||||
(searchServiceStub as any).search.and.returnValue(observableOf(paginatedListRD));
|
||||
comp.pagination = pagination;
|
||||
|
||||
comp.search(searchString);
|
||||
expect(comp.searchService.search).toHaveBeenCalledWith(searchOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bound', () => {
|
||||
it('should call close, deselectAllLists and importedObject.emit', () => {
|
||||
spyOn(comp, 'deselectAllLists');
|
||||
spyOn(comp, 'close');
|
||||
spyOn(comp.importedObject, 'emit');
|
||||
comp.selectedEntity = OpenaireMockDspaceObject;
|
||||
comp.bound();
|
||||
|
||||
expect(comp.importedObject.emit).toHaveBeenCalled();
|
||||
expect(comp.deselectAllLists).toHaveBeenCalled();
|
||||
expect(comp.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectEntity', () => {
|
||||
const entity = Object.assign(new Item(), { uuid: uuid });
|
||||
beforeEach(() => {
|
||||
comp.selectEntity(entity);
|
||||
});
|
||||
|
||||
it('should set selected entity', () => {
|
||||
expect(comp.selectedEntity).toBe(entity);
|
||||
});
|
||||
|
||||
it('should set the import type to local entity', () => {
|
||||
expect(comp.selectedImportType).toEqual(ImportType.LocalEntity);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deselectEntity', () => {
|
||||
const entity = Object.assign(new Item(), { uuid: uuid });
|
||||
beforeEach(() => {
|
||||
comp.selectedImportType = ImportType.LocalEntity;
|
||||
comp.selectedEntity = entity;
|
||||
comp.deselectEntity();
|
||||
});
|
||||
|
||||
it('should remove the selected entity', () => {
|
||||
expect(comp.selectedEntity).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should set the import type to none', () => {
|
||||
expect(comp.selectedImportType).toEqual(ImportType.None);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deselectAllLists', () => {
|
||||
it('should call SelectableListService.deselectAll', () => {
|
||||
comp.deselectAllLists();
|
||||
expect(compAsAny.selectService.deselectAll).toHaveBeenCalledWith(comp.entityListId);
|
||||
expect(compAsAny.selectService.deselectAll).toHaveBeenCalledWith(comp.authorityListId);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
comp = null;
|
||||
compAsAny = null;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// declare a test component
|
||||
@Component({
|
||||
selector: 'ds-test-cmp',
|
||||
template: ``
|
||||
})
|
||||
class TestComponent {
|
||||
eventData = eventData;
|
||||
}
|
@@ -0,0 +1,274 @@
|
||||
import { Component, EventEmitter, Input, OnInit } from '@angular/core';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { SearchResult } from '../../../shared/search/models/search-result.model';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||
import { CollectionElementLinkType } from '../../../shared/object-collection/collection-element-link.type';
|
||||
import { Context } from '../../../core/shared/context.model';
|
||||
import { SelectableListService } from '../../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { ListableObject } from '../../../shared/object-collection/shared/listable-object.model';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { OpenaireBrokerEventObject } from '../../../core/openaire/broker/models/openaire-broker-event.model';
|
||||
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
|
||||
/**
|
||||
* The possible types of import for the external entry
|
||||
*/
|
||||
export enum ImportType {
|
||||
None = 'None',
|
||||
LocalEntity = 'LocalEntity',
|
||||
LocalAuthority = 'LocalAuthority',
|
||||
NewEntity = 'NewEntity',
|
||||
NewAuthority = 'NewAuthority'
|
||||
}
|
||||
|
||||
/**
|
||||
* The data type passed from the parent page
|
||||
*/
|
||||
export interface OpenaireBrokerEventData {
|
||||
/**
|
||||
* The OpenAIRE Broker event
|
||||
*/
|
||||
event: OpenaireBrokerEventObject;
|
||||
/**
|
||||
* The OpenAIRE Broker event Id (uuid)
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* The publication title
|
||||
*/
|
||||
title: string;
|
||||
/**
|
||||
* Contains the boolean that indicates if a project is present
|
||||
*/
|
||||
hasProject: boolean;
|
||||
/**
|
||||
* The project title, if present
|
||||
*/
|
||||
projectTitle: string;
|
||||
/**
|
||||
* The project id (uuid), if present
|
||||
*/
|
||||
projectId: string;
|
||||
/**
|
||||
* The project handle, if present
|
||||
*/
|
||||
handle: string;
|
||||
/**
|
||||
* The reject/discard reason
|
||||
*/
|
||||
reason: string;
|
||||
/**
|
||||
* Contains the boolean that indicates if there is a running operation (REST call)
|
||||
*/
|
||||
isRunning: boolean;
|
||||
/**
|
||||
* The related publication DSpace item
|
||||
*/
|
||||
target?: Item;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ds-project-entry-import-modal',
|
||||
styleUrls: ['./project-entry-import-modal.component.scss'],
|
||||
templateUrl: './project-entry-import-modal.component.html'
|
||||
})
|
||||
/**
|
||||
* Component to display a modal window for linking a project to an OpenAIRE Broker event
|
||||
* Shows information about the selected project and a selectable list.
|
||||
*/
|
||||
export class ProjectEntryImportModalComponent implements OnInit {
|
||||
/**
|
||||
* The external source entry
|
||||
*/
|
||||
@Input() externalSourceEntry: OpenaireBrokerEventData;
|
||||
/**
|
||||
* The number of results per page
|
||||
*/
|
||||
pageSize = 3;
|
||||
/**
|
||||
* The prefix for every i18n key within this modal
|
||||
*/
|
||||
labelPrefix = 'openaire.broker.event.modal.';
|
||||
/**
|
||||
* The search configuration to retrieve project
|
||||
*/
|
||||
configuration = 'funding';
|
||||
/**
|
||||
* The label to use for all messages (added to the end of relevant i18n keys)
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The project title from the parent object
|
||||
*/
|
||||
projectTitle: string;
|
||||
/**
|
||||
* The search results
|
||||
*/
|
||||
localEntitiesRD$: Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>;
|
||||
/**
|
||||
* Information about the data loading status
|
||||
*/
|
||||
isLoading$ = observableOf(true);
|
||||
/**
|
||||
* Search options to use for fetching projects
|
||||
*/
|
||||
searchOptions: PaginatedSearchOptions;
|
||||
/**
|
||||
* The context we're currently in (submission)
|
||||
*/
|
||||
context = Context.EntitySearchModalWithNameVariants;
|
||||
/**
|
||||
* List ID for selecting local entities
|
||||
*/
|
||||
entityListId = 'openaire-project-bound';
|
||||
/**
|
||||
* List ID for selecting local authorities
|
||||
*/
|
||||
authorityListId = 'openaire-project-bound-authority';
|
||||
/**
|
||||
* ImportType enum
|
||||
*/
|
||||
importType = ImportType;
|
||||
/**
|
||||
* The type of link to render in listable elements
|
||||
*/
|
||||
linkTypes = CollectionElementLinkType;
|
||||
/**
|
||||
* The type of import the user currently has selected
|
||||
*/
|
||||
selectedImportType = ImportType.None;
|
||||
/**
|
||||
* The selected local entity
|
||||
*/
|
||||
selectedEntity: ListableObject;
|
||||
/**
|
||||
* An project has been selected, send it to the parent component
|
||||
*/
|
||||
importedObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||
/**
|
||||
* Pagination options
|
||||
*/
|
||||
pagination: PaginationComponentOptions;
|
||||
/**
|
||||
* Array to track all the component subscriptions. Useful to unsubscribe them with 'onDestroy'.
|
||||
* @type {Array}
|
||||
*/
|
||||
protected subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* Initialize the component variables.
|
||||
* @param {NgbActiveModal} modal
|
||||
* @param {SearchService} searchService
|
||||
* @param {SelectableListService} selectService
|
||||
*/
|
||||
constructor(public modal: NgbActiveModal,
|
||||
public searchService: SearchService,
|
||||
private selectService: SelectableListService) { }
|
||||
|
||||
/**
|
||||
* Component intitialization.
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
this.pagination = Object.assign(new PaginationComponentOptions(), { id: 'openaire-project-bound', pageSize: this.pageSize });
|
||||
this.projectTitle = (this.externalSourceEntry.projectTitle !== null) ? this.externalSourceEntry.projectTitle : this.externalSourceEntry.event.message.title;
|
||||
this.searchOptions = Object.assign(new PaginatedSearchOptions(
|
||||
{
|
||||
configuration: this.configuration,
|
||||
query: this.projectTitle,
|
||||
pagination: this.pagination
|
||||
}
|
||||
));
|
||||
this.localEntitiesRD$ = this.searchService.search(this.searchOptions);
|
||||
this.subs.push(
|
||||
this.localEntitiesRD$.subscribe(
|
||||
() => this.isLoading$ = observableOf(false)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the modal.
|
||||
*/
|
||||
public close(): void {
|
||||
this.deselectAllLists();
|
||||
this.modal.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a project search by title.
|
||||
*/
|
||||
public search(searchTitle): void {
|
||||
if (isNotEmpty(searchTitle)) {
|
||||
const filterRegEx = /[:]/g;
|
||||
this.isLoading$ = observableOf(true);
|
||||
this.searchOptions = Object.assign(new PaginatedSearchOptions(
|
||||
{
|
||||
configuration: this.configuration,
|
||||
query: (searchTitle) ? searchTitle.replace(filterRegEx, '') : searchTitle,
|
||||
pagination: this.pagination
|
||||
}
|
||||
));
|
||||
this.localEntitiesRD$ = this.searchService.search(this.searchOptions);
|
||||
this.subs.push(
|
||||
this.localEntitiesRD$.subscribe(
|
||||
() => this.isLoading$ = observableOf(false)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the bound of the project.
|
||||
*/
|
||||
public bound(): void {
|
||||
if (this.selectedEntity !== undefined) {
|
||||
this.importedObject.emit(this.selectedEntity);
|
||||
}
|
||||
this.selectedImportType = ImportType.None;
|
||||
this.deselectAllLists();
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deselected a local entity
|
||||
*/
|
||||
public deselectEntity(): void {
|
||||
this.selectedEntity = undefined;
|
||||
if (this.selectedImportType === ImportType.LocalEntity) {
|
||||
this.selectedImportType = ImportType.None;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selected a local entity
|
||||
* @param entity
|
||||
*/
|
||||
public selectEntity(entity): void {
|
||||
this.selectedEntity = entity;
|
||||
this.selectedImportType = ImportType.LocalEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deselect every element from both entity and authority lists
|
||||
*/
|
||||
public deselectAllLists(): void {
|
||||
this.selectService.deselectAll(this.entityListId);
|
||||
this.selectService.deselectAll(this.authorityListId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from all subscriptions.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.deselectAllLists();
|
||||
this.subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { type } from '../../../shared/ngrx/type';
|
||||
import { OpenaireBrokerTopicObject } from '../../../core/openaire/broker/models/openaire-broker-topic.model';
|
||||
|
||||
/**
|
||||
* For each action type in an action group, make a simple
|
||||
* enum object for all of this group's action types.
|
||||
*
|
||||
* The 'type' utility function coerces strings into string
|
||||
* literal types and runs a simple check to guarantee all
|
||||
* action types in the application are unique.
|
||||
*/
|
||||
export const OpenaireBrokerTopicActionTypes = {
|
||||
ADD_TOPICS: type('dspace/integration/openaire/broker/topic/ADD_TOPICS'),
|
||||
RETRIEVE_ALL_TOPICS: type('dspace/integration/openaire/broker/topic/RETRIEVE_ALL_TOPICS'),
|
||||
RETRIEVE_ALL_TOPICS_ERROR: type('dspace/integration/openaire/broker/topic/RETRIEVE_ALL_TOPICS_ERROR'),
|
||||
};
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* An ngrx action to retrieve all the OpenAIRE Broker topics.
|
||||
*/
|
||||
export class RetrieveAllTopicsAction implements Action {
|
||||
type = OpenaireBrokerTopicActionTypes.RETRIEVE_ALL_TOPICS;
|
||||
payload: {
|
||||
elementsPerPage: number;
|
||||
currentPage: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new RetrieveAllTopicsAction.
|
||||
*
|
||||
* @param elementsPerPage
|
||||
* the number of topics per page
|
||||
* @param currentPage
|
||||
* The page number to retrieve
|
||||
*/
|
||||
constructor(elementsPerPage: number, currentPage: number) {
|
||||
this.payload = {
|
||||
elementsPerPage,
|
||||
currentPage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action for retrieving 'all OpenAIRE Broker topics' error.
|
||||
*/
|
||||
export class RetrieveAllTopicsErrorAction implements Action {
|
||||
type = OpenaireBrokerTopicActionTypes.RETRIEVE_ALL_TOPICS_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* An ngrx action to load the OpenAIRE Broker topic objects.
|
||||
* Called by the ??? effect.
|
||||
*/
|
||||
export class AddTopicsAction implements Action {
|
||||
type = OpenaireBrokerTopicActionTypes.ADD_TOPICS;
|
||||
payload: {
|
||||
topics: OpenaireBrokerTopicObject[];
|
||||
totalPages: number;
|
||||
currentPage: number;
|
||||
totalElements: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new AddTopicsAction.
|
||||
*
|
||||
* @param topics
|
||||
* the list of topics
|
||||
* @param totalPages
|
||||
* the total available pages of topics
|
||||
* @param currentPage
|
||||
* the current page
|
||||
* @param totalElements
|
||||
* the total available OpenAIRE Broker topics
|
||||
*/
|
||||
constructor(topics: OpenaireBrokerTopicObject[], totalPages: number, currentPage: number, totalElements: number) {
|
||||
this.payload = {
|
||||
topics,
|
||||
totalPages,
|
||||
currentPage,
|
||||
totalElements
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* Export a type alias of all actions in this action group
|
||||
* so that reducers can easily compose action types.
|
||||
*/
|
||||
export type OpenaireBrokerTopicsActions
|
||||
= AddTopicsAction
|
||||
|RetrieveAllTopicsAction
|
||||
|RetrieveAllTopicsErrorAction;
|
@@ -0,0 +1,57 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2 class="border-bottom pb-2">{{'openaire.broker.title'| translate}}</h2>
|
||||
<p>{{'openaire.broker.topics.description'| translate}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h3 class="border-bottom pb-2">{{'openaire.broker.topics'| translate}}</h3>
|
||||
|
||||
<ds-loading class="container" *ngIf="(isTopicsLoading() | async)" message="{{'openaire.broker.loading' | translate}}"></ds-loading>
|
||||
<ds-pagination *ngIf="!(isTopicsLoading() | async)"
|
||||
[paginationOptions]="paginationConfig"
|
||||
[collectionSize]="(totalElements$ | async)"
|
||||
[hideGear]="false"
|
||||
[hideSortOptions]="true"
|
||||
(paginationChange)="getOpenaireBrokerTopics()">
|
||||
|
||||
<ds-loading class="container" *ngIf="(isTopicsProcessing() | async)" message="'openaire.broker.loading' | translate"></ds-loading>
|
||||
<ng-container *ngIf="!(isTopicsProcessing() | async)">
|
||||
<div *ngIf="(topics$|async)?.length == 0" class="alert alert-info w-100 mb-2 mt-2" role="alert">
|
||||
{{'openaire.broker.noTopics' | translate}}
|
||||
</div>
|
||||
<div *ngIf="(topics$|async)?.length != 0" class="table-responsive mt-2">
|
||||
<table id="epeople" class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{'openaire.broker.table.topic' | translate}}</th>
|
||||
<th scope="col">{{'openaire.broker.table.last-event' | translate}}</th>
|
||||
<th scope="col">{{'openaire.broker.table.actions' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let topicElement of (topics$ | async); let i = index">
|
||||
<td>{{topicElement.name}}</td>
|
||||
<td>{{topicElement.lastEvent}}</td>
|
||||
<td>
|
||||
<div class="btn-group edit-field">
|
||||
<button
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
title="{{'openaire.broker.button.detail' | translate }}"
|
||||
[routerLink]="[topicElement.id]">
|
||||
<span class="badge badge-info">{{topicElement.totalEvents}}</span>
|
||||
<i class="fas fa-info fa-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ds-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,152 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { createTestComponent } from '../../../shared/testing/utils.test';
|
||||
import {
|
||||
getMockOpenaireStateService,
|
||||
openaireBrokerTopicObjectMoreAbstract,
|
||||
openaireBrokerTopicObjectMorePid
|
||||
} from '../../../shared/mocks/openaire.mock';
|
||||
import { OpenaireBrokerTopicsComponent } from './openaire-broker-topics.component';
|
||||
import { OpenaireStateService } from '../../openaire-state.service';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
|
||||
describe('OpenaireBrokerTopicsComponent test suite', () => {
|
||||
let fixture: ComponentFixture<OpenaireBrokerTopicsComponent>;
|
||||
let comp: OpenaireBrokerTopicsComponent;
|
||||
let compAsAny: any;
|
||||
const mockOpenaireStateService = getMockOpenaireStateService();
|
||||
const activatedRouteParams = {
|
||||
openaireBrokerTopicsParams: {
|
||||
currentPage: 0,
|
||||
pageSize: 5
|
||||
}
|
||||
};
|
||||
const paginationService = new PaginationServiceStub();
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule.forRoot(),
|
||||
],
|
||||
declarations: [
|
||||
OpenaireBrokerTopicsComponent,
|
||||
TestComponent,
|
||||
],
|
||||
providers: [
|
||||
{ provide: OpenaireStateService, useValue: mockOpenaireStateService },
|
||||
{ provide: ActivatedRoute, useValue: { data: observableOf(activatedRouteParams), params: observableOf({}) } },
|
||||
{ provide: PaginationService, useValue: paginationService },
|
||||
OpenaireBrokerTopicsComponent
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents().then(() => {
|
||||
mockOpenaireStateService.getOpenaireBrokerTopics.and.returnValue(observableOf([
|
||||
openaireBrokerTopicObjectMorePid,
|
||||
openaireBrokerTopicObjectMoreAbstract
|
||||
]));
|
||||
mockOpenaireStateService.getOpenaireBrokerTopicsTotalPages.and.returnValue(observableOf(1));
|
||||
mockOpenaireStateService.getOpenaireBrokerTopicsCurrentPage.and.returnValue(observableOf(0));
|
||||
mockOpenaireStateService.getOpenaireBrokerTopicsTotals.and.returnValue(observableOf(2));
|
||||
mockOpenaireStateService.isOpenaireBrokerTopicsLoaded.and.returnValue(observableOf(true));
|
||||
mockOpenaireStateService.isOpenaireBrokerTopicsLoading.and.returnValue(observableOf(false));
|
||||
mockOpenaireStateService.isOpenaireBrokerTopicsProcessing.and.returnValue(observableOf(false));
|
||||
});
|
||||
}));
|
||||
|
||||
// First test to check the correct component creation
|
||||
describe('', () => {
|
||||
let testComp: TestComponent;
|
||||
let testFixture: ComponentFixture<TestComponent>;
|
||||
|
||||
// synchronous beforeEach
|
||||
beforeEach(() => {
|
||||
const html = `
|
||||
<ds-openaire-broker-topic></ds-openaire-broker-topic>`;
|
||||
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||
testComp = testFixture.componentInstance;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testFixture.destroy();
|
||||
});
|
||||
|
||||
it('should create OpenaireBrokerTopicsComponent', inject([OpenaireBrokerTopicsComponent], (app: OpenaireBrokerTopicsComponent) => {
|
||||
expect(app).toBeDefined();
|
||||
}));
|
||||
});
|
||||
|
||||
describe('Main tests running with two topics', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(OpenaireBrokerTopicsComponent);
|
||||
comp = fixture.componentInstance;
|
||||
compAsAny = comp;
|
||||
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fixture.destroy();
|
||||
comp = null;
|
||||
compAsAny = null;
|
||||
});
|
||||
|
||||
it(('Should init component properly'), () => {
|
||||
comp.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(comp.topics$).toBeObservable(cold('(a|)', {
|
||||
a: [
|
||||
openaireBrokerTopicObjectMorePid,
|
||||
openaireBrokerTopicObjectMoreAbstract
|
||||
]
|
||||
}));
|
||||
expect(comp.totalElements$).toBeObservable(cold('(a|)', {
|
||||
a: 2
|
||||
}));
|
||||
});
|
||||
|
||||
it(('Should set data properly after the view init'), () => {
|
||||
spyOn(compAsAny, 'getOpenaireBrokerTopics');
|
||||
|
||||
comp.ngAfterViewInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(compAsAny.getOpenaireBrokerTopics).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it(('isTopicsLoading should return FALSE'), () => {
|
||||
expect(comp.isTopicsLoading()).toBeObservable(cold('(a|)', {
|
||||
a: false
|
||||
}));
|
||||
});
|
||||
|
||||
it(('isTopicsProcessing should return FALSE'), () => {
|
||||
expect(comp.isTopicsProcessing()).toBeObservable(cold('(a|)', {
|
||||
a: false
|
||||
}));
|
||||
});
|
||||
|
||||
it(('getOpenaireBrokerTopics should call the service to dispatch a STATE change'), () => {
|
||||
comp.ngOnInit();
|
||||
fixture.detectChanges();
|
||||
|
||||
compAsAny.openaireStateService.dispatchRetrieveOpenaireBrokerTopics(comp.paginationConfig.pageSize, comp.paginationConfig.currentPage).and.callThrough();
|
||||
expect(compAsAny.openaireStateService.dispatchRetrieveOpenaireBrokerTopics).toHaveBeenCalledWith(comp.paginationConfig.pageSize, comp.paginationConfig.currentPage);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// declare a test component
|
||||
@Component({
|
||||
selector: 'ds-test-cmp',
|
||||
template: ``
|
||||
})
|
||||
class TestComponent {
|
||||
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { distinctUntilChanged, take } from 'rxjs/operators';
|
||||
|
||||
import { SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
import { OpenaireBrokerTopicObject } from '../../../core/openaire/broker/models/openaire-broker-topic.model';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { OpenaireStateService } from '../../openaire-state.service';
|
||||
import { AdminNotificationsOpenaireTopicsPageParams } from '../../../admin/admin-notifications/admin-notifications-openaire-topics-page/admin-notifications-openaire-topics-page-resolver.service';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
|
||||
/**
|
||||
* Component to display the OpenAIRE Broker topic list.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-openaire-broker-topic',
|
||||
templateUrl: './openaire-broker-topics.component.html',
|
||||
styleUrls: ['./openaire-broker-topics.component.scss'],
|
||||
})
|
||||
export class OpenaireBrokerTopicsComponent implements OnInit {
|
||||
/**
|
||||
* The pagination system configuration for HTML listing.
|
||||
* @type {PaginationComponentOptions}
|
||||
*/
|
||||
public paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'btp',
|
||||
pageSize: 10,
|
||||
pageSizeOptions: [5, 10, 20, 40, 60]
|
||||
});
|
||||
/**
|
||||
* The OpenAIRE Broker topic list sort options.
|
||||
* @type {SortOptions}
|
||||
*/
|
||||
public paginationSortConfig: SortOptions;
|
||||
/**
|
||||
* The OpenAIRE Broker topic list.
|
||||
*/
|
||||
public topics$: Observable<OpenaireBrokerTopicObject[]>;
|
||||
/**
|
||||
* The total number of OpenAIRE Broker topics.
|
||||
*/
|
||||
public totalElements$: Observable<number>;
|
||||
/**
|
||||
* Array to track all the component subscriptions. Useful to unsubscribe them with 'onDestroy'.
|
||||
* @type {Array}
|
||||
*/
|
||||
protected subs: Subscription[] = [];
|
||||
|
||||
/**
|
||||
* Initialize the component variables.
|
||||
* @param {PaginationService} paginationService
|
||||
* @param {OpenaireStateService} openaireStateService
|
||||
*/
|
||||
constructor(
|
||||
private paginationService: PaginationService,
|
||||
private openaireStateService: OpenaireStateService,
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Component initialization.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.topics$ = this.openaireStateService.getOpenaireBrokerTopics();
|
||||
this.totalElements$ = this.openaireStateService.getOpenaireBrokerTopicsTotals();
|
||||
}
|
||||
|
||||
/**
|
||||
* First OpenAIRE Broker topics loading after view initialization.
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.subs.push(
|
||||
this.openaireStateService.isOpenaireBrokerTopicsLoaded().pipe(
|
||||
take(1)
|
||||
).subscribe(() => {
|
||||
this.getOpenaireBrokerTopics();
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about the loading status of the OpenAIRE Broker topics (if it's running or not).
|
||||
*
|
||||
* @return Observable<boolean>
|
||||
* 'true' if the topics are loading, 'false' otherwise.
|
||||
*/
|
||||
public isTopicsLoading(): Observable<boolean> {
|
||||
return this.openaireStateService.isOpenaireBrokerTopicsLoading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about the processing status of the OpenAIRE Broker topics (if it's running or not).
|
||||
*
|
||||
* @return Observable<boolean>
|
||||
* 'true' if there are operations running on the topics (ex.: a REST call), 'false' otherwise.
|
||||
*/
|
||||
public isTopicsProcessing(): Observable<boolean> {
|
||||
return this.openaireStateService.isOpenaireBrokerTopicsProcessing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the OpenAIRE Broker topics retrival.
|
||||
*/
|
||||
public getOpenaireBrokerTopics(): void {
|
||||
this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig).pipe(
|
||||
distinctUntilChanged(),
|
||||
).subscribe((options: PaginationComponentOptions) => {
|
||||
this.openaireStateService.dispatchRetrieveOpenaireBrokerTopics(
|
||||
options.pageSize,
|
||||
options.currentPage
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update pagination Config from route params
|
||||
*
|
||||
* @param eventsRouteParams
|
||||
*/
|
||||
protected updatePaginationFromRouteParams(eventsRouteParams: AdminNotificationsOpenaireTopicsPageParams) {
|
||||
if (eventsRouteParams.currentPage) {
|
||||
this.paginationConfig.currentPage = eventsRouteParams.currentPage;
|
||||
}
|
||||
if (eventsRouteParams.pageSize) {
|
||||
if (this.paginationConfig.pageSizeOptions.includes(eventsRouteParams.pageSize)) {
|
||||
this.paginationConfig.pageSize = eventsRouteParams.pageSize;
|
||||
} else {
|
||||
this.paginationConfig.pageSize = this.paginationConfig.pageSizeOptions[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from all subscriptions.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.subs
|
||||
.filter((sub) => hasValue(sub))
|
||||
.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { catchError, map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import {
|
||||
AddTopicsAction,
|
||||
OpenaireBrokerTopicActionTypes,
|
||||
RetrieveAllTopicsAction,
|
||||
RetrieveAllTopicsErrorAction,
|
||||
} from './openaire-broker-topics.actions';
|
||||
|
||||
import { OpenaireBrokerTopicObject } from '../../../core/openaire/broker/models/openaire-broker-topic.model';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { OpenaireBrokerTopicsService } from './openaire-broker-topics.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { OpenaireBrokerTopicRestService } from '../../../core/openaire/broker/topics/openaire-broker-topic-rest.service';
|
||||
|
||||
/**
|
||||
* Provides effect methods for the OpenAIRE Broker topics actions.
|
||||
*/
|
||||
@Injectable()
|
||||
export class OpenaireBrokerTopicsEffects {
|
||||
|
||||
/**
|
||||
* Retrieve all OpenAIRE Broker topics managing pagination and errors.
|
||||
*/
|
||||
@Effect() retrieveAllTopics$ = this.actions$.pipe(
|
||||
ofType(OpenaireBrokerTopicActionTypes.RETRIEVE_ALL_TOPICS),
|
||||
withLatestFrom(this.store$),
|
||||
switchMap(([action, currentState]: [RetrieveAllTopicsAction, any]) => {
|
||||
return this.openaireBrokerTopicService.getTopics(
|
||||
action.payload.elementsPerPage,
|
||||
action.payload.currentPage
|
||||
).pipe(
|
||||
map((topics: PaginatedList<OpenaireBrokerTopicObject>) =>
|
||||
new AddTopicsAction(topics.page, topics.totalPages, topics.currentPage, topics.totalElements)
|
||||
),
|
||||
catchError((error: Error) => {
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
return observableOf(new RetrieveAllTopicsErrorAction());
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Show a notification on error.
|
||||
*/
|
||||
@Effect({ dispatch: false }) retrieveAllTopicsErrorAction$ = this.actions$.pipe(
|
||||
ofType(OpenaireBrokerTopicActionTypes.RETRIEVE_ALL_TOPICS_ERROR),
|
||||
tap(() => {
|
||||
this.notificationsService.error(null, this.translate.get('openaire.broker.topic.error.service.retrieve'));
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Clear find all topics requests from cache.
|
||||
*/
|
||||
@Effect({ dispatch: false }) addTopicsAction$ = this.actions$.pipe(
|
||||
ofType(OpenaireBrokerTopicActionTypes.ADD_TOPICS),
|
||||
tap(() => {
|
||||
this.openaireBrokerTopicDataService.clearFindAllTopicsRequests();
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Initialize the effect class variables.
|
||||
* @param {Actions} actions$
|
||||
* @param {Store<any>} store$
|
||||
* @param {TranslateService} translate
|
||||
* @param {NotificationsService} notificationsService
|
||||
* @param {OpenaireBrokerTopicsService} openaireBrokerTopicService
|
||||
* @param {OpenaireBrokerTopicRestService} openaireBrokerTopicDataService
|
||||
*/
|
||||
constructor(
|
||||
private actions$: Actions,
|
||||
private store$: Store<any>,
|
||||
private translate: TranslateService,
|
||||
private notificationsService: NotificationsService,
|
||||
private openaireBrokerTopicService: OpenaireBrokerTopicsService,
|
||||
private openaireBrokerTopicDataService: OpenaireBrokerTopicRestService
|
||||
) { }
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
import {
|
||||
AddTopicsAction,
|
||||
RetrieveAllTopicsAction,
|
||||
RetrieveAllTopicsErrorAction
|
||||
} from './openaire-broker-topics.actions';
|
||||
import { openaireBrokerTopicsReducer, OpenaireBrokerTopicState } from './openaire-broker-topics.reducer';
|
||||
import {
|
||||
openaireBrokerTopicObjectMoreAbstract,
|
||||
openaireBrokerTopicObjectMorePid
|
||||
} from '../../../shared/mocks/openaire.mock';
|
||||
|
||||
describe('openaireBrokerTopicsReducer test suite', () => {
|
||||
let openaireBrokerTopicInitialState: OpenaireBrokerTopicState;
|
||||
const elementPerPage = 3;
|
||||
const currentPage = 0;
|
||||
|
||||
beforeEach(() => {
|
||||
openaireBrokerTopicInitialState = {
|
||||
topics: [],
|
||||
processing: false,
|
||||
loaded: false,
|
||||
totalPages: 0,
|
||||
currentPage: 0,
|
||||
totalElements: 0
|
||||
};
|
||||
});
|
||||
|
||||
it('Action RETRIEVE_ALL_TOPICS should set the State property "processing" to TRUE', () => {
|
||||
const expectedState = openaireBrokerTopicInitialState;
|
||||
expectedState.processing = true;
|
||||
|
||||
const action = new RetrieveAllTopicsAction(elementPerPage, currentPage);
|
||||
const newState = openaireBrokerTopicsReducer(openaireBrokerTopicInitialState, action);
|
||||
|
||||
expect(newState).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('Action RETRIEVE_ALL_TOPICS_ERROR should change the State to initial State but processing, loaded, and currentPage', () => {
|
||||
const expectedState = openaireBrokerTopicInitialState;
|
||||
expectedState.processing = false;
|
||||
expectedState.loaded = true;
|
||||
expectedState.currentPage = 0;
|
||||
|
||||
const action = new RetrieveAllTopicsErrorAction();
|
||||
const newState = openaireBrokerTopicsReducer(openaireBrokerTopicInitialState, action);
|
||||
|
||||
expect(newState).toEqual(expectedState);
|
||||
});
|
||||
|
||||
it('Action ADD_TOPICS should populate the State with OpenAIRE Broker topics', () => {
|
||||
const expectedState = {
|
||||
topics: [ openaireBrokerTopicObjectMorePid, openaireBrokerTopicObjectMoreAbstract ],
|
||||
processing: false,
|
||||
loaded: true,
|
||||
totalPages: 1,
|
||||
currentPage: 0,
|
||||
totalElements: 2
|
||||
};
|
||||
|
||||
const action = new AddTopicsAction(
|
||||
[ openaireBrokerTopicObjectMorePid, openaireBrokerTopicObjectMoreAbstract ],
|
||||
1, 0, 2
|
||||
);
|
||||
const newState = openaireBrokerTopicsReducer(openaireBrokerTopicInitialState, action);
|
||||
|
||||
expect(newState).toEqual(expectedState);
|
||||
});
|
||||
});
|
@@ -0,0 +1,72 @@
|
||||
import { OpenaireBrokerTopicObject } from '../../../core/openaire/broker/models/openaire-broker-topic.model';
|
||||
import { OpenaireBrokerTopicActionTypes, OpenaireBrokerTopicsActions } from './openaire-broker-topics.actions';
|
||||
|
||||
/**
|
||||
* The interface representing the OpenAIRE Broker topic state.
|
||||
*/
|
||||
export interface OpenaireBrokerTopicState {
|
||||
topics: OpenaireBrokerTopicObject[];
|
||||
processing: boolean;
|
||||
loaded: boolean;
|
||||
totalPages: number;
|
||||
currentPage: number;
|
||||
totalElements: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for the OpenAIRE Broker topic state initialization.
|
||||
*/
|
||||
const openaireBrokerTopicInitialState: OpenaireBrokerTopicState = {
|
||||
topics: [],
|
||||
processing: false,
|
||||
loaded: false,
|
||||
totalPages: 0,
|
||||
currentPage: 0,
|
||||
totalElements: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* The OpenAIRE Broker Topic Reducer
|
||||
*
|
||||
* @param state
|
||||
* the current state initialized with openaireBrokerTopicInitialState
|
||||
* @param action
|
||||
* the action to perform on the state
|
||||
* @return OpenaireBrokerTopicState
|
||||
* the new state
|
||||
*/
|
||||
export function openaireBrokerTopicsReducer(state = openaireBrokerTopicInitialState, action: OpenaireBrokerTopicsActions): OpenaireBrokerTopicState {
|
||||
switch (action.type) {
|
||||
case OpenaireBrokerTopicActionTypes.RETRIEVE_ALL_TOPICS: {
|
||||
return Object.assign({}, state, {
|
||||
topics: [],
|
||||
processing: true
|
||||
});
|
||||
}
|
||||
|
||||
case OpenaireBrokerTopicActionTypes.ADD_TOPICS: {
|
||||
return Object.assign({}, state, {
|
||||
topics: action.payload.topics,
|
||||
processing: false,
|
||||
loaded: true,
|
||||
totalPages: action.payload.totalPages,
|
||||
currentPage: state.currentPage,
|
||||
totalElements: action.payload.totalElements
|
||||
});
|
||||
}
|
||||
|
||||
case OpenaireBrokerTopicActionTypes.RETRIEVE_ALL_TOPICS_ERROR: {
|
||||
return Object.assign({}, state, {
|
||||
processing: false,
|
||||
loaded: true,
|
||||
totalPages: 0,
|
||||
currentPage: 0,
|
||||
totalElements: 0
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { OpenaireBrokerTopicsService } from './openaire-broker-topics.service';
|
||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
import { OpenaireBrokerTopicRestService } from '../../../core/openaire/broker/topics/openaire-broker-topic-rest.service';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
import { FindListOptions } from '../../../core/data/request.models';
|
||||
import {
|
||||
getMockOpenaireBrokerTopicRestService,
|
||||
openaireBrokerTopicObjectMoreAbstract,
|
||||
openaireBrokerTopicObjectMorePid
|
||||
} from '../../../shared/mocks/openaire.mock';
|
||||
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
|
||||
|
||||
describe('OpenaireBrokerTopicsService', () => {
|
||||
let service: OpenaireBrokerTopicsService;
|
||||
let restService: OpenaireBrokerTopicRestService;
|
||||
let serviceAsAny: any;
|
||||
let restServiceAsAny: any;
|
||||
|
||||
const pageInfo = new PageInfo();
|
||||
const array = [ openaireBrokerTopicObjectMorePid, openaireBrokerTopicObjectMoreAbstract ];
|
||||
const paginatedList = buildPaginatedList(pageInfo, array);
|
||||
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||
const elementsPerPage = 3;
|
||||
const currentPage = 0;
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
{ provide: OpenaireBrokerTopicRestService, useClass: getMockOpenaireBrokerTopicRestService },
|
||||
{ provide: OpenaireBrokerTopicsService, useValue: service }
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
restService = TestBed.get(OpenaireBrokerTopicRestService);
|
||||
restServiceAsAny = restService;
|
||||
restServiceAsAny.getTopics.and.returnValue(observableOf(paginatedListRD));
|
||||
service = new OpenaireBrokerTopicsService(restService);
|
||||
serviceAsAny = service;
|
||||
});
|
||||
|
||||
describe('getTopics', () => {
|
||||
it('Should proxy the call to openaireBrokerTopicRestService.getTopics', () => {
|
||||
const sortOptions = new SortOptions('name', SortDirection.ASC);
|
||||
const findListOptions: FindListOptions = {
|
||||
elementsPerPage: elementsPerPage,
|
||||
currentPage: currentPage,
|
||||
sort: sortOptions
|
||||
};
|
||||
const result = service.getTopics(elementsPerPage, currentPage);
|
||||
expect((service as any).openaireBrokerTopicRestService.getTopics).toHaveBeenCalledWith(findListOptions);
|
||||
});
|
||||
|
||||
it('Should return a paginated list of OpenAIRE Broker topics', () => {
|
||||
const expected = cold('(a|)', {
|
||||
a: paginatedList
|
||||
});
|
||||
const result = service.getTopics(elementsPerPage, currentPage);
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,55 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { find, map } from 'rxjs/operators';
|
||||
import { OpenaireBrokerTopicRestService } from '../../../core/openaire/broker/topics/openaire-broker-topic-rest.service';
|
||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
import { FindListOptions } from '../../../core/data/request.models';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { OpenaireBrokerTopicObject } from '../../../core/openaire/broker/models/openaire-broker-topic.model';
|
||||
|
||||
/**
|
||||
* The service handling all OpenAIRE Broker topic requests to the REST service.
|
||||
*/
|
||||
@Injectable()
|
||||
export class OpenaireBrokerTopicsService {
|
||||
|
||||
/**
|
||||
* Initialize the service variables.
|
||||
* @param {OpenaireBrokerTopicRestService} openaireBrokerTopicRestService
|
||||
*/
|
||||
constructor(
|
||||
private openaireBrokerTopicRestService: OpenaireBrokerTopicRestService
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Return the list of OpenAIRE Broker topics managing pagination and errors.
|
||||
*
|
||||
* @param elementsPerPage
|
||||
* The number of the topics per page
|
||||
* @param currentPage
|
||||
* The page number to retrieve
|
||||
* @return Observable<PaginatedList<OpenaireBrokerTopicObject>>
|
||||
* The list of OpenAIRE Broker topics.
|
||||
*/
|
||||
public getTopics(elementsPerPage, currentPage): Observable<PaginatedList<OpenaireBrokerTopicObject>> {
|
||||
const sortOptions = new SortOptions('name', SortDirection.ASC);
|
||||
|
||||
const findListOptions: FindListOptions = {
|
||||
elementsPerPage: elementsPerPage,
|
||||
currentPage: currentPage,
|
||||
sort: sortOptions
|
||||
};
|
||||
|
||||
return this.openaireBrokerTopicRestService.getTopics(findListOptions).pipe(
|
||||
find((rd: RemoteData<PaginatedList<OpenaireBrokerTopicObject>>) => !rd.isResponsePending),
|
||||
map((rd: RemoteData<PaginatedList<OpenaireBrokerTopicObject>>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
return rd.payload;
|
||||
} else {
|
||||
throw new Error('Can\'t retrieve OpenAIRE Broker topics from the Broker topics REST service');
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
275
src/app/openaire/openaire-state.service.spec.ts
Normal file
275
src/app/openaire/openaire-state.service.spec.ts
Normal file
@@ -0,0 +1,275 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Store, StoreModule } from '@ngrx/store';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { cold } from 'jasmine-marbles';
|
||||
import { openaireReducers } from './openaire.reducer';
|
||||
import { OpenaireStateService } from './openaire-state.service';
|
||||
import {
|
||||
openaireBrokerTopicObjectMissingPid,
|
||||
openaireBrokerTopicObjectMoreAbstract,
|
||||
openaireBrokerTopicObjectMorePid
|
||||
} from '../shared/mocks/openaire.mock';
|
||||
import { RetrieveAllTopicsAction } from './broker/topics/openaire-broker-topics.actions';
|
||||
|
||||
describe('OpenaireStateService', () => {
|
||||
let service: OpenaireStateService;
|
||||
let serviceAsAny: any;
|
||||
let store: any;
|
||||
let initialState: any;
|
||||
|
||||
function init(mode: string) {
|
||||
if (mode === 'empty') {
|
||||
initialState = {
|
||||
openaire: {
|
||||
brokerTopic: {
|
||||
topics: [],
|
||||
processing: false,
|
||||
loaded: false,
|
||||
totalPages: 0,
|
||||
currentPage: 0,
|
||||
totalElements: 0,
|
||||
totalLoadedPages: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
} else {
|
||||
initialState = {
|
||||
openaire: {
|
||||
brokerTopic: {
|
||||
topics: [
|
||||
openaireBrokerTopicObjectMorePid,
|
||||
openaireBrokerTopicObjectMoreAbstract,
|
||||
openaireBrokerTopicObjectMissingPid
|
||||
],
|
||||
processing: false,
|
||||
loaded: true,
|
||||
totalPages: 1,
|
||||
currentPage: 1,
|
||||
totalElements: 3,
|
||||
totalLoadedPages: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
describe('Testing methods with empty topic objects', () => {
|
||||
beforeEach(async () => {
|
||||
init('empty');
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
StoreModule.forRoot({ openaire: openaireReducers } as any),
|
||||
],
|
||||
providers: [
|
||||
provideMockStore({ initialState }),
|
||||
{ provide: OpenaireStateService, useValue: service }
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
store = TestBed.get(Store);
|
||||
service = new OpenaireStateService(store);
|
||||
serviceAsAny = service;
|
||||
spyOn(store, 'dispatch');
|
||||
});
|
||||
|
||||
describe('getOpenaireBrokerTopics', () => {
|
||||
it('Should return an empty array', () => {
|
||||
const result = service.getOpenaireBrokerTopics();
|
||||
const expected = cold('(a)', {
|
||||
a: []
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOpenaireBrokerTopicsTotalPages', () => {
|
||||
it('Should return zero (0)', () => {
|
||||
const result = service.getOpenaireBrokerTopicsTotalPages();
|
||||
const expected = cold('(a)', {
|
||||
a: 0
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOpenaireBrokerTopicsCurrentPage', () => {
|
||||
it('Should return minus one (0)', () => {
|
||||
const result = service.getOpenaireBrokerTopicsCurrentPage();
|
||||
const expected = cold('(a)', {
|
||||
a: 0
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOpenaireBrokerTopicsTotals', () => {
|
||||
it('Should return zero (0)', () => {
|
||||
const result = service.getOpenaireBrokerTopicsTotals();
|
||||
const expected = cold('(a)', {
|
||||
a: 0
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOpenaireBrokerTopicsLoading', () => {
|
||||
it('Should return TRUE', () => {
|
||||
const result = service.isOpenaireBrokerTopicsLoading();
|
||||
const expected = cold('(a)', {
|
||||
a: true
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOpenaireBrokerTopicsLoaded', () => {
|
||||
it('Should return FALSE', () => {
|
||||
const result = service.isOpenaireBrokerTopicsLoaded();
|
||||
const expected = cold('(a)', {
|
||||
a: false
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOpenaireBrokerTopicsProcessing', () => {
|
||||
it('Should return FALSE', () => {
|
||||
const result = service.isOpenaireBrokerTopicsProcessing();
|
||||
const expected = cold('(a)', {
|
||||
a: false
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing methods with topic objects', () => {
|
||||
beforeEach(async () => {
|
||||
init('full');
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
StoreModule.forRoot({ openaire: openaireReducers } as any),
|
||||
],
|
||||
providers: [
|
||||
provideMockStore({ initialState }),
|
||||
{ provide: OpenaireStateService, useValue: service }
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
store = TestBed.get(Store);
|
||||
service = new OpenaireStateService(store);
|
||||
serviceAsAny = service;
|
||||
spyOn(store, 'dispatch');
|
||||
});
|
||||
|
||||
describe('getOpenaireBrokerTopics', () => {
|
||||
it('Should return an array of topics', () => {
|
||||
const result = service.getOpenaireBrokerTopics();
|
||||
const expected = cold('(a)', {
|
||||
a: [
|
||||
openaireBrokerTopicObjectMorePid,
|
||||
openaireBrokerTopicObjectMoreAbstract,
|
||||
openaireBrokerTopicObjectMissingPid
|
||||
]
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOpenaireBrokerTopicsTotalPages', () => {
|
||||
it('Should return one (1)', () => {
|
||||
const result = service.getOpenaireBrokerTopicsTotalPages();
|
||||
const expected = cold('(a)', {
|
||||
a: 1
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOpenaireBrokerTopicsCurrentPage', () => {
|
||||
it('Should return minus zero (1)', () => {
|
||||
const result = service.getOpenaireBrokerTopicsCurrentPage();
|
||||
const expected = cold('(a)', {
|
||||
a: 1
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOpenaireBrokerTopicsTotals', () => {
|
||||
it('Should return three (3)', () => {
|
||||
const result = service.getOpenaireBrokerTopicsTotals();
|
||||
const expected = cold('(a)', {
|
||||
a: 3
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOpenaireBrokerTopicsLoading', () => {
|
||||
it('Should return FALSE', () => {
|
||||
const result = service.isOpenaireBrokerTopicsLoading();
|
||||
const expected = cold('(a)', {
|
||||
a: false
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOpenaireBrokerTopicsLoaded', () => {
|
||||
it('Should return TRUE', () => {
|
||||
const result = service.isOpenaireBrokerTopicsLoaded();
|
||||
const expected = cold('(a)', {
|
||||
a: true
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isOpenaireBrokerTopicsProcessing', () => {
|
||||
it('Should return FALSE', () => {
|
||||
const result = service.isOpenaireBrokerTopicsProcessing();
|
||||
const expected = cold('(a)', {
|
||||
a: false
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing the topic dispatch methods', () => {
|
||||
beforeEach(async () => {
|
||||
init('full');
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
StoreModule.forRoot({ openaire: openaireReducers } as any),
|
||||
],
|
||||
providers: [
|
||||
provideMockStore({ initialState }),
|
||||
{ provide: OpenaireStateService, useValue: service }
|
||||
]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
store = TestBed.get(Store);
|
||||
service = new OpenaireStateService(store);
|
||||
serviceAsAny = service;
|
||||
spyOn(store, 'dispatch');
|
||||
});
|
||||
|
||||
describe('dispatchRetrieveOpenaireBrokerTopics', () => {
|
||||
it('Should call store.dispatch', () => {
|
||||
const elementsPerPage = 3;
|
||||
const currentPage = 1;
|
||||
const action = new RetrieveAllTopicsAction(elementsPerPage, currentPage);
|
||||
service.dispatchRetrieveOpenaireBrokerTopics(elementsPerPage, currentPage);
|
||||
expect(serviceAsAny.store.dispatch).toHaveBeenCalledWith(action);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
116
src/app/openaire/openaire-state.service.ts
Normal file
116
src/app/openaire/openaire-state.service.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import {
|
||||
getOpenaireBrokerTopicsCurrentPageSelector,
|
||||
getOpenaireBrokerTopicsTotalPagesSelector,
|
||||
getOpenaireBrokerTopicsTotalsSelector,
|
||||
isOpenaireBrokerTopicsLoadedSelector,
|
||||
openaireBrokerTopicsObjectSelector,
|
||||
sOpenaireBrokerTopicsProcessingSelector
|
||||
} from './selectors';
|
||||
import { OpenaireBrokerTopicObject } from '../core/openaire/broker/models/openaire-broker-topic.model';
|
||||
import { OpenaireState } from './openaire.reducer';
|
||||
import { RetrieveAllTopicsAction } from './broker/topics/openaire-broker-topics.actions';
|
||||
|
||||
/**
|
||||
* The service handling the OpenAIRE State.
|
||||
*/
|
||||
@Injectable()
|
||||
export class OpenaireStateService {
|
||||
|
||||
/**
|
||||
* Initialize the service variables.
|
||||
* @param {Store<OpenaireState>} store
|
||||
*/
|
||||
constructor(private store: Store<OpenaireState>) { }
|
||||
|
||||
// OpenAIRE Broker topics
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the list of OpenAIRE Broker topics from the state.
|
||||
*
|
||||
* @return Observable<OpenaireBrokerTopicObject>
|
||||
* The list of OpenAIRE Broker topics.
|
||||
*/
|
||||
public getOpenaireBrokerTopics(): Observable<OpenaireBrokerTopicObject[]> {
|
||||
return this.store.pipe(select(openaireBrokerTopicsObjectSelector()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about the loading status of the OpenAIRE Broker topics (if it's running or not).
|
||||
*
|
||||
* @return Observable<boolean>
|
||||
* 'true' if the topics are loading, 'false' otherwise.
|
||||
*/
|
||||
public isOpenaireBrokerTopicsLoading(): Observable<boolean> {
|
||||
return this.store.pipe(
|
||||
select(isOpenaireBrokerTopicsLoadedSelector),
|
||||
map((loaded: boolean) => !loaded)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about the loading status of the OpenAIRE Broker topics (whether or not they were loaded).
|
||||
*
|
||||
* @return Observable<boolean>
|
||||
* 'true' if the topics are loaded, 'false' otherwise.
|
||||
*/
|
||||
public isOpenaireBrokerTopicsLoaded(): Observable<boolean> {
|
||||
return this.store.pipe(select(isOpenaireBrokerTopicsLoadedSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about the processing status of the OpenAIRE Broker topics (if it's running or not).
|
||||
*
|
||||
* @return Observable<boolean>
|
||||
* 'true' if there are operations running on the topics (ex.: a REST call), 'false' otherwise.
|
||||
*/
|
||||
public isOpenaireBrokerTopicsProcessing(): Observable<boolean> {
|
||||
return this.store.pipe(select(sOpenaireBrokerTopicsProcessingSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, from the state, the total available pages of the OpenAIRE Broker topics.
|
||||
*
|
||||
* @return Observable<number>
|
||||
* The number of the OpenAIRE Broker topics pages.
|
||||
*/
|
||||
public getOpenaireBrokerTopicsTotalPages(): Observable<number> {
|
||||
return this.store.pipe(select(getOpenaireBrokerTopicsTotalPagesSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current page of the OpenAIRE Broker topics, from the state.
|
||||
*
|
||||
* @return Observable<number>
|
||||
* The number of the current OpenAIRE Broker topics page.
|
||||
*/
|
||||
public getOpenaireBrokerTopicsCurrentPage(): Observable<number> {
|
||||
return this.store.pipe(select(getOpenaireBrokerTopicsCurrentPageSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of the OpenAIRE Broker topics.
|
||||
*
|
||||
* @return Observable<number>
|
||||
* The number of the OpenAIRE Broker topics.
|
||||
*/
|
||||
public getOpenaireBrokerTopicsTotals(): Observable<number> {
|
||||
return this.store.pipe(select(getOpenaireBrokerTopicsTotalsSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a request to change the OpenAIRE Broker topics state, retrieving the topics from the server.
|
||||
*
|
||||
* @param elementsPerPage
|
||||
* The number of the topics per page.
|
||||
* @param currentPage
|
||||
* The number of the current page.
|
||||
*/
|
||||
public dispatchRetrieveOpenaireBrokerTopics(elementsPerPage: number, currentPage: number): void {
|
||||
this.store.dispatch(new RetrieveAllTopicsAction(elementsPerPage, currentPage));
|
||||
}
|
||||
}
|
5
src/app/openaire/openaire.effects.ts
Normal file
5
src/app/openaire/openaire.effects.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { OpenaireBrokerTopicsEffects } from './broker/topics/openaire-broker-topics.effects';
|
||||
|
||||
export const openaireEffects = [
|
||||
OpenaireBrokerTopicsEffects
|
||||
];
|
74
src/app/openaire/openaire.module.ts
Normal file
74
src/app/openaire/openaire.module.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Action, StoreConfig, StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { CoreModule } from '../core/core.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { storeModuleConfig } from '../app.reducer';
|
||||
import { OpenaireBrokerTopicsComponent } from './broker/topics/openaire-broker-topics.component';
|
||||
import { OpenaireBrokerEventsComponent } from './broker/events/openaire-broker-events.component';
|
||||
import { OpenaireStateService } from './openaire-state.service';
|
||||
import { openaireReducers, OpenaireState } from './openaire.reducer';
|
||||
import { openaireEffects } from './openaire.effects';
|
||||
import { OpenaireBrokerTopicsService } from './broker/topics/openaire-broker-topics.service';
|
||||
import { OpenaireBrokerTopicRestService } from '../core/openaire/broker/topics/openaire-broker-topic-rest.service';
|
||||
import { OpenaireBrokerEventRestService } from '../core/openaire/broker/events/openaire-broker-event-rest.service';
|
||||
import { ProjectEntryImportModalComponent } from './broker/project-entry-import-modal/project-entry-import-modal.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SearchModule } from '../shared/search/search.module';
|
||||
|
||||
const MODULES = [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
CoreModule.forRoot(),
|
||||
StoreModule.forFeature('openaire', openaireReducers, storeModuleConfig as StoreConfig<OpenaireState, Action>),
|
||||
EffectsModule.forFeature(openaireEffects),
|
||||
TranslateModule
|
||||
];
|
||||
|
||||
const COMPONENTS = [
|
||||
OpenaireBrokerTopicsComponent,
|
||||
OpenaireBrokerEventsComponent
|
||||
];
|
||||
|
||||
const DIRECTIVES = [ ];
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
ProjectEntryImportModalComponent
|
||||
];
|
||||
|
||||
const PROVIDERS = [
|
||||
OpenaireStateService,
|
||||
OpenaireBrokerTopicsService,
|
||||
OpenaireBrokerTopicRestService,
|
||||
OpenaireBrokerEventRestService
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
...MODULES,
|
||||
SearchModule
|
||||
],
|
||||
declarations: [
|
||||
...COMPONENTS,
|
||||
...DIRECTIVES,
|
||||
...ENTRY_COMPONENTS
|
||||
],
|
||||
providers: [
|
||||
...PROVIDERS
|
||||
],
|
||||
entryComponents: [
|
||||
...ENTRY_COMPONENTS
|
||||
],
|
||||
exports: [
|
||||
...COMPONENTS,
|
||||
...DIRECTIVES
|
||||
]
|
||||
})
|
||||
|
||||
/**
|
||||
* This module handles all components that are necessary for the OpenAIRE components
|
||||
*/
|
||||
export class OpenaireModule {
|
||||
}
|
16
src/app/openaire/openaire.reducer.ts
Normal file
16
src/app/openaire/openaire.reducer.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { ActionReducerMap, createFeatureSelector } from '@ngrx/store';
|
||||
|
||||
import { openaireBrokerTopicsReducer, OpenaireBrokerTopicState, } from './broker/topics/openaire-broker-topics.reducer';
|
||||
|
||||
/**
|
||||
* The OpenAIRE State
|
||||
*/
|
||||
export interface OpenaireState {
|
||||
'brokerTopic': OpenaireBrokerTopicState;
|
||||
}
|
||||
|
||||
export const openaireReducers: ActionReducerMap<OpenaireState> = {
|
||||
brokerTopic: openaireBrokerTopicsReducer,
|
||||
};
|
||||
|
||||
export const openaireSelector = createFeatureSelector<OpenaireState>('openaire');
|
79
src/app/openaire/selectors.ts
Normal file
79
src/app/openaire/selectors.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { createSelector, MemoizedSelector } from '@ngrx/store';
|
||||
import { subStateSelector } from '../shared/selector.util';
|
||||
import { openaireSelector, OpenaireState } from './openaire.reducer';
|
||||
import { OpenaireBrokerTopicObject } from '../core/openaire/broker/models/openaire-broker-topic.model';
|
||||
import { OpenaireBrokerTopicState } from './broker/topics/openaire-broker-topics.reducer';
|
||||
|
||||
/**
|
||||
* Returns the OpenAIRE state.
|
||||
* @function _getOpenaireState
|
||||
* @param {AppState} state Top level state.
|
||||
* @return {OpenaireState}
|
||||
*/
|
||||
const _getOpenaireState = (state: any) => state.openaire;
|
||||
|
||||
// OpenAIRE Broker topics
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the OpenAIRE Broker topics State.
|
||||
* @function openaireBrokerTopicsStateSelector
|
||||
* @return {OpenaireBrokerTopicState}
|
||||
*/
|
||||
export function openaireBrokerTopicsStateSelector(): MemoizedSelector<OpenaireState, OpenaireBrokerTopicState> {
|
||||
return subStateSelector<OpenaireState,OpenaireBrokerTopicState>(openaireSelector, 'brokerTopic');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the OpenAIRE Broker topics list.
|
||||
* @function openaireBrokerTopicsObjectSelector
|
||||
* @return {OpenaireBrokerTopicObject[]}
|
||||
*/
|
||||
export function openaireBrokerTopicsObjectSelector(): MemoizedSelector<OpenaireState, OpenaireBrokerTopicObject[]> {
|
||||
return subStateSelector<OpenaireState, OpenaireBrokerTopicObject[]>(openaireBrokerTopicsStateSelector(), 'topics');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the OpenAIRE Broker topics are loaded.
|
||||
* @function isOpenaireBrokerTopicsLoadedSelector
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const isOpenaireBrokerTopicsLoadedSelector = createSelector(_getOpenaireState,
|
||||
(state: OpenaireState) => state.brokerTopic.loaded
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the deduplication sets are processing.
|
||||
* @function isDeduplicationSetsProcessingSelector
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const sOpenaireBrokerTopicsProcessingSelector = createSelector(_getOpenaireState,
|
||||
(state: OpenaireState) => state.brokerTopic.processing
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the total available pages of OpenAIRE Broker topics.
|
||||
* @function getOpenaireBrokerTopicsTotalPagesSelector
|
||||
* @return {number}
|
||||
*/
|
||||
export const getOpenaireBrokerTopicsTotalPagesSelector = createSelector(_getOpenaireState,
|
||||
(state: OpenaireState) => state.brokerTopic.totalPages
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the current page of OpenAIRE Broker topics.
|
||||
* @function getOpenaireBrokerTopicsCurrentPageSelector
|
||||
* @return {number}
|
||||
*/
|
||||
export const getOpenaireBrokerTopicsCurrentPageSelector = createSelector(_getOpenaireState,
|
||||
(state: OpenaireState) => state.brokerTopic.currentPage
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the total number of OpenAIRE Broker topics.
|
||||
* @function getOpenaireBrokerTopicsTotalsSelector
|
||||
* @return {number}
|
||||
*/
|
||||
export const getOpenaireBrokerTopicsTotalsSelector = createSelector(_getOpenaireState,
|
||||
(state: OpenaireState) => state.brokerTopic.totalElements
|
||||
);
|
1796
src/app/shared/mocks/openaire.mock.ts
Normal file
1796
src/app/shared/mocks/openaire.mock.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,10 @@
|
||||
<div id="paginationControlsDropdownMenu" aria-labelledby="paginationControls" ngbDropdownMenu>
|
||||
<h6 class="dropdown-header">{{ 'pagination.results-per-page' | translate}}</h6>
|
||||
<button class="dropdown-item" *ngFor="let item of pageSizeOptions" (click)="doPageSizeChange(item)"><i [ngClass]="{'invisible': item != (pageSize$|async)}" class="fas fa-check" aria-hidden="true"></i> {{item}} </button>
|
||||
<ng-container *ngIf="!hideSortOptions">
|
||||
<h6 class="dropdown-header">{{ 'pagination.sort-direction' | translate}}</h6>
|
||||
<button class="dropdown-item" *ngFor="let direction of (sortDirections | dsKeys)" (click)="doSortDirectionChange(direction.value)"><i [ngClass]="{'invisible': direction.value !== (sortDirection$ |async)}" class="fas fa-check" aria-hidden="true"></i> {{'sorting.' + direction.key | translate}} </button>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -93,6 +93,11 @@ export class PaginationComponent implements OnDestroy, OnInit {
|
||||
*/
|
||||
@Input() public hideGear = false;
|
||||
|
||||
/**
|
||||
* Option for hiding the gear
|
||||
*/
|
||||
@Input() public hideSortOptions = false;
|
||||
|
||||
/**
|
||||
* Option for hiding the pager when there is less than 2 pages
|
||||
*/
|
||||
|
27
src/app/shared/selector.util.ts
Normal file
27
src/app/shared/selector.util.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { createSelector, MemoizedSelector, Selector } from '@ngrx/store';
|
||||
import { hasValue } from './empty.util';
|
||||
|
||||
/**
|
||||
* Export a function to return a subset of the state by key
|
||||
*/
|
||||
export function keySelector<T, V>(parentSelector: Selector<any, any>, subState: string, key: string): MemoizedSelector<T, V> {
|
||||
return createSelector(parentSelector, (state: T) => {
|
||||
if (hasValue(state) && hasValue(state[subState])) {
|
||||
return state[subState][key];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Export a function to return a subset of the state
|
||||
*/
|
||||
export function subStateSelector<T, V>(parentSelector: Selector<any, any>, subState: string): MemoizedSelector<T, V> {
|
||||
return createSelector(parentSelector, (state: T) => {
|
||||
if (hasValue(state) && hasValue(state[subState])) {
|
||||
return state[subState];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
@@ -479,7 +479,13 @@
|
||||
|
||||
"admin.access-control.groups.form.return": "Back",
|
||||
|
||||
"admin.notifications.openairebroker.breadcrumbs": "OpenAIRE Broker",
|
||||
|
||||
"admin.notifications.openairebroker.page.title": "OpenAIRE Broker",
|
||||
|
||||
"admin.notifications.openaireevent.breadcrumbs": "OpenAIRE Broker Suggestions",
|
||||
|
||||
"admin.notifications.openaireevent.page.title": "OpenAIRE Broker Suggestions",
|
||||
|
||||
"admin.search.breadcrumbs": "Administrative Search",
|
||||
|
||||
@@ -2408,6 +2414,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
"menu.header.admin": "Management",
|
||||
|
||||
"menu.header.image.logo": "Repository logo",
|
||||
@@ -2510,6 +2517,8 @@
|
||||
|
||||
"menu.section.icon.unpin": "Unpin sidebar",
|
||||
|
||||
"menu.section.icon.notifications": "Notifications menu section",
|
||||
|
||||
|
||||
|
||||
"menu.section.import": "Import",
|
||||
@@ -2533,6 +2542,12 @@
|
||||
"menu.section.new_process": "Process",
|
||||
|
||||
|
||||
"menu.section.notifications": "Notifications",
|
||||
|
||||
"menu.section.notifications_openaire_broker": "OpenAIRE Broker",
|
||||
|
||||
"menu.section.notifications_reciter": "Publication Claim",
|
||||
|
||||
|
||||
"menu.section.pin": "Pin sidebar",
|
||||
|
||||
@@ -2698,6 +2713,126 @@
|
||||
|
||||
"none.listelement.badge": "Item",
|
||||
|
||||
"openaire.broker.title": "OpenAIRE Broker",
|
||||
|
||||
"openaire.broker.topics.description": "Below you can see all the topics received from the subscriptions to OpenAIRE.",
|
||||
|
||||
"openaire.broker.topics": "Current Topics",
|
||||
|
||||
"openaire.broker.table.topic": "Topic",
|
||||
|
||||
"openaire.broker.table.last-event": "Last Event",
|
||||
|
||||
"openaire.broker.table.actions": "Actions",
|
||||
|
||||
"openaire.broker.button.detail": "Show details",
|
||||
|
||||
"openaire.broker.noTopics": "No topics found.",
|
||||
|
||||
"openaire.broker.topic.error.service.retrieve": "An error occurred while loading the OpenAIRE Broker topics",
|
||||
|
||||
"openaire.broker.loading": "Loading ...",
|
||||
|
||||
"openaire.events.title": "OpenAIRE Broker Suggestions",
|
||||
|
||||
"openaire.broker.events.description": "Below the list of all the suggestions, received from OpenAIRE, for the selected topic.",
|
||||
|
||||
"openaire.broker.events.topic": "Topic:",
|
||||
|
||||
"openaire.broker.noEvents": "No suggestions found.",
|
||||
|
||||
"openaire.broker.event.table.trust": "Trust",
|
||||
|
||||
"openaire.broker.event.table.publication": "Publication",
|
||||
|
||||
"openaire.broker.event.table.details": "Details",
|
||||
|
||||
"openaire.broker.event.table.project-details": "Project details",
|
||||
|
||||
"openaire.broker.event.table.actions": "Actions",
|
||||
|
||||
"openaire.broker.event.action.accept": "Accept suggestion",
|
||||
|
||||
"openaire.broker.event.action.ignore": "Ignore suggestion",
|
||||
|
||||
"openaire.broker.event.action.reject": "Reject suggestion",
|
||||
|
||||
"openaire.broker.event.action.import": "Import project and accept suggestion",
|
||||
|
||||
"openaire.broker.event.table.pidtype": "PID Type:",
|
||||
|
||||
"openaire.broker.event.table.pidvalue": "PID Value:",
|
||||
|
||||
"openaire.broker.event.table.subjectValue": "Subject Value:",
|
||||
|
||||
"openaire.broker.event.table.abstract": "Abstract:",
|
||||
|
||||
"openaire.broker.event.table.suggestedProject": "OpenAIRE Suggested Project data",
|
||||
|
||||
"openaire.broker.event.table.project": "Project title:",
|
||||
|
||||
"openaire.broker.event.table.acronym": "Acronym:",
|
||||
|
||||
"openaire.broker.event.table.code": "Code:",
|
||||
|
||||
"openaire.broker.event.table.funder": "Funder:",
|
||||
|
||||
"openaire.broker.event.table.fundingProgram": "Funding program:",
|
||||
|
||||
"openaire.broker.event.table.jurisdiction": "Jurisdiction:",
|
||||
|
||||
"openaire.broker.events.back": "Back to topics",
|
||||
|
||||
"openaire.broker.event.table.less": "Show less",
|
||||
|
||||
"openaire.broker.event.table.more": "Show more",
|
||||
|
||||
"openaire.broker.event.project.found": "Bound to the local record:",
|
||||
|
||||
"openaire.broker.event.project.notFound": "No local record found",
|
||||
|
||||
"openaire.broker.event.sure": "Are you sure?",
|
||||
|
||||
"openaire.broker.event.ignore.description": "This operation can't be undone. Ignore this suggestion?",
|
||||
|
||||
"openaire.broker.event.reject.description": "This operation can't be undone. Reject this suggestion?",
|
||||
|
||||
"openaire.broker.event.accept.description": "No DSpace project selected. A new project will be created based on the suggestion data.",
|
||||
|
||||
"openaire.broker.event.action.cancel": "Cancel",
|
||||
|
||||
"openaire.broker.event.action.saved": "Your decision has been saved successfully.",
|
||||
|
||||
"openaire.broker.event.action.error": "An error has occurred. Your decision has not been saved.",
|
||||
|
||||
"openaire.broker.event.modal.project.title": "Choose a project to bound",
|
||||
|
||||
"openaire.broker.event.modal.project.publication": "Publication:",
|
||||
|
||||
"openaire.broker.event.modal.project.bountToLocal": "Bound to the local record:",
|
||||
|
||||
"openaire.broker.event.modal.project.select": "Project search",
|
||||
|
||||
"openaire.broker.event.modal.project.search": "Search",
|
||||
|
||||
"openaire.broker.event.modal.project.clear": "Clear",
|
||||
|
||||
"openaire.broker.event.modal.project.cancel": "Cancel",
|
||||
|
||||
"openaire.broker.event.modal.project.bound": "Bound project",
|
||||
|
||||
"openaire.broker.event.modal.project.placeholder": "Enter a project name",
|
||||
|
||||
"openaire.broker.event.modal.project.notFound": "No project found.",
|
||||
|
||||
"openaire.broker.event.project.bounded": "The project has been linked successfully.",
|
||||
|
||||
"openaire.broker.event.project.removed": "The project has been successfully unlinked.",
|
||||
|
||||
"openaire.broker.event.project.error": "An error has occurred. No operation performed.",
|
||||
|
||||
"openaire.broker.event.reason": "Reason",
|
||||
|
||||
|
||||
"orgunit.listelement.badge": "Organizational Unit",
|
||||
|
||||
@@ -3362,7 +3497,9 @@
|
||||
|
||||
"search.filters.filter.submitter.label": "Search submitter",
|
||||
|
||||
"search.filters.filter.funding.head": "Funding",
|
||||
|
||||
"search.filters.filter.funding.placeholder": "Funding",
|
||||
|
||||
"search.filters.entityType.JournalIssue": "Journal Issue",
|
||||
|
||||
|
Reference in New Issue
Block a user