mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'main' into coar-notify-7
This commit is contained in:
@@ -386,3 +386,9 @@ vocabularies:
|
||||
comcolSelectionSort:
|
||||
sortField: 'dc.title'
|
||||
sortDirection: 'ASC'
|
||||
|
||||
# Example of fallback collection for suggestions import
|
||||
# suggestion:
|
||||
# - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af
|
||||
# source: "openaire"
|
||||
|
||||
|
@@ -0,0 +1,44 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Interface for the route parameters.
|
||||
*/
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service.ts
|
||||
export interface NotificationsSuggestionTargetsPageParams {
|
||||
========
|
||||
export interface AdminNotificationsPublicationClaimPageParams {
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts
|
||||
pageId?: string;
|
||||
pageSize?: number;
|
||||
currentPage?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a resolver that retrieve the route data before the route is activated.
|
||||
*/
|
||||
@Injectable()
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service.ts
|
||||
export class NotificationsSuggestionTargetsPageResolver implements Resolve<NotificationsSuggestionTargetsPageParams> {
|
||||
========
|
||||
export class AdminNotificationsPublicationClaimPageResolver implements Resolve<AdminNotificationsPublicationClaimPageParams> {
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts
|
||||
|
||||
/**
|
||||
* Method for resolving the parameters in the current route.
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns AdminNotificationsSuggestionTargetsPageParams Emits the route parameters
|
||||
*/
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service.ts
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): NotificationsSuggestionTargetsPageParams {
|
||||
========
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): AdminNotificationsPublicationClaimPageParams {
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts
|
||||
return {
|
||||
pageId: route.queryParams.pageId,
|
||||
pageSize: parseInt(route.queryParams.pageSize, 10),
|
||||
currentPage: parseInt(route.queryParams.page, 10)
|
||||
};
|
||||
}
|
||||
}
|
@@ -0,0 +1 @@
|
||||
<ds-publication-claim [source]="'openaire'"></ds-publication-claim>
|
@@ -0,0 +1,59 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page.component.spec.ts
|
||||
import { NotificationsSuggestionTargetsPageComponent } from './notifications-suggestion-targets-page.component';
|
||||
========
|
||||
import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page.component';
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page.component.spec.ts
|
||||
describe('NotificationsSuggestionTargetsPageComponent', () => {
|
||||
let component: NotificationsSuggestionTargetsPageComponent;
|
||||
let fixture: ComponentFixture<NotificationsSuggestionTargetsPageComponent>;
|
||||
========
|
||||
describe('AdminNotificationsPublicationClaimPageComponent', () => {
|
||||
let component: AdminNotificationsPublicationClaimPageComponent;
|
||||
let fixture: ComponentFixture<AdminNotificationsPublicationClaimPageComponent>;
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page.component.spec.ts
|
||||
NotificationsSuggestionTargetsPageComponent
|
||||
],
|
||||
providers: [
|
||||
NotificationsSuggestionTargetsPageComponent
|
||||
========
|
||||
AdminNotificationsPublicationClaimPageComponent
|
||||
],
|
||||
providers: [
|
||||
AdminNotificationsPublicationClaimPageComponent
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page.component.spec.ts
|
||||
fixture = TestBed.createComponent(NotificationsSuggestionTargetsPageComponent);
|
||||
========
|
||||
fixture = TestBed.createComponent(AdminNotificationsPublicationClaimPageComponent);
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@@ -0,0 +1,10 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-admin-notifications-publication-claim-page',
|
||||
templateUrl: './admin-notifications-publication-claim-page.component.html',
|
||||
styleUrls: ['./admin-notifications-publication-claim-page.component.scss']
|
||||
})
|
||||
export class AdminNotificationsPublicationClaimPageComponent {
|
||||
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||
import { getNotificationsModuleRoute } from '../admin-routing-paths';
|
||||
|
||||
export const QUALITY_ASSURANCE_EDIT_PATH = 'quality-assurance';
|
||||
export const PUBLICATION_CLAIMS_PATH = 'publication-claim';
|
||||
|
||||
export function getQualityAssuranceRoute(id: string) {
|
||||
return new URLCombiner(getNotificationsModuleRoute(), QUALITY_ASSURANCE_EDIT_PATH, id).toString();
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
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 { PUBLICATION_CLAIMS_PATH } from './admin-notifications-routing-paths';
|
||||
import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component';
|
||||
import { AdminNotificationsPublicationClaimPageResolver } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service';
|
||||
import { QUALITY_ASSURANCE_EDIT_PATH } from './admin-notifications-routing-paths';
|
||||
import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component';
|
||||
import { AdminQualityAssuranceEventsPageComponent } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.component';
|
||||
import { AdminQualityAssuranceTopicsPageResolver } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page-resolver.service';
|
||||
import { AdminQualityAssuranceEventsPageResolver } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.resolver';
|
||||
import { AdminQualityAssuranceSourcePageComponent } from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-page.component';
|
||||
import { AdminQualityAssuranceSourcePageResolver } from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-page-resolver.service';
|
||||
import { QualityAssuranceBreadcrumbResolver } from '../../core/breadcrumbs/quality-assurance-breadcrumb.resolver';
|
||||
import { QualityAssuranceBreadcrumbService } from '../../core/breadcrumbs/quality-assurance-breadcrumb.service';
|
||||
import {
|
||||
SourceDataResolver
|
||||
} from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-data.resolver';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: `${PUBLICATION_CLAIMS_PATH}`,
|
||||
component: AdminNotificationsPublicationClaimPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
suggestionTargetParams: AdminNotificationsPublicationClaimPageResolver
|
||||
},
|
||||
data: {
|
||||
title: 'admin.notifications.publicationclaim.page.title',
|
||||
breadcrumbKey: 'admin.notifications.publicationclaim',
|
||||
showBreadcrumbsFluid: false
|
||||
}
|
||||
},
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId`,
|
||||
component: AdminQualityAssuranceTopicsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: QualityAssuranceBreadcrumbResolver,
|
||||
openaireQualityAssuranceTopicsParams: AdminQualityAssuranceTopicsPageResolver
|
||||
},
|
||||
data: {
|
||||
title: 'admin.quality-assurance.page.title',
|
||||
breadcrumbKey: 'admin.quality-assurance',
|
||||
showBreadcrumbsFluid: false
|
||||
}
|
||||
},
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}`,
|
||||
component: AdminQualityAssuranceSourcePageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: I18nBreadcrumbResolver,
|
||||
openaireQualityAssuranceSourceParams: AdminQualityAssuranceSourcePageResolver,
|
||||
sourceData: SourceDataResolver
|
||||
},
|
||||
data: {
|
||||
title: 'admin.notifications.source.breadcrumbs',
|
||||
breadcrumbKey: 'admin.notifications.source',
|
||||
showBreadcrumbsFluid: false
|
||||
}
|
||||
},
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: `${QUALITY_ASSURANCE_EDIT_PATH}/:sourceId/:topicId`,
|
||||
component: AdminQualityAssuranceEventsPageComponent,
|
||||
pathMatch: 'full',
|
||||
resolve: {
|
||||
breadcrumb: QualityAssuranceBreadcrumbResolver,
|
||||
openaireQualityAssuranceEventsParams: AdminQualityAssuranceEventsPageResolver
|
||||
},
|
||||
data: {
|
||||
title: 'admin.notifications.event.page.title',
|
||||
breadcrumbKey: 'admin.notifications.event',
|
||||
showBreadcrumbsFluid: false
|
||||
}
|
||||
}
|
||||
])
|
||||
],
|
||||
providers: [
|
||||
I18nBreadcrumbResolver,
|
||||
I18nBreadcrumbsService,
|
||||
AdminNotificationsPublicationClaimPageResolver,
|
||||
SourceDataResolver,
|
||||
AdminQualityAssuranceSourcePageResolver,
|
||||
AdminQualityAssuranceTopicsPageResolver,
|
||||
AdminQualityAssuranceEventsPageResolver,
|
||||
AdminQualityAssuranceSourcePageResolver,
|
||||
QualityAssuranceBreadcrumbResolver,
|
||||
QualityAssuranceBreadcrumbService
|
||||
]
|
||||
})
|
||||
/**
|
||||
* Routing module for the Notifications section of the admin sidebar
|
||||
*/
|
||||
export class AdminNotificationsRoutingModule {
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
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 { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component';
|
||||
import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component';
|
||||
import { AdminQualityAssuranceEventsPageComponent } from './admin-quality-assurance-events-page/admin-quality-assurance-events-page.component';
|
||||
import { AdminQualityAssuranceSourcePageComponent } from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-page.component';
|
||||
import { NotificationsModule } from '../../notifications/notifications.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
CoreModule.forRoot(),
|
||||
AdminNotificationsRoutingModule,
|
||||
NotificationsModule
|
||||
],
|
||||
declarations: [
|
||||
AdminNotificationsPublicationClaimPageComponent,
|
||||
AdminQualityAssuranceTopicsPageComponent,
|
||||
AdminQualityAssuranceEventsPageComponent,
|
||||
AdminQualityAssuranceSourcePageComponent
|
||||
],
|
||||
entryComponents: []
|
||||
})
|
||||
/**
|
||||
* This module handles all components related to the notifications pages
|
||||
*/
|
||||
export class AdminNotificationsModule {
|
||||
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
import { PublicationClaimBreadcrumbResolver } from './publication-claim-breadcrumb.resolver';
|
||||
|
||||
describe('PublicationClaimBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: PublicationClaimBreadcrumbResolver;
|
||||
let publicationClaimBreadcrumbService: any;
|
||||
const fullPath = '/test/publication-claim/openaire:6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||
const expectedKey = '6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||
const expectedId = 'openaire:6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||
let route;
|
||||
|
||||
beforeEach(() => {
|
||||
route = {
|
||||
paramMap: {
|
||||
get: function (param) {
|
||||
return this[param];
|
||||
},
|
||||
targetId: expectedId,
|
||||
}
|
||||
};
|
||||
publicationClaimBreadcrumbService = {};
|
||||
resolver = new PublicationClaimBreadcrumbResolver(publicationClaimBreadcrumbService);
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', () => {
|
||||
const resolvedConfig = resolver.resolve(route as any, {url: fullPath } as any);
|
||||
const expectedConfig = { provider: publicationClaimBreadcrumbService, key: expectedKey };
|
||||
expect(resolvedConfig).toEqual(expectedConfig);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import {BreadcrumbConfig} from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { PublicationClaimBreadcrumbService } from './publication-claim-breadcrumb.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PublicationClaimBreadcrumbResolver implements Resolve<BreadcrumbConfig<string>> {
|
||||
constructor(protected breadcrumbService: PublicationClaimBreadcrumbService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that resolve Publication Claim item into a breadcrumb
|
||||
* The parameter are retrieved by the url since part of the Publication Claim route config
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
const targetId = route.paramMap.get('targetId').split(':')[1];
|
||||
return { provider: this.breadcrumbService, key: targetId };
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
import { TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { PublicationClaimBreadcrumbService } from './publication-claim-breadcrumb.service';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
describe('PublicationClaimBreadcrumbService', () => {
|
||||
let service: PublicationClaimBreadcrumbService;
|
||||
let dsoNameService: any = {
|
||||
getName: (str) => str
|
||||
};
|
||||
let translateService: any = {
|
||||
instant: (str) => str,
|
||||
};
|
||||
|
||||
let dataService: any = {
|
||||
findById: (str) => createSuccessfulRemoteDataObject$(str),
|
||||
};
|
||||
|
||||
let authorizationService: any = {
|
||||
isAuthorized: (str) => of(true),
|
||||
};
|
||||
|
||||
let exampleKey;
|
||||
|
||||
const ADMIN_PUBLICATION_CLAIMS_PATH = 'admin/notifications/publication-claim';
|
||||
const ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY = 'admin.notifications.publicationclaim.page.title';
|
||||
|
||||
function init() {
|
||||
exampleKey = 'suggestion.suggestionFor.breadcrumb';
|
||||
}
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
init();
|
||||
TestBed.configureTestingModule({}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
service = new PublicationClaimBreadcrumbService(dataService,dsoNameService,translateService, authorizationService);
|
||||
});
|
||||
|
||||
describe('getBreadcrumbs', () => {
|
||||
it('should return a breadcrumb based on a string', () => {
|
||||
const breadcrumbs = service.getBreadcrumbs(exampleKey);
|
||||
getTestScheduler().expectObservable(breadcrumbs).toBe('(a|)', { a: [new Breadcrumb(ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY, ADMIN_PUBLICATION_CLAIMS_PATH),
|
||||
new Breadcrumb(exampleKey, undefined)]
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,46 @@
|
||||
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
|
||||
import { BreadcrumbsProviderService } from './breadcrumbsProviderService';
|
||||
import { combineLatest, Observable } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ItemDataService } from '../data/item-data.service';
|
||||
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { DSONameService } from './dso-name.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../data/feature-authorization/feature-id';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Service to calculate Publication claims breadcrumbs
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PublicationClaimBreadcrumbService implements BreadcrumbsProviderService<string> {
|
||||
private ADMIN_PUBLICATION_CLAIMS_PATH = 'admin/notifications/publication-claim';
|
||||
private ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY = 'admin.notifications.publicationclaim.page.title';
|
||||
|
||||
constructor(private dataService: ItemDataService,
|
||||
private dsoNameService: DSONameService,
|
||||
private tranlsateService: TranslateService,
|
||||
protected authorizationService: AuthorizationDataService) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method to calculate the breadcrumbs
|
||||
* @param key The key used to resolve the breadcrumb
|
||||
*/
|
||||
getBreadcrumbs(key: string): Observable<Breadcrumb[]> {
|
||||
return combineLatest([this.dataService.findById(key).pipe(getFirstCompletedRemoteData()),this.authorizationService.isAuthorized(FeatureID.AdministratorOf)]).pipe(
|
||||
map(([item, isAdmin]) => {
|
||||
const itemName = this.dsoNameService.getName(item.payload);
|
||||
return isAdmin ? [new Breadcrumb(this.tranlsateService.instant(this.ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY), this.ADMIN_PUBLICATION_CLAIMS_PATH),
|
||||
new Breadcrumb(this.tranlsateService.instant('suggestion.suggestionFor.breadcrumb', {name: itemName}), undefined)] :
|
||||
[new Breadcrumb(this.tranlsateService.instant('suggestion.suggestionFor.breadcrumb', {name: itemName}), undefined)];
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
import { HALLink } from '../../shared/hal-link.model';
|
||||
import { HALResource } from '../../shared/hal-resource.model';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
import { getLinkDefinition, link } from './build-decorators';
|
||||
import { dataService, getDataServiceFor, getLinkDefinition, link } from './build-decorators';
|
||||
|
||||
class TestHALResource implements HALResource {
|
||||
_links: {
|
||||
@@ -46,5 +46,17 @@ describe('build decorators', () => {
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe(`set data service`, () => {
|
||||
it(`should throw error`, () => {
|
||||
expect(dataService(null)).toThrow();
|
||||
});
|
||||
|
||||
it(`should set properly data service for type`, () => {
|
||||
const target = new TestHALResource();
|
||||
dataService(testType)(target);
|
||||
expect(getDataServiceFor(testType)).toEqual(target);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -29,11 +29,11 @@ export const EMBED_SEPARATOR = '%2F';
|
||||
/**
|
||||
* Common functionality for data services.
|
||||
* Specific functionality that not all services would need
|
||||
* is implemented in "DataService feature" classes (e.g. {@link CreateData}
|
||||
* is implemented in "UpdateDataServiceImpl feature" classes (e.g. {@link CreateData}
|
||||
*
|
||||
* All DataService (or DataService feature) classes must
|
||||
* All UpdateDataServiceImpl (or UpdateDataServiceImpl feature) classes must
|
||||
* - extend this class (or {@link IdentifiableDataService})
|
||||
* - implement any DataService features it requires in order to forward calls to it
|
||||
* - implement any UpdateDataServiceImpl features it requires in order to forward calls to it
|
||||
*
|
||||
* ```
|
||||
* export class SomeDataService extends BaseDataService<Something> implements CreateData<Something>, SearchData<Something> {
|
||||
@@ -385,7 +385,7 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
|
||||
|
||||
/**
|
||||
* Return the links to traverse from the root of the api to the
|
||||
* endpoint this DataService represents
|
||||
* endpoint this UpdateDataServiceImpl represents
|
||||
*
|
||||
* e.g. if the api root links to 'foo', and the endpoint at 'foo'
|
||||
* links to 'bar' the linkPath for the BarDataService would be
|
||||
|
@@ -37,7 +37,7 @@ export interface CreateData<T extends CacheableObject> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A DataService feature to create objects.
|
||||
* A UpdateDataServiceImpl feature to create objects.
|
||||
*
|
||||
* Concrete data services can use this feature by implementing {@link CreateData}
|
||||
* and delegating its method to an inner instance of this class.
|
||||
|
@@ -42,7 +42,7 @@ export interface FindAllData<T extends CacheableObject> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A DataService feature to list all objects.
|
||||
* A UpdateDataServiceImpl feature to list all objects.
|
||||
*
|
||||
* Concrete data services can use this feature by implementing {@link FindAllData}
|
||||
* and delegating its method to an inner instance of this class.
|
||||
|
@@ -54,7 +54,7 @@ export interface PatchData<T extends CacheableObject> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A DataService feature to patch and update objects.
|
||||
* A UpdateDataServiceImpl feature to patch and update objects.
|
||||
*
|
||||
* Concrete data services can use this feature by implementing {@link PatchData}
|
||||
* and delegating its method to an inner instance of this class.
|
||||
|
@@ -31,7 +31,7 @@ export interface PutData<T extends CacheableObject> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A DataService feature to send PUT requests.
|
||||
* A UpdateDataServiceImpl feature to send PUT requests.
|
||||
*
|
||||
* Concrete data services can use this feature by implementing {@link PutData}
|
||||
* and delegating its method to an inner instance of this class.
|
||||
|
@@ -51,7 +51,7 @@ export interface SearchData<T extends CacheableObject> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A DataService feature to search for objects.
|
||||
* A UpdateDataServiceImpl feature to search for objects.
|
||||
*
|
||||
* Concrete data services can use this feature by implementing {@link SearchData}
|
||||
* and delegating its method to an inner instance of this class.
|
||||
|
@@ -17,8 +17,8 @@ import { HALDataService } from './base/hal-data-service.interface';
|
||||
import { dataService } from './base/data-service.decorator';
|
||||
|
||||
/**
|
||||
* A DataService with only findByHref methods. Its purpose is to be used for resources that don't
|
||||
* need to be retrieved by ID, or have any way to update them, but require a DataService in order
|
||||
* A UpdateDataServiceImpl with only findByHref methods. Its purpose is to be used for resources that don't
|
||||
* need to be retrieved by ID, or have any way to update them, but require a UpdateDataServiceImpl in order
|
||||
* for their links to be resolved by the LinkService.
|
||||
*
|
||||
* an @dataService annotation can be added for any number of these resource types
|
||||
|
144
src/app/core/data/update-data.service.spec.ts
Normal file
144
src/app/core/data/update-data.service.spec.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { RequestService } from './request.service';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { HrefOnlyDataService } from './href-only-data.service';
|
||||
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
||||
import { RestResponse } from '../cache/response.models';
|
||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { Version } from '../shared/version.model';
|
||||
import { VersionHistory } from '../shared/version-history.model';
|
||||
import { RequestEntry } from './request-entry.model';
|
||||
import { testPatchDataImplementation } from './base/patch-data.spec';
|
||||
import { UpdateDataServiceImpl } from './update-data.service';
|
||||
import { testSearchDataImplementation } from './base/search-data.spec';
|
||||
import { testDeleteDataImplementation } from './base/delete-data.spec';
|
||||
import { testCreateDataImplementation } from './base/create-data.spec';
|
||||
import { testFindAllDataImplementation } from './base/find-all-data.spec';
|
||||
import { testPutDataImplementation } from './base/put-data.spec';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
|
||||
describe('VersionDataService test', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: UpdateDataServiceImpl<any>;
|
||||
let requestService: RequestService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: HALEndpointService;
|
||||
let hrefOnlyDataService: HrefOnlyDataService;
|
||||
let responseCacheEntry: RequestEntry;
|
||||
|
||||
const notificationsService = {} as NotificationsService;
|
||||
|
||||
const item = Object.assign(new Item(), {
|
||||
id: '1234-1234',
|
||||
uuid: '1234-1234',
|
||||
bundles: observableOf({}),
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{
|
||||
language: 'en_US',
|
||||
value: 'This is just another title'
|
||||
}
|
||||
],
|
||||
'dc.type': [
|
||||
{
|
||||
language: null,
|
||||
value: 'Article'
|
||||
}
|
||||
],
|
||||
'dc.contributor.author': [
|
||||
{
|
||||
language: 'en_US',
|
||||
value: 'Smith, Donald'
|
||||
}
|
||||
],
|
||||
'dc.date.issued': [
|
||||
{
|
||||
language: null,
|
||||
value: '2015-06-26'
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const versionHistory = Object.assign(new VersionHistory(), {
|
||||
id: '1',
|
||||
draftVersion: true,
|
||||
});
|
||||
|
||||
const mockVersion: Version = Object.assign(new Version(), {
|
||||
item: createSuccessfulRemoteDataObject$(item),
|
||||
versionhistory: createSuccessfulRemoteDataObject$(versionHistory),
|
||||
version: 1,
|
||||
});
|
||||
const mockVersionRD = createSuccessfulRemoteDataObject(mockVersion);
|
||||
|
||||
const endpointURL = `https://rest.api/rest/api/versioning/versions`;
|
||||
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||
|
||||
objectCache = {} as ObjectCacheService;
|
||||
const comparatorEntry = {} as any;
|
||||
function initTestService() {
|
||||
hrefOnlyDataService = getMockHrefOnlyDataService();
|
||||
return new UpdateDataServiceImpl(
|
||||
'testLinkPath',
|
||||
requestService,
|
||||
rdbService,
|
||||
objectCache,
|
||||
halService,
|
||||
notificationsService,
|
||||
comparatorEntry,
|
||||
10 * 1000
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: cold('a', { a: endpointURL })
|
||||
});
|
||||
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: observableOf(responseCacheEntry),
|
||||
getByUUID: observableOf(responseCacheEntry),
|
||||
});
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: hot('(a|)', {
|
||||
a: mockVersionRD
|
||||
})
|
||||
});
|
||||
|
||||
service = initTestService();
|
||||
|
||||
spyOn((service as any), 'findById').and.callThrough();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
service = null;
|
||||
});
|
||||
|
||||
describe('composition', () => {
|
||||
const initService = () => new UpdateDataServiceImpl(null, null, null, null, null, null, null, null);
|
||||
|
||||
testPatchDataImplementation(initService);
|
||||
testSearchDataImplementation(initService);
|
||||
testDeleteDataImplementation(initService);
|
||||
testCreateDataImplementation(initService);
|
||||
testFindAllDataImplementation(initService);
|
||||
testPutDataImplementation(initService);
|
||||
});
|
||||
|
||||
});
|
@@ -1,13 +1,317 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { RestRequestMethod } from './rest-request-method';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { AsyncSubject, from as observableFrom, Observable } from 'rxjs';
|
||||
import {
|
||||
find,
|
||||
map,
|
||||
mergeMap,
|
||||
switchMap,
|
||||
take,
|
||||
toArray
|
||||
} from 'rxjs/operators';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { RequestParam } from '../cache/models/request-param.model';
|
||||
import { ObjectCacheEntry } from '../cache/object-cache.reducer';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { ChangeAnalyzer } from './change-analyzer';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import {
|
||||
DeleteByIDRequest,
|
||||
PostRequest
|
||||
} from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { RestRequestMethod } from './rest-request-method';
|
||||
import { NoContent } from '../shared/NoContent.model';
|
||||
import { CacheableObject } from '../cache/cacheable-object.model';
|
||||
import { FindListOptions } from './find-list-options.model';
|
||||
import { FindAllData, FindAllDataImpl } from './base/find-all-data';
|
||||
import { SearchData, SearchDataImpl } from './base/search-data';
|
||||
import { CreateData, CreateDataImpl } from './base/create-data';
|
||||
import { PatchData, PatchDataImpl } from './base/patch-data';
|
||||
import { IdentifiableDataService } from './base/identifiable-data.service';
|
||||
import { PutData, PutDataImpl } from './base/put-data';
|
||||
import { DeleteData, DeleteDataImpl } from './base/delete-data';
|
||||
|
||||
|
||||
/**
|
||||
* Represents a data service to update a given object
|
||||
* Interface to list the methods used by the injected service in components
|
||||
*/
|
||||
export interface UpdateDataService<T> {
|
||||
patch(dso: T, operations: Operation[]): Observable<RemoteData<T>>;
|
||||
update(object: T): Observable<RemoteData<T>>;
|
||||
commitUpdates(method?: RestRequestMethod);
|
||||
commitUpdates(method?: RestRequestMethod): void;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specific functionalities that not all services would need.
|
||||
* Goal of the class is to update remote objects, handling custom methods that don't belong to BaseDataService
|
||||
* The class implements also the following common interfaces
|
||||
*
|
||||
* findAllData: FindAllData<T>;
|
||||
* searchData: SearchData<T>;
|
||||
* createData: CreateData<T>;
|
||||
* patchData: PatchData<T>;
|
||||
* putData: PutData<T>;
|
||||
* deleteData: DeleteData<T>;
|
||||
*
|
||||
* Custom methods are:
|
||||
*
|
||||
* deleteOnRelated - delete all related objects to the given one
|
||||
* postOnRelated - post all the related objects to the given one
|
||||
* invalidate - invalidate the DSpaceObject making all requests as stale
|
||||
* invalidateByHref - invalidate the href making all requests as stale
|
||||
*/
|
||||
|
||||
export class UpdateDataServiceImpl<T extends CacheableObject> extends IdentifiableDataService<T> implements FindAllData<T>, SearchData<T>, CreateData<T>, PatchData<T>, PutData<T>, DeleteData<T> {
|
||||
private findAllData: FindAllDataImpl<T>;
|
||||
private searchData: SearchDataImpl<T>;
|
||||
private createData: CreateDataImpl<T>;
|
||||
private patchData: PatchDataImpl<T>;
|
||||
private putData: PutDataImpl<T>;
|
||||
private deleteData: DeleteDataImpl<T>;
|
||||
|
||||
|
||||
constructor(
|
||||
protected linkPath: string,
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected comparator: ChangeAnalyzer<T>,
|
||||
protected responseMsToLive: number,
|
||||
) {
|
||||
super(linkPath, requestService, rdbService, objectCache, halService, responseMsToLive);
|
||||
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||
this.createData = new CreateDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService ,this.responseMsToLive);
|
||||
this.patchData = new PatchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, comparator ,this.responseMsToLive, this.constructIdEndpoint);
|
||||
this.putData = new PutDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||
this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService ,this.responseMsToLive, this.constructIdEndpoint);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the HREF with given options object
|
||||
*
|
||||
* @param options The [[FindListOptions]] object
|
||||
* @param linkPath The link path for the object
|
||||
* @return {Observable<string>}
|
||||
* Return an observable that emits created HREF
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
public getFindAllHref(options: FindListOptions = {}, linkPath?: string, ...linksToFollow: FollowLinkConfig<T>[]): Observable<string> {
|
||||
return this.findAllData.getFindAllHref(options, linkPath, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the HREF for a specific object's search method with given options object
|
||||
*
|
||||
* @param searchMethod The search method for the object
|
||||
* @param options The [[FindListOptions]] object
|
||||
* @return {Observable<string>}
|
||||
* Return an observable that emits created HREF
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
public getSearchByHref(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<T>[]): Observable<string> {
|
||||
return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link RemoteData} of all object with a list of {@link FollowLinkConfig}, to indicate which embedded
|
||||
* info should be added to the objects
|
||||
*
|
||||
* @param options Find list options object
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<T>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new FindListRequest with given search method
|
||||
*
|
||||
* @param searchMethod The search method for the object
|
||||
* @param options The [[FindListOptions]] object
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<T>>}
|
||||
* Return an observable that emits response from the server
|
||||
*/
|
||||
searchBy(searchMethod: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a patch request for a specified object
|
||||
* @param {T} object The object to send a patch request for
|
||||
* @param {Operation[]} operations The patch operations to be performed
|
||||
*/
|
||||
patch(object: T, operations: Operation[]): Observable<RemoteData<T>> {
|
||||
return this.patchData.patch(object, operations);
|
||||
}
|
||||
|
||||
createPatchFromCache(object: T): Observable<Operation[]> {
|
||||
return this.patchData.createPatchFromCache(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a PUT request for the specified object
|
||||
*
|
||||
* @param object The object to send a put request for.
|
||||
*/
|
||||
put(object: T): Observable<RemoteData<T>> {
|
||||
return this.putData.put(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new patch to the object cache
|
||||
* The patch is derived from the differences between the given object and its version in the object cache
|
||||
* @param {DSpaceObject} object The given object
|
||||
*/
|
||||
update(object: T): Observable<RemoteData<T>> {
|
||||
return this.patchData.update(object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DSpaceObject on the server, and store the response
|
||||
* in the object cache
|
||||
*
|
||||
* @param {CacheableObject} object
|
||||
* The object to create
|
||||
* @param {RequestParam[]} params
|
||||
* Array with additional params to combine with query string
|
||||
*/
|
||||
create(object: T, ...params: RequestParam[]): Observable<RemoteData<T>> {
|
||||
return this.createData.create(object, ...params);
|
||||
}
|
||||
|
||||
/**
|
||||
<<<<<<< HEAD
|
||||
* 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* Invalidate an existing DSpaceObject by marking all requests it is included in as stale
|
||||
* @param objectId The id of the object to be invalidated
|
||||
* @return An Observable that will emit `true` once all requests are stale
|
||||
*/
|
||||
invalidate(objectId: string): Observable<boolean> {
|
||||
return this.getIDHrefObs(objectId).pipe(
|
||||
switchMap((href: string) => this.invalidateByHref(href))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate an existing DSpaceObject by marking all requests it is included in as stale
|
||||
* @param href The self link of the object to be invalidated
|
||||
* @return An Observable that will emit `true` once all requests are stale
|
||||
*/
|
||||
invalidateByHref(href: string): Observable<boolean> {
|
||||
const done$ = new AsyncSubject<boolean>();
|
||||
|
||||
this.objectCache.getByHref(href).pipe(
|
||||
switchMap((oce: ObjectCacheEntry) => observableFrom(oce.requestUUIDs).pipe(
|
||||
mergeMap((requestUUID: string) => this.requestService.setStaleByUUID(requestUUID)),
|
||||
toArray(),
|
||||
)),
|
||||
).subscribe(() => {
|
||||
done$.next(true);
|
||||
done$.complete();
|
||||
});
|
||||
|
||||
return done$;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing DSpace Object on the server
|
||||
* @param objectId The id of the object to be removed
|
||||
* @param copyVirtualMetadata (optional parameter) the identifiers of the relationship types for which the virtual
|
||||
* metadata should be saved as real metadata
|
||||
* @return A RemoteData observable with an empty payload, but still representing the state of the request: statusCode,
|
||||
* errorMessage, timeCompleted, etc
|
||||
*/
|
||||
delete(objectId: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
||||
return this.deleteData.delete(objectId, copyVirtualMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing DSpace Object on the server
|
||||
* @param href The self link of the object to be removed
|
||||
* @param copyVirtualMetadata (optional parameter) the identifiers of the relationship types for which the virtual
|
||||
* metadata should be saved as real metadata
|
||||
* @return A RemoteData observable with an empty payload, but still representing the state of the request: statusCode,
|
||||
* errorMessage, timeCompleted, etc
|
||||
* Only emits once all request related to the DSO has been invalidated.
|
||||
*/
|
||||
deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
||||
return this.deleteData.deleteByHref(href, copyVirtualMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit current object changes to the server
|
||||
* @param method The RestRequestMethod for which de server sync buffer should be committed
|
||||
*/
|
||||
commitUpdates(method?: RestRequestMethod) {
|
||||
this.patchData.commitUpdates(method);
|
||||
}
|
||||
}
|
||||
|
@@ -128,7 +128,7 @@ describe('VersionDataService test', () => {
|
||||
});
|
||||
|
||||
describe('getHistoryFromVersion', () => {
|
||||
it('should proxy the call to DataService.findByHref', () => {
|
||||
it('should proxy the call to UpdateDataServiceImpl.findByHref', () => {
|
||||
scheduler.schedule(() => service.getHistoryFromVersion(mockVersion, true, true));
|
||||
scheduler.flush();
|
||||
|
||||
|
@@ -315,7 +315,7 @@ describe('EPersonDataService', () => {
|
||||
service.deleteEPerson(EPersonMock).subscribe();
|
||||
});
|
||||
|
||||
it('should call DataService.delete with the EPerson\'s UUID', () => {
|
||||
it('should call UpdateDataServiceImpl.delete with the EPerson\'s UUID', () => {
|
||||
expect(service.delete).toHaveBeenCalledWith(EPersonMock.id);
|
||||
});
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ResourceType } from '../../../shared/resource-type';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for the Suggestion object
|
@@ -1,4 +1,4 @@
|
||||
import { ResourceType } from '../../../shared/resource-type';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for the Suggestion Source object
|
@@ -1,11 +1,11 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
|
||||
import { SUGGESTION_SOURCE } from './suggestion-source-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';
|
||||
import {CacheableObject} from '../../../cache/cacheable-object.model';
|
||||
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';
|
||||
import {CacheableObject} from '../../cache/cacheable-object.model';
|
||||
|
||||
/**
|
||||
* The interface representing the Suggestion Source model
|
@@ -1,4 +1,4 @@
|
||||
import { ResourceType } from '../../../shared/resource-type';
|
||||
import { ResourceType } from '../../shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for the Suggestion Target object
|
@@ -1,12 +1,12 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
|
||||
|
||||
import { CacheableObject } from '../../../cache/cacheable-object.model';
|
||||
import { CacheableObject } from '../../cache/cacheable-object.model';
|
||||
import { SUGGESTION_TARGET } from './suggestion-target-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';
|
||||
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 Suggestion Target model
|
@@ -1,13 +1,16 @@
|
||||
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
|
||||
|
||||
import { SUGGESTION } from './suggestion-objects.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';
|
||||
import { MetadataMap, MetadataMapSerializer } from '../../../shared/metadata.models';
|
||||
import {CacheableObject} from '../../../cache/cacheable-object.model';
|
||||
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';
|
||||
import { MetadataMap, MetadataMapSerializer } from '../../shared/metadata.models';
|
||||
import {CacheableObject} from '../../cache/cacheable-object.model';
|
||||
|
||||
/**
|
||||
* The interface representing Suggestion Evidences such as scores (authorScore, datescore)
|
||||
*/
|
||||
export interface SuggestionEvidences {
|
||||
[sectionId: string]: {
|
||||
score: string;
|
@@ -1,120 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { dataService } from '../../../data/base/data-service.decorator';
|
||||
|
||||
import { IdentifiableDataService } from '../../../data/base/identifiable-data.service';
|
||||
import { SuggestionTarget } from '../models/suggestion-target.model';
|
||||
import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { RequestService } from '../../../data/request.service';
|
||||
import { RemoteDataBuildService } from '../../../cache/builders/remote-data-build.service';
|
||||
import { CoreState } from '../../../core-state.model';
|
||||
import { ObjectCacheService } from '../../../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../../../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FindListOptions } from '../../../data/find-list-options.model';
|
||||
import { FollowLinkConfig } from '../../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../../data/paginated-list.model';
|
||||
import { RemoteData } from '../../../data/remote-data';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RequestParam } from '../../../cache/models/request-param.model';
|
||||
import { SearchData, SearchDataImpl } from '../../../data/base/search-data';
|
||||
import { DefaultChangeAnalyzer } from '../../../data/default-change-analyzer.service';
|
||||
import { SUGGESTION_TARGET } from '../models/suggestion-target-object.resource-type';
|
||||
|
||||
@Injectable()
|
||||
@dataService(SUGGESTION_TARGET)
|
||||
export class SuggestionTargetDataService extends IdentifiableDataService<SuggestionTarget> {
|
||||
|
||||
protected linkPath = 'suggestiontargets';
|
||||
private findAllData: FindAllData<SuggestionTarget>;
|
||||
private searchBy: SearchData<SuggestionTarget>;
|
||||
protected searchFindBySourceMethod = 'findBySource';
|
||||
protected searchFindByTargetMethod = 'findByTarget';
|
||||
protected searchFindByTargetAndSourceMethod = 'findByTargetAndSource';
|
||||
|
||||
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: DefaultChangeAnalyzer<SuggestionTarget>) {
|
||||
super('suggestiontargets', requestService, rdbService, objectCache, halService);
|
||||
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||
this.searchBy = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||
}
|
||||
/**
|
||||
* Return the list of Suggestion Target for a given source
|
||||
*
|
||||
* @param source
|
||||
* The source for which to find targets.
|
||||
* @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<SuggestionTarget>>>
|
||||
* The list of Suggestion Target.
|
||||
*/
|
||||
public getTargets(
|
||||
source: string,
|
||||
options: FindListOptions = {},
|
||||
...linksToFollow: FollowLinkConfig<SuggestionTarget>[]
|
||||
): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
|
||||
options.searchParams = [new RequestParam('source', source)];
|
||||
|
||||
return this.searchBy.searchBy(this.searchFindBySourceMethod, options, true, true, ...linksToFollow);
|
||||
}
|
||||
/**
|
||||
* Return a single Suggestion target.
|
||||
*
|
||||
* @param id The Suggestion Target id
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved.
|
||||
*
|
||||
* @return Observable<RemoteData<QualityAssuranceSourceObject>> The Quality Assurance source.
|
||||
*/
|
||||
public getTarget(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SuggestionTarget>[]): Observable<RemoteData<SuggestionTarget>> {
|
||||
return this.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of Suggestion Target for a given user
|
||||
*
|
||||
* @param userId
|
||||
* The user Id for which to find targets.
|
||||
* @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<SuggestionTarget>>>
|
||||
* The list of Suggestion Target.
|
||||
*/
|
||||
public getTargetsByUser(
|
||||
userId: string,
|
||||
options: FindListOptions = {},
|
||||
...linksToFollow: FollowLinkConfig<SuggestionTarget>[]
|
||||
): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
|
||||
options.searchParams = [new RequestParam('target', userId)];
|
||||
|
||||
return this.searchBy.searchBy(this.searchFindByTargetMethod, options, true, true, ...linksToFollow);
|
||||
}
|
||||
/**
|
||||
* Return a Suggestion Target for a given id
|
||||
*
|
||||
* @param targetId
|
||||
* The target id to retrieve.
|
||||
*
|
||||
* @return Observable<RemoteData<SuggestionTarget>>
|
||||
* The list of Suggestion Target.
|
||||
*/
|
||||
public getTargetById(targetId: string): Observable<RemoteData<SuggestionTarget>> {
|
||||
return this.findById(targetId);
|
||||
}
|
||||
|
||||
}
|
@@ -1,24 +1,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { dataService } from '../../../data/base/data-service.decorator';
|
||||
import { dataService } from '../../data/base/data-service.decorator';
|
||||
import { SUGGESTION_SOURCE } from '../models/suggestion-source-object.resource-type';
|
||||
import { IdentifiableDataService } from '../../../data/base/identifiable-data.service';
|
||||
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
|
||||
import { SuggestionSource } from '../models/suggestion-source.model';
|
||||
import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data';
|
||||
import { FindAllData, FindAllDataImpl } from '../../data/base/find-all-data';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { RequestService } from '../../../data/request.service';
|
||||
import { RemoteDataBuildService } from '../../../cache/builders/remote-data-build.service';
|
||||
import { CoreState } from '../../../core-state.model';
|
||||
import { ObjectCacheService } from '../../../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../../../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { CoreState } from '../../core-state.model';
|
||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FindListOptions } from '../../../data/find-list-options.model';
|
||||
import { FollowLinkConfig } from '../../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../../data/paginated-list.model';
|
||||
import { RemoteData } from '../../../data/remote-data';
|
||||
import { FindListOptions } from '../../data/find-list-options.model';
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../data/paginated-list.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { DefaultChangeAnalyzer } from '../../../data/default-change-analyzer.service';
|
||||
import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service';
|
||||
|
||||
/**
|
||||
* Service that retrieves Suggestion Source data
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(SUGGESTION_SOURCE)
|
||||
export class SuggestionSourceDataService extends IdentifiableDataService<SuggestionSource> {
|
||||
@@ -53,7 +56,7 @@ export class SuggestionSourceDataService extends IdentifiableDataService<Suggest
|
||||
* The list of Quality Assurance source.
|
||||
*/
|
||||
public getSources(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SuggestionSource>[]): Observable<RemoteData<PaginatedList<SuggestionSource>>> {
|
||||
return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
return this.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,4 +74,23 @@ export class SuggestionSourceDataService extends IdentifiableDataService<Suggest
|
||||
public getSource(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SuggestionSource>[]): Observable<RemoteData<SuggestionSource>> {
|
||||
return this.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@link RemoteData} of all object with a list of {@link FollowLinkConfig}, to indicate which embedded
|
||||
* info should be added to the objects
|
||||
*
|
||||
* @param options Find list options object
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<T>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SuggestionSource>[]): Observable<RemoteData<PaginatedList<SuggestionSource>>> {
|
||||
return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||
import { RequestEntry } from '../../data/request-entry.model';
|
||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||
import { RestResponse } from '../../cache/response.models';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../../core-state.model';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service';
|
||||
import { testFindAllDataImplementation } from '../../data/base/find-all-data.spec';
|
||||
import { FindAllData } from '../../data/base/find-all-data';
|
||||
import { GetRequest } from '../../data/request.models';
|
||||
import {
|
||||
createSuccessfulRemoteDataObject$
|
||||
} from '../../../shared/remote-data.utils';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { RequestEntryState } from '../../data/request-entry-state.model';
|
||||
import { SuggestionSourceDataService } from './suggestion-source-data.service';
|
||||
import { SuggestionSource } from '../models/suggestion-source.model';
|
||||
|
||||
describe('SuggestionSourceDataService test', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: SuggestionSourceDataService;
|
||||
let requestService: RequestService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: HALEndpointService;
|
||||
let notificationsService: NotificationsService;
|
||||
let http: HttpClient;
|
||||
let comparator: DefaultChangeAnalyzer<SuggestionSource>;
|
||||
let responseCacheEntry: RequestEntry;
|
||||
|
||||
const store = {} as Store<CoreState>;
|
||||
const endpointURL = `https://rest.api/rest/api/suggestionsources`;
|
||||
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||
|
||||
const remoteDataMocks = {
|
||||
Success: new RemoteData(null, null, null, RequestEntryState.Success, null, null, 200),
|
||||
};
|
||||
|
||||
function initTestService() {
|
||||
return new SuggestionSourceDataService(
|
||||
requestService,
|
||||
rdbService,
|
||||
store,
|
||||
objectCache,
|
||||
halService,
|
||||
notificationsService,
|
||||
http,
|
||||
comparator
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
objectCache = {} as ObjectCacheService;
|
||||
http = {} as HttpClient;
|
||||
notificationsService = {} as NotificationsService;
|
||||
comparator = {} as DefaultChangeAnalyzer<SuggestionSource>;
|
||||
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: observableOf(responseCacheEntry),
|
||||
getByUUID: observableOf(responseCacheEntry),
|
||||
});
|
||||
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: observableOf(endpointURL)
|
||||
});
|
||||
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: createSuccessfulRemoteDataObject$({}, 500),
|
||||
buildList: cold('a', { a: remoteDataMocks.Success })
|
||||
});
|
||||
|
||||
|
||||
service = initTestService();
|
||||
});
|
||||
|
||||
describe('composition', () => {
|
||||
const initFindAllService = () => new SuggestionSourceDataService(null, null, null, null, null, null, null, null) as unknown as FindAllData<any>;
|
||||
testFindAllDataImplementation(initFindAllService);
|
||||
});
|
||||
|
||||
describe('getSources', () => {
|
||||
it('should send a new GetRequest', () => {
|
||||
const expected = new GetRequest(requestService.generateRequestId(), `${endpointURL}`);
|
||||
scheduler.schedule(() => service.getSources().subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSource', () => {
|
||||
it('should send a new GetRequest', () => {
|
||||
const expected = new GetRequest(requestService.generateRequestId(), `${endpointURL}/testId`);
|
||||
scheduler.schedule(() => service.getSource('testId').subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||
});
|
||||
});
|
||||
});
|
173
src/app/core/notifications/suggestion-data.service.spec.ts
Normal file
173
src/app/core/notifications/suggestion-data.service.spec.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { SuggestionDataServiceImpl, SuggestionsDataService } from './suggestions-data.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
|
||||
import { Suggestion } from './models/suggestion.model';
|
||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||
import { RequestEntry } from '../data/request-entry.model';
|
||||
import { RestResponse } from '../cache/response.models';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { RequestEntryState } from '../data/request-entry-state.model';
|
||||
import { SuggestionSource } from './models/suggestion-source.model';
|
||||
import { SuggestionTarget } from './models/suggestion-target.model';
|
||||
import { SuggestionSourceDataService } from './source/suggestion-source-data.service';
|
||||
import { SuggestionTargetDataService } from './target/suggestion-target-data.service';
|
||||
import { RequestParam } from '../cache/models/request-param.model';
|
||||
|
||||
describe('SuggestionDataService test', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: SuggestionsDataService;
|
||||
let requestService: RequestService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: HALEndpointService;
|
||||
let notificationsService: NotificationsService;
|
||||
let http: HttpClient;
|
||||
let comparatorSuggestion: DefaultChangeAnalyzer<Suggestion>;
|
||||
let comparatorSuggestionSource: DefaultChangeAnalyzer<SuggestionSource>;
|
||||
let comparatorSuggestionTarget: DefaultChangeAnalyzer<SuggestionTarget>;
|
||||
let suggestionSourcesDataService: SuggestionSourceDataService;
|
||||
let suggestionTargetsDataService: SuggestionTargetDataService;
|
||||
let suggestionsDataService: SuggestionDataServiceImpl;
|
||||
let responseCacheEntry: RequestEntry;
|
||||
|
||||
|
||||
const testSource = 'test-source';
|
||||
const testUserId = '1234-4321';
|
||||
const endpointURL = `https://rest.api/rest/api/`;
|
||||
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||
const remoteDataMocks = {
|
||||
Success: new RemoteData(null, null, null, RequestEntryState.Success, null, null, 200),
|
||||
};
|
||||
|
||||
function initTestService() {
|
||||
return new SuggestionsDataService(
|
||||
requestService,
|
||||
rdbService,
|
||||
objectCache,
|
||||
halService,
|
||||
notificationsService,
|
||||
http,
|
||||
comparatorSuggestion,
|
||||
comparatorSuggestionSource,
|
||||
comparatorSuggestionTarget
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
objectCache = {} as ObjectCacheService;
|
||||
http = {} as HttpClient;
|
||||
notificationsService = {} as NotificationsService;
|
||||
comparatorSuggestion = {} as DefaultChangeAnalyzer<Suggestion>;
|
||||
comparatorSuggestionTarget = {} as DefaultChangeAnalyzer<SuggestionTarget>;
|
||||
comparatorSuggestionSource = {} as DefaultChangeAnalyzer<SuggestionSource>;
|
||||
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: observableOf(responseCacheEntry),
|
||||
getByUUID: observableOf(responseCacheEntry),
|
||||
setStaleByHrefSubstring: observableOf(true)
|
||||
});
|
||||
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: observableOf(endpointURL)
|
||||
});
|
||||
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: createSuccessfulRemoteDataObject$({}, 500),
|
||||
buildList: cold('a', { a: remoteDataMocks.Success })
|
||||
});
|
||||
|
||||
|
||||
suggestionSourcesDataService = jasmine.createSpyObj('suggestionSourcesDataService', {
|
||||
getSources: observableOf(null),
|
||||
});
|
||||
|
||||
suggestionTargetsDataService = jasmine.createSpyObj('suggestionTargetsDataService', {
|
||||
getTargets: observableOf(null),
|
||||
getTargetsByUser: observableOf(null),
|
||||
findById: observableOf(null),
|
||||
});
|
||||
|
||||
suggestionsDataService = jasmine.createSpyObj('suggestionsDataService', {
|
||||
searchBy: observableOf(null),
|
||||
delete: observableOf(null),
|
||||
});
|
||||
|
||||
|
||||
service = initTestService();
|
||||
/* eslint-disable-next-line @typescript-eslint/dot-notation */
|
||||
service['suggestionSourcesDataService'] = suggestionSourcesDataService;
|
||||
/* eslint-disable-next-line @typescript-eslint/dot-notation */
|
||||
service['suggestionTargetsDataService'] = suggestionTargetsDataService;
|
||||
/* eslint-disable-next-line @typescript-eslint/dot-notation */
|
||||
service['suggestionsDataService'] = suggestionsDataService;
|
||||
});
|
||||
|
||||
describe('Suggestion targets service', () => {
|
||||
it('should call suggestionSourcesDataService.getTargets', () => {
|
||||
const options = {
|
||||
searchParams: [new RequestParam('source', testSource)]
|
||||
};
|
||||
service.getTargets(testSource);
|
||||
expect(suggestionTargetsDataService.getTargets).toHaveBeenCalledWith('findBySource', options);
|
||||
});
|
||||
|
||||
it('should call suggestionSourcesDataService.getTargetsByUser', () => {
|
||||
const options = {
|
||||
searchParams: [new RequestParam('target', testUserId)]
|
||||
};
|
||||
service.getTargetsByUser(testUserId);
|
||||
expect(suggestionTargetsDataService.getTargetsByUser).toHaveBeenCalledWith(testUserId, options);
|
||||
});
|
||||
|
||||
it('should call suggestionSourcesDataService.getTargetById', () => {
|
||||
service.getTargetById('1');
|
||||
expect(suggestionTargetsDataService.findById).toHaveBeenCalledWith('1');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Suggestion sources service', () => {
|
||||
it('should call suggestionSourcesDataService.getSources', () => {
|
||||
service.getSources();
|
||||
expect(suggestionSourcesDataService.getSources).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Suggestion service', () => {
|
||||
it('should call suggestionsDataService.searchBy', () => {
|
||||
const options = {
|
||||
searchParams: [new RequestParam('target', testUserId), new RequestParam('source', testSource)]
|
||||
};
|
||||
service.getSuggestionsByTargetAndSource(testUserId, testSource);
|
||||
expect(suggestionsDataService.searchBy).toHaveBeenCalledWith('findByTargetAndSource', options, false, true);
|
||||
});
|
||||
|
||||
it('should call suggestionsDataService.delete', () => {
|
||||
service.deleteSuggestion('1');
|
||||
expect(suggestionsDataService.delete).toHaveBeenCalledWith('1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Request service', () => {
|
||||
it('should call requestService.setStaleByHrefSubstring', () => {
|
||||
service.clearSuggestionRequests();
|
||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@@ -5,39 +5,35 @@ import { Store } from '@ngrx/store';
|
||||
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
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 { 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 { 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 { UpdateDataServiceImpl } from '../data/update-data.service';
|
||||
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { SUGGESTION } from './models/suggestion-objects.resource-type';
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../data/paginated-list.model';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../data/paginated-list.model';
|
||||
import { SuggestionSource } from './models/suggestion-source.model';
|
||||
import { SuggestionTarget } from './models/suggestion-target.model';
|
||||
import { Suggestion } from './models/suggestion.model';
|
||||
import { RequestParam } from '../../cache/models/request-param.model';
|
||||
import { NoContent } from '../../shared/NoContent.model';
|
||||
import {CoreState} from '../../core-state.model';
|
||||
import {FindListOptions} from '../../data/find-list-options.model';
|
||||
import { RequestParam } from '../cache/models/request-param.model';
|
||||
import { NoContent } from '../shared/NoContent.model';
|
||||
import {CoreState} from '../core-state.model';
|
||||
import {FindListOptions} from '../data/find-list-options.model';
|
||||
import { SuggestionSourceDataService } from './source/suggestion-source-data.service';
|
||||
import { SuggestionTargetDataService } from './target/suggestion-target-data.service';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
* A private UpdateDataServiceImpl implementation to delegate specific methods to.
|
||||
*/
|
||||
class SuggestionDataServiceImpl extends DataService<Suggestion> {
|
||||
/**
|
||||
* The REST endpoint.
|
||||
*/
|
||||
protected linkPath = 'suggestions';
|
||||
export class SuggestionDataServiceImpl extends UpdateDataServiceImpl<Suggestion> {
|
||||
|
||||
/**
|
||||
* Initialize service variables
|
||||
@@ -49,6 +45,7 @@ class SuggestionDataServiceImpl extends DataService<Suggestion> {
|
||||
* @param {NotificationsService} notificationsService
|
||||
* @param {HttpClient} http
|
||||
* @param {ChangeAnalyzer<Suggestion>} comparator
|
||||
* @param responseMsToLive
|
||||
*/
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
@@ -58,8 +55,10 @@ class SuggestionDataServiceImpl extends DataService<Suggestion> {
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: ChangeAnalyzer<Suggestion>) {
|
||||
super();
|
||||
protected comparator: ChangeAnalyzer<Suggestion>,
|
||||
protected responseMsToLive: number,
|
||||
) {
|
||||
super('suggestions', requestService, rdbService, objectCache, halService, notificationsService, comparator ,responseMsToLive);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,24 +69,25 @@ class SuggestionDataServiceImpl extends DataService<Suggestion> {
|
||||
@dataService(SUGGESTION)
|
||||
export class SuggestionsDataService {
|
||||
protected searchFindBySourceMethod = 'findBySource';
|
||||
protected searchFindByTargetMethod = 'findByTarget';
|
||||
protected searchFindByTargetAndSourceMethod = 'findByTargetAndSource';
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
* A private UpdateDataServiceImpl implementation to delegate specific methods to.
|
||||
*/
|
||||
private suggestionsDataService: SuggestionDataServiceImpl;
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
* A private UpdateDataServiceImpl implementation to delegate specific methods to.
|
||||
*/
|
||||
private suggestionSourcesDataService: SuggestionSourceDataService;
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
* A private UpdateDataServiceImpl implementation to delegate specific methods to.
|
||||
*/
|
||||
private suggestionTargetsDataService: SuggestionTargetDataService;
|
||||
|
||||
private responseMsToLive = 10 * 1000;
|
||||
|
||||
/**
|
||||
* Initialize service variables
|
||||
* @param {RequestService} requestService
|
||||
@@ -111,13 +111,13 @@ export class SuggestionsDataService {
|
||||
protected comparatorSources: DefaultChangeAnalyzer<SuggestionSource>,
|
||||
protected comparatorTargets: DefaultChangeAnalyzer<SuggestionTarget>,
|
||||
) {
|
||||
this.suggestionsDataService = new SuggestionDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSuggestions);
|
||||
this.suggestionsDataService = new SuggestionDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSuggestions, this.responseMsToLive);
|
||||
this.suggestionSourcesDataService = new SuggestionSourceDataService(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSources);
|
||||
this.suggestionTargetsDataService = new SuggestionTargetDataService(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorTargets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of Suggestion Target
|
||||
* Return the list of Suggestion Sources
|
||||
*
|
||||
* @param options
|
||||
* Find list options object.
|
||||
@@ -168,7 +168,6 @@ export class SuggestionsDataService {
|
||||
...linksToFollow: FollowLinkConfig<SuggestionTarget>[]
|
||||
): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
|
||||
options.searchParams = [new RequestParam('target', userId)];
|
||||
//return this.suggestionTargetsDataService.getTargetsByUser(this.searchFindByTargetMethod, options, ...linksToFollow);
|
||||
return this.suggestionTargetsDataService.getTargetsByUser(userId, options, ...linksToFollow);
|
||||
}
|
||||
|
||||
@@ -193,14 +192,6 @@ export class SuggestionsDataService {
|
||||
return this.suggestionsDataService.delete(suggestionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to fetch Suggestion notification for user
|
||||
* @suggestionId
|
||||
*/
|
||||
public getSuggestion(suggestionId: string, ...linksToFollow: FollowLinkConfig<Suggestion>[]): Observable<RemoteData<Suggestion>> {
|
||||
return this.suggestionsDataService.findById(suggestionId, true, true, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of Suggestion for a given target and source
|
||||
*
|
||||
@@ -226,7 +217,7 @@ export class SuggestionsDataService {
|
||||
new RequestParam('source', source)
|
||||
];
|
||||
|
||||
return this.suggestionsDataService.searchBy(this.searchFindByTargetAndSourceMethod, options, true, true, ...linksToFollow);
|
||||
return this.suggestionsDataService.searchBy(this.searchFindByTargetAndSourceMethod, options, false, true, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
@@ -0,0 +1,141 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { dataService } from '../../data/base/data-service.decorator';
|
||||
|
||||
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
|
||||
import { SuggestionTarget } from '../models/suggestion-target.model';
|
||||
import { FindAllData, FindAllDataImpl } from '../../data/base/find-all-data';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { CoreState } from '../../core-state.model';
|
||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FindListOptions } from '../../data/find-list-options.model';
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { PaginatedList } from '../../data/paginated-list.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RequestParam } from '../../cache/models/request-param.model';
|
||||
import { SearchData, SearchDataImpl } from '../../data/base/search-data';
|
||||
import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service';
|
||||
import { SUGGESTION_TARGET } from '../models/suggestion-target-object.resource-type';
|
||||
|
||||
@Injectable()
|
||||
@dataService(SUGGESTION_TARGET)
|
||||
export class SuggestionTargetDataService extends IdentifiableDataService<SuggestionTarget> {
|
||||
|
||||
protected linkPath = 'suggestiontargets';
|
||||
private findAllData: FindAllData<SuggestionTarget>;
|
||||
private searchData: SearchData<SuggestionTarget>;
|
||||
protected searchFindBySourceMethod = 'findBySource';
|
||||
protected searchFindByTargetMethod = 'findByTarget';
|
||||
|
||||
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: DefaultChangeAnalyzer<SuggestionTarget>) {
|
||||
super('suggestiontargets', requestService, rdbService, objectCache, halService);
|
||||
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||
}
|
||||
/**
|
||||
* Return the list of Suggestion Target for a given source
|
||||
*
|
||||
* @param source
|
||||
* The source for which to find targets.
|
||||
* @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<SuggestionTarget>>>
|
||||
* The list of Suggestion Target.
|
||||
*/
|
||||
public getTargets(
|
||||
source: string,
|
||||
options: FindListOptions = {},
|
||||
...linksToFollow: FollowLinkConfig<SuggestionTarget>[]
|
||||
): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
|
||||
options.searchParams = [new RequestParam('source', source)];
|
||||
|
||||
return this.searchBy(this.searchFindBySourceMethod, options, true, true, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of Suggestion Target for a given user
|
||||
*
|
||||
* @param userId
|
||||
* The user Id for which to find targets.
|
||||
* @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<SuggestionTarget>>>
|
||||
* The list of Suggestion Target.
|
||||
*/
|
||||
public getTargetsByUser(
|
||||
userId: string,
|
||||
options: FindListOptions = {},
|
||||
...linksToFollow: FollowLinkConfig<SuggestionTarget>[]
|
||||
): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
|
||||
options.searchParams = [new RequestParam('target', userId)];
|
||||
|
||||
return this.searchBy(this.searchFindByTargetMethod, options, true, true, ...linksToFollow);
|
||||
}
|
||||
/**
|
||||
* Return a Suggestion Target for a given id
|
||||
*
|
||||
* @param targetId
|
||||
* The target id to retrieve.
|
||||
*
|
||||
* @return Observable<RemoteData<SuggestionTarget>>
|
||||
* The list of Suggestion Target.
|
||||
*/
|
||||
public getTargetById(targetId: string): Observable<RemoteData<SuggestionTarget>> {
|
||||
return this.findById(targetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new FindListRequest with given search method
|
||||
*
|
||||
* @param searchMethod The search method for the object
|
||||
* @param options The [[FindListOptions]] object
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<T>>}
|
||||
* Return an observable that emits response from the server
|
||||
*/
|
||||
public searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig<SuggestionTarget>[]): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
|
||||
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@link RemoteData} of all object with a list of {@link FollowLinkConfig}, to indicate which embedded
|
||||
* info should be added to the objects
|
||||
*
|
||||
* @param options Find list options object
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<T>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SuggestionTarget>[]): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
|
||||
return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||
import { RequestEntry } from '../../data/request-entry.model';
|
||||
import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||
import { RestResponse } from '../../cache/response.models';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../../core-state.model';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { SearchData } from '../../data/base/search-data';
|
||||
import { testSearchDataImplementation } from '../../data/base/search-data.spec';
|
||||
import { SuggestionTargetDataService } from './suggestion-target-data.service';
|
||||
import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service';
|
||||
import { SuggestionTarget } from '../models/suggestion-target.model';
|
||||
import { testFindAllDataImplementation } from '../../data/base/find-all-data.spec';
|
||||
import { FindAllData } from '../../data/base/find-all-data';
|
||||
import { GetRequest } from '../../data/request.models';
|
||||
import {
|
||||
createSuccessfulRemoteDataObject$
|
||||
} from '../../../shared/remote-data.utils';
|
||||
import { RequestParam } from '../../cache/models/request-param.model';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { RequestEntryState } from '../../data/request-entry-state.model';
|
||||
|
||||
describe('SuggestionTargetDataService test', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: SuggestionTargetDataService;
|
||||
let requestService: RequestService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: HALEndpointService;
|
||||
let notificationsService: NotificationsService;
|
||||
let http: HttpClient;
|
||||
let comparator: DefaultChangeAnalyzer<SuggestionTarget>;
|
||||
let responseCacheEntry: RequestEntry;
|
||||
|
||||
const store = {} as Store<CoreState>;
|
||||
const endpointURL = `https://rest.api/rest/api/suggestiontargets`;
|
||||
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||
|
||||
const remoteDataMocks = {
|
||||
Success: new RemoteData(null, null, null, RequestEntryState.Success, null, null, 200),
|
||||
};
|
||||
|
||||
function initTestService() {
|
||||
return new SuggestionTargetDataService(
|
||||
requestService,
|
||||
rdbService,
|
||||
store,
|
||||
objectCache,
|
||||
halService,
|
||||
notificationsService,
|
||||
http,
|
||||
comparator
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
objectCache = {} as ObjectCacheService;
|
||||
http = {} as HttpClient;
|
||||
notificationsService = {} as NotificationsService;
|
||||
comparator = {} as DefaultChangeAnalyzer<SuggestionTarget>;
|
||||
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: observableOf(responseCacheEntry),
|
||||
getByUUID: observableOf(responseCacheEntry),
|
||||
});
|
||||
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: observableOf(endpointURL)
|
||||
});
|
||||
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: createSuccessfulRemoteDataObject$({}, 500),
|
||||
buildList: cold('a', { a: remoteDataMocks.Success })
|
||||
});
|
||||
|
||||
|
||||
service = initTestService();
|
||||
});
|
||||
|
||||
describe('composition', () => {
|
||||
const initSearchService = () => new SuggestionTargetDataService(null, null, null, null, null, null, null, null) as unknown as SearchData<any>;
|
||||
const initFindAllService = () => new SuggestionTargetDataService(null, null, null, null, null, null, null, null) as unknown as FindAllData<any>;
|
||||
testSearchDataImplementation(initSearchService);
|
||||
testFindAllDataImplementation(initFindAllService);
|
||||
});
|
||||
|
||||
describe('getTargetById', () => {
|
||||
it('should send a new GetRequest', () => {
|
||||
const expected = new GetRequest(requestService.generateRequestId(), endpointURL + '/testId');
|
||||
scheduler.schedule(() => service.getTargetById('testId').subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTargetsByUser', () => {
|
||||
it('should send a new GetRequest', () => {
|
||||
const options = {
|
||||
searchParams: [new RequestParam('target', 'testId')]
|
||||
};
|
||||
const searchFindByTargetMethod = 'findByTarget';
|
||||
const expected = new GetRequest(requestService.generateRequestId(), `${endpointURL}/search/${searchFindByTargetMethod}?target=testId`);
|
||||
scheduler.schedule(() => service.getTargetsByUser('testId', options).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTargets', () => {
|
||||
it('should send a new GetRequest', () => {
|
||||
const options = {
|
||||
searchParams: [new RequestParam('source', 'testId')]
|
||||
};
|
||||
const searchFindBySourceMethod = 'findBySource';
|
||||
const expected = new GetRequest(requestService.generateRequestId(), `${endpointURL}/search/${searchFindBySourceMethod}?source=testId`);
|
||||
scheduler.schedule(() => service.getTargets('testId', options).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||
});
|
||||
});
|
||||
});
|
@@ -27,6 +27,7 @@ const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
|
||||
export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');
|
||||
export const IN_PLACE_SEARCH: InjectionToken<boolean> = new InjectionToken<boolean>('inPlaceSearch');
|
||||
export const REFRESH_FILTER: InjectionToken<BehaviorSubject<any>> = new InjectionToken<boolean>('refreshFilters');
|
||||
export const SCOPE: InjectionToken<string> = new InjectionToken<string>('scope');
|
||||
|
||||
/**
|
||||
* Service that performs all actions that have to do with search filters and facets
|
||||
|
@@ -126,7 +126,7 @@ describe('WorkflowItemDataService test', () => {
|
||||
});
|
||||
|
||||
describe('findByItem', () => {
|
||||
it('should proxy the call to DataService.findByHref', () => {
|
||||
it('should proxy the call to UpdateDataServiceImpl.findByHref', () => {
|
||||
scheduler.schedule(() => service.findByItem('1234-1234', true, true, pageInfo));
|
||||
scheduler.flush();
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
||||
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
||||
import { WorkspaceitemDataService } from './workspaceitem-data.service';
|
||||
@@ -19,7 +19,13 @@ import { Item } from '../shared/item.model';
|
||||
import { WorkspaceItem } from './models/workspaceitem.model';
|
||||
import { RequestEntry } from '../data/request-entry.model';
|
||||
import { CoreState } from '../core-state.model';
|
||||
import { testSearchDataImplementation } from '../data/base/search-data.spec';
|
||||
import { testDeleteDataImplementation } from '../data/base/delete-data.spec';
|
||||
import { SearchData } from '../data/base/search-data';
|
||||
import { DeleteData } from '../data/base/delete-data';
|
||||
import { RequestParam } from '../cache/models/request-param.model';
|
||||
import { PostRequest } from '../data/request.models';
|
||||
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||
|
||||
describe('WorkspaceitemDataService test', () => {
|
||||
let scheduler: TestScheduler;
|
||||
@@ -67,15 +73,12 @@ describe('WorkspaceitemDataService test', () => {
|
||||
const wsiRD = createSuccessfulRemoteDataObject(wsi);
|
||||
|
||||
const endpointURL = `https://rest.api/rest/api/submission/workspaceitems`;
|
||||
const searchRequestURL = `https://rest.api/rest/api/submission/workspaceitems/search/item?uuid=1234-1234`;
|
||||
const searchRequestURL$ = observableOf(searchRequestURL);
|
||||
|
||||
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||
|
||||
objectCache = {} as ObjectCacheService;
|
||||
const notificationsService = {} as NotificationsService;
|
||||
const http = {} as HttpClient;
|
||||
const comparator = {} as any;
|
||||
const comparatorEntry = {} as any;
|
||||
const store = {} as Store<CoreState>;
|
||||
const pageInfo = new PageInfo();
|
||||
@@ -83,20 +86,23 @@ describe('WorkspaceitemDataService test', () => {
|
||||
function initTestService() {
|
||||
hrefOnlyDataService = getMockHrefOnlyDataService();
|
||||
return new WorkspaceitemDataService(
|
||||
comparator,
|
||||
comparatorEntry,
|
||||
halService,
|
||||
http,
|
||||
notificationsService,
|
||||
requestService,
|
||||
rdbService,
|
||||
objectCache,
|
||||
store,
|
||||
store
|
||||
);
|
||||
}
|
||||
|
||||
describe('composition', () => {
|
||||
const initService = () => new WorkspaceitemDataService(null, null, null, null, null, null, null, null);
|
||||
testDeleteDataImplementation(initService);
|
||||
const initSearchService = () => new WorkspaceitemDataService(null, null, null, null, null, null, null, null) as unknown as SearchData<any>;
|
||||
const initDeleteService = () => new WorkspaceitemDataService(null, null, null, null, null, null, null, null) as unknown as DeleteData<any>;
|
||||
|
||||
testSearchDataImplementation(initSearchService);
|
||||
testDeleteDataImplementation(initDeleteService);
|
||||
});
|
||||
|
||||
describe('', () => {
|
||||
@@ -105,7 +111,7 @@ describe('WorkspaceitemDataService test', () => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: cold('a', { a: endpointURL })
|
||||
getEndpoint: observableOf(endpointURL)
|
||||
});
|
||||
responseCacheEntry = new RequestEntry();
|
||||
responseCacheEntry.request = { href: 'https://rest.api/' } as any;
|
||||
@@ -121,13 +127,13 @@ describe('WorkspaceitemDataService test', () => {
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: hot('a|', {
|
||||
a: wsiRD
|
||||
})
|
||||
}),
|
||||
buildFromRequestUUID: createSuccessfulRemoteDataObject$({})
|
||||
});
|
||||
|
||||
service = initTestService();
|
||||
|
||||
spyOn((service as any), 'findByHref').and.callThrough();
|
||||
spyOn((service as any), 'getIDHref').and.callThrough();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -135,11 +141,11 @@ describe('WorkspaceitemDataService test', () => {
|
||||
});
|
||||
|
||||
describe('findByItem', () => {
|
||||
it('should proxy the call to DataService.findByHref', () => {
|
||||
it('should proxy the call to UpdateDataServiceImpl.findByHref', () => {
|
||||
scheduler.schedule(() => service.findByItem('1234-1234', true, true, pageInfo));
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).findByHref).toHaveBeenCalled();
|
||||
const searchUrl = service.getIDHref('item', [new RequestParam('uuid', encodeURIComponent('1234-1234'))]);
|
||||
expect((service as any).findByHref).toHaveBeenCalledWith(searchUrl, true, true);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<WorkspaceItem> for the search', () => {
|
||||
@@ -151,6 +157,19 @@ describe('WorkspaceitemDataService test', () => {
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('importExternalSourceEntry', () => {
|
||||
it('should send a POST request containing the provided item request', (done) => {
|
||||
const options: HttpOptions = Object.create({});
|
||||
let headers = new HttpHeaders();
|
||||
headers = headers.append('Content-Type', 'text/uri-list');
|
||||
options.headers = headers;
|
||||
|
||||
service.importExternalSourceEntry('externalHref', 'testId').subscribe(() => {
|
||||
expect(requestService.send).toHaveBeenCalledWith(new PostRequest(requestUUID, `${endpointURL}?owningCollection=testId`, 'externalHref', options));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -23,16 +23,19 @@ import {hasValue} from '../../shared/empty.util';
|
||||
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
||||
import { NoContent } from '../shared/NoContent.model';
|
||||
import { DeleteData, DeleteDataImpl } from '../data/base/delete-data';
|
||||
import { SearchData, SearchDataImpl } from '../data/base/search-data';
|
||||
import { PaginatedList } from '../data/paginated-list.model';
|
||||
|
||||
/**
|
||||
* A service that provides methods to make REST requests with workspaceitems endpoint.
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(WorkspaceItem.type)
|
||||
export class WorkspaceitemDataService extends IdentifiableDataService<WorkspaceItem> {
|
||||
export class WorkspaceitemDataService extends IdentifiableDataService<WorkspaceItem> implements DeleteData<WorkspaceItem>, SearchData<WorkspaceItem>{
|
||||
protected linkPath = 'workspaceitems';
|
||||
protected searchByItemLinkPath = 'item';
|
||||
private deleteData: DeleteData<WorkspaceItem>;
|
||||
private searchData: SearchData<WorkspaceItem>;
|
||||
|
||||
constructor(
|
||||
protected comparator: DSOChangeAnalyzer<WorkspaceItem>,
|
||||
@@ -45,7 +48,7 @@ export class WorkspaceitemDataService extends IdentifiableDataService<WorkspaceI
|
||||
protected store: Store<CoreState>) {
|
||||
super('workspaceitems', requestService, rdbService, objectCache, halService);
|
||||
this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint);
|
||||
|
||||
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||
}
|
||||
public delete(objectId: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
||||
return this.deleteData.delete(objectId, copyVirtualMetadata);
|
||||
@@ -107,4 +110,34 @@ export class WorkspaceitemDataService extends IdentifiableDataService<WorkspaceI
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existing object on the server
|
||||
* @param href The self link of the object to be removed
|
||||
* @param copyVirtualMetadata (optional parameter) the identifiers of the relationship types for which the virtual
|
||||
* metadata should be saved as real metadata
|
||||
* @return A RemoteData observable with an empty payload, but still representing the state of the request: statusCode,
|
||||
* errorMessage, timeCompleted, etc
|
||||
* Only emits once all request related to the DSO has been invalidated.
|
||||
*/
|
||||
deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
||||
return this.deleteData.deleteByHref(href, copyVirtualMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a new FindListRequest with given search method
|
||||
*
|
||||
* @param searchMethod The search method for the object
|
||||
* @param options The [[FindListOptions]] object
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<T>>}
|
||||
* Return an observable that emits response from the server
|
||||
*/
|
||||
searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig<WorkspaceItem>[]): Observable<RemoteData<PaginatedList<WorkspaceItem>>> {
|
||||
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,6 @@ import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||
import {
|
||||
getFirstCompletedRemoteData,
|
||||
} from '../../core/shared/operators';
|
||||
import { UpdateDataService } from '../../core/data/update-data.service';
|
||||
import { ResourceType } from '../../core/shared/resource-type';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@@ -22,6 +21,7 @@ import { ArrayMoveChangeAnalyzer } from '../../core/data/array-move-change-analy
|
||||
import { DATA_SERVICE_FACTORY } from '../../core/data/base/data-service.decorator';
|
||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||
import { HALDataService } from '../../core/data/base/hal-data-service.interface';
|
||||
import { UpdateDataService } from '../../core/data/update-data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-dso-edit-metadata',
|
||||
|
@@ -32,6 +32,18 @@
|
||||
</ds-generic-item-page-field>
|
||||
</div>
|
||||
<div class="col-xs-12 col-md-7">
|
||||
<ds-item-page-img-field
|
||||
[fields]="['organization.identifier.ror']"
|
||||
[img]="{
|
||||
URI: './assets/images/ror-icon.svg',
|
||||
alt: 'item.page.image.alt.ROR',
|
||||
heightVar: '--ds-item-page-img-field-ror-inline-height'
|
||||
}"
|
||||
[item]="object"
|
||||
[label]="'orgunit.page.ror'"
|
||||
[urlRegex]="'(.*)ror.org'"
|
||||
>
|
||||
</ds-item-page-img-field>
|
||||
<ds-related-items
|
||||
[parentItem]="object"
|
||||
[relationType]="'isPublicationOfOrgUnit'"
|
||||
|
@@ -26,15 +26,15 @@ const DECLARATIONS = [
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule.withEntryComponents(),
|
||||
JournalEntitiesModule.withEntryComponents(),
|
||||
ResearchEntitiesModule.withEntryComponents(),
|
||||
HomePageRoutingModule,
|
||||
StatisticsModule.forRoot(),
|
||||
NotificationsModule
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule.withEntryComponents(),
|
||||
JournalEntitiesModule.withEntryComponents(),
|
||||
ResearchEntitiesModule.withEntryComponents(),
|
||||
HomePageRoutingModule,
|
||||
StatisticsModule.forRoot(),
|
||||
NotificationsModule
|
||||
],
|
||||
declarations: [
|
||||
...DECLARATIONS,
|
||||
],
|
||||
|
@@ -3,8 +3,8 @@
|
||||
<!--
|
||||
Choose a template. Priority: markdown, link, browse link.
|
||||
-->
|
||||
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : (hasLink(mdValue) ? link : (hasBrowseDefinition() ? browselink : simple)));
|
||||
context: {value: mdValue.value}">
|
||||
<ng-container *ngTemplateOutlet="(renderMarkdown ? markdown : (hasLink(mdValue) ? (img != null ? linkImg : link) : (hasBrowseDefinition() ? browselink : simple)));
|
||||
context: {value: mdValue.value, img}">
|
||||
</ng-container>
|
||||
<span class="separator" *ngIf="!last" [innerHTML]="separator"></span>
|
||||
</ng-container>
|
||||
@@ -23,6 +23,17 @@
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<!-- Render value as a link with icon -->
|
||||
<ng-template #linkImg let-img="img" let-value="value">
|
||||
<a [href]="value" class="link-anchor dont-break-out ds-simple-metadata-link" target="_blank">
|
||||
<img class="link-logo"
|
||||
[alt]="img.alt | translate"
|
||||
[style.height]="'var(' + img.heightVar + ', --ds-item-page-img-field-default-inline-height)'"
|
||||
[src]="img.URI"/>
|
||||
{{value}}
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<!-- Render simple value in a span -->
|
||||
<ng-template #simple let-value="value">
|
||||
<span class="dont-break-out preserve-line-breaks">{{value}}</span>
|
||||
|
@@ -4,7 +4,7 @@ import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
|
||||
import { BrowseDefinition } from '../../../core/shared/browse-definition.model';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { VALUE_LIST_BROWSE_DEFINITION } from '../../../core/shared/value-list-browse-definition.resource-type';
|
||||
import { environment } from './../../../../environments/environment';
|
||||
import { ImageField } from '../../simple/field-components/specific-field/item-page-field.component';
|
||||
|
||||
/**
|
||||
* This component renders the configured 'values' into the ds-metadata-field-wrapper component.
|
||||
@@ -56,6 +56,11 @@ export class MetadataValuesComponent implements OnChanges {
|
||||
|
||||
@Input() browseDefinition?: BrowseDefinition;
|
||||
|
||||
/**
|
||||
* Optional {@code ImageField} reference that represents an image to be displayed inline.
|
||||
*/
|
||||
@Input() img?: ImageField;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.renderMarkdown = !!this.appConfig.markdown.enabled && this.enableMarkdown;
|
||||
}
|
||||
@@ -91,13 +96,4 @@ export class MetadataValuesComponent implements OnChanges {
|
||||
}
|
||||
return queryParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given link value is an internal link.
|
||||
* @param linkValue - The link value to check.
|
||||
* @returns True if the link value starts with the base URL defined in the environment configuration, false otherwise.
|
||||
*/
|
||||
hasInternalLink(linkValue: string): boolean {
|
||||
return linkValue.startsWith(environment.ui.baseUrl);
|
||||
}
|
||||
}
|
||||
|
@@ -1,21 +1,36 @@
|
||||
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
|
||||
import {
|
||||
RelatedEntitiesSearchComponent
|
||||
} from './simple/related-entities/related-entities-search/related-entities-search.component';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CommonModule, NgOptimizedImage } from '@angular/common';
|
||||
import { SearchModule } from '../shared/search/search.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DYNAMIC_FORM_CONTROL_MAP_FN } from '@ng-dynamic-forms/core';
|
||||
import { dsDynamicFormControlMapFn } from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
|
||||
import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
|
||||
import { ItemVersionsDeleteModalComponent } from './versions/item-versions-delete-modal/item-versions-delete-modal.component';
|
||||
import { ItemVersionsSummaryModalComponent } from './versions/item-versions-summary-modal/item-versions-summary-modal.component';
|
||||
import {
|
||||
dsDynamicFormControlMapFn
|
||||
} from '../shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
|
||||
import {
|
||||
TabbedRelatedEntitiesSearchComponent
|
||||
} from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
|
||||
import {
|
||||
ItemVersionsDeleteModalComponent
|
||||
} from './versions/item-versions-delete-modal/item-versions-delete-modal.component';
|
||||
import {
|
||||
ItemVersionsSummaryModalComponent
|
||||
} from './versions/item-versions-summary-modal/item-versions-summary-modal.component';
|
||||
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
|
||||
import { GenericItemPageFieldComponent } from './simple/field-components/specific-field/generic/generic-item-page-field.component';
|
||||
import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component';
|
||||
import {
|
||||
GenericItemPageFieldComponent
|
||||
} from './simple/field-components/specific-field/generic/generic-item-page-field.component';
|
||||
import {
|
||||
MetadataRepresentationListComponent
|
||||
} from './simple/metadata-representation-list/metadata-representation-list.component';
|
||||
import { RelatedItemsComponent } from './simple/related-items/related-items-component';
|
||||
import {
|
||||
ThemedMetadataRepresentationListComponent
|
||||
} from './simple/metadata-representation-list/themed-metadata-representation-list.component';
|
||||
import { ItemPageImgFieldComponent } from './simple/field-components/specific-field/img/item-page-img-field.component';
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
ItemVersionsDeleteModalComponent,
|
||||
@@ -32,6 +47,7 @@ const COMPONENTS = [
|
||||
MetadataRepresentationListComponent,
|
||||
ThemedMetadataRepresentationListComponent,
|
||||
RelatedItemsComponent,
|
||||
ItemPageImgFieldComponent,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -42,7 +58,8 @@ const COMPONENTS = [
|
||||
CommonModule,
|
||||
SearchModule,
|
||||
SharedModule,
|
||||
TranslateModule
|
||||
TranslateModule,
|
||||
NgOptimizedImage
|
||||
],
|
||||
exports: [
|
||||
...COMPONENTS
|
||||
|
@@ -0,0 +1,85 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ItemPageImgFieldComponent } from './item-page-img-field.component';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
||||
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||
import { environment } from '../../../../../../environments/environment';
|
||||
import { BrowseDefinitionDataService } from '../../../../../core/browse/browse-definition-data.service';
|
||||
import { BrowseDefinitionDataServiceStub } from '../../../../../shared/testing/browse-definition-data-service.stub';
|
||||
import { GenericItemPageFieldComponent } from '../generic/generic-item-page-field.component';
|
||||
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { mockItemWithMetadataFieldsAndValue } from '../item-page-field.component.spec';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { ImageField } from '../item-page-field.component';
|
||||
|
||||
let component: ItemPageImgFieldComponent;
|
||||
let fixture: ComponentFixture<ItemPageImgFieldComponent>;
|
||||
|
||||
const mockField = 'organization.identifier.ror';
|
||||
const mockValue = 'http://ror.org/awesome-identifier';
|
||||
const mockLabel = 'ROR label';
|
||||
const mockUrlRegex = '(.*)ror.org';
|
||||
const mockImg = {
|
||||
URI: './assets/images/ror-icon.svg',
|
||||
alt: 'item.page.image.alt.ROR',
|
||||
heightVar: '--ds-item-page-img-field-ror-inline-height'
|
||||
} as ImageField;
|
||||
|
||||
describe('ItemPageImgFieldComponent', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
})],
|
||||
providers: [
|
||||
{ provide: APP_CONFIG, useValue: environment },
|
||||
{ provide: BrowseDefinitionDataService, useValue: BrowseDefinitionDataServiceStub }
|
||||
],
|
||||
declarations: [ItemPageImgFieldComponent, GenericItemPageFieldComponent, MetadataValuesComponent],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.overrideComponent(GenericItemPageFieldComponent, {
|
||||
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ItemPageImgFieldComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.item = mockItemWithMetadataFieldsAndValue([mockField], mockValue);
|
||||
component.fields = [mockField];
|
||||
component.label = mockLabel;
|
||||
component.urlRegex = mockUrlRegex;
|
||||
component.img = mockImg;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display display img tag', () => {
|
||||
const image = fixture.debugElement.query(By.css('img.link-logo'));
|
||||
expect(image).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should have right attributes', () => {
|
||||
const image = fixture.debugElement.query(By.css('img.link-logo'));
|
||||
expect(image.attributes.src).toEqual(mockImg.URI);
|
||||
expect(image.attributes.alt).toEqual(mockImg.alt);
|
||||
|
||||
const imageEl = image.nativeElement;
|
||||
expect(imageEl.style.height).toContain(mockImg.heightVar);
|
||||
});
|
||||
|
||||
it('should have the right value', () => {
|
||||
const imageAnchor = fixture.debugElement.query(By.css('a.link-anchor'));
|
||||
const anchorEl = imageAnchor.nativeElement;
|
||||
expect(anchorEl.innerHTML).toContain(mockValue);
|
||||
});
|
||||
});
|
@@ -0,0 +1,46 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { ImageField, ItemPageFieldComponent } from '../item-page-field.component';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-page-img-field',
|
||||
templateUrl: '../item-page-field.component.html'
|
||||
})
|
||||
/**
|
||||
* Component that renders an inline image for a given field.
|
||||
* This component uses a given {@code ImageField} configuration to correctly render the img.
|
||||
*/
|
||||
export class ItemPageImgFieldComponent extends ItemPageFieldComponent {
|
||||
|
||||
/**
|
||||
* The item to display metadata for
|
||||
*/
|
||||
@Input() item: Item;
|
||||
|
||||
/**
|
||||
* Separator string between multiple values of the metadata fields defined
|
||||
* @type {string}
|
||||
*/
|
||||
@Input() separator: string;
|
||||
|
||||
/**
|
||||
* Fields (schema.element.qualifier) used to render their values.
|
||||
*/
|
||||
@Input() fields: string[];
|
||||
|
||||
/**
|
||||
* Label i18n key for the rendered metadata
|
||||
*/
|
||||
@Input() label: string;
|
||||
|
||||
/**
|
||||
* Image Configuration
|
||||
*/
|
||||
@Input() img: ImageField;
|
||||
|
||||
/**
|
||||
* Whether any valid HTTP(S) URL should be rendered as a link
|
||||
*/
|
||||
@Input() urlRegex?: string;
|
||||
|
||||
}
|
@@ -6,5 +6,6 @@
|
||||
[enableMarkdown]="enableMarkdown"
|
||||
[urlRegex]="urlRegex"
|
||||
[browseDefinition]="browseDefinition|async"
|
||||
[img]="img"
|
||||
></ds-metadata-values>
|
||||
</div>
|
||||
|
@@ -6,6 +6,25 @@ import { BrowseDefinition } from '../../../../core/shared/browse-definition.mode
|
||||
import { BrowseDefinitionDataService } from '../../../../core/browse/browse-definition-data.service';
|
||||
import { getRemoteDataPayload } from '../../../../core/shared/operators';
|
||||
|
||||
/**
|
||||
* Interface that encapsulate Image configuration for this component.
|
||||
*/
|
||||
export interface ImageField {
|
||||
/**
|
||||
* URI that is used to retrieve the image.
|
||||
*/
|
||||
URI: string;
|
||||
/**
|
||||
* i18n Key that represents the alt text to display
|
||||
*/
|
||||
alt: string;
|
||||
/**
|
||||
* CSS variable that contains the height of the inline image.
|
||||
*/
|
||||
heightVar: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This component can be used to represent metadata on a simple item page.
|
||||
* It expects one input parameter of type Item to which the metadata belongs.
|
||||
@@ -51,6 +70,11 @@ export class ItemPageFieldComponent {
|
||||
*/
|
||||
urlRegex?: string;
|
||||
|
||||
/**
|
||||
* Image Configuration
|
||||
*/
|
||||
img: ImageField;
|
||||
|
||||
/**
|
||||
* Return browse definition that matches any field used in this component if it is configured as a browse
|
||||
* link in dspace.cfg (webui.browse.link.<n>)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { QualityAssuranceSourceEffects } from './qa/source/quality-assurance-source.effects';
|
||||
import { QualityAssuranceTopicsEffects } from './qa/topics/quality-assurance-topics.effects';
|
||||
import {SuggestionTargetsEffects} from './reciter-suggestions/suggestion-targets/suggestion-targets.effects';
|
||||
import { SuggestionTargetsEffects } from '../suggestion-notifications/suggestion-targets/suggestion-targets.effects';
|
||||
|
||||
export const notificationsEffects = [
|
||||
QualityAssuranceTopicsEffects,
|
||||
|
@@ -26,29 +26,30 @@ import { QualityAssuranceSourceService } from './qa/source/quality-assurance-sou
|
||||
import {
|
||||
QualityAssuranceSourceDataService
|
||||
} from '../core/notifications/qa/source/quality-assurance-source-data.service';
|
||||
import { SuggestionTargetsComponent } from './reciter-suggestions/suggestion-targets/suggestion-targets.component';
|
||||
import { SuggestionActionsComponent } from './reciter-suggestions/suggestion-actions/suggestion-actions.component';
|
||||
import { PublicationClaimComponent } from '../suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component';
|
||||
import { SuggestionActionsComponent } from '../suggestion-notifications/suggestion-actions/suggestion-actions.component';
|
||||
import {
|
||||
SuggestionListElementComponent
|
||||
} from './reciter-suggestions/suggestion-list-element/suggestion-list-element.component';
|
||||
} from '../suggestion-notifications/suggestion-list-element/suggestion-list-element.component';
|
||||
import {
|
||||
SuggestionEvidencesComponent
|
||||
} from './reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component';
|
||||
import { SuggestionsPopupComponent } from './reciter-suggestions/suggestions-popup/suggestions-popup.component';
|
||||
} from '../suggestion-notifications/suggestion-list-element/suggestion-evidences/suggestion-evidences.component';
|
||||
import { SuggestionsPopupComponent } from '../suggestion-notifications/suggestions-popup/suggestions-popup.component';
|
||||
import {
|
||||
SuggestionsNotificationComponent
|
||||
} from './reciter-suggestions/suggestions-notification/suggestions-notification.component';
|
||||
import { SuggestionsService } from './reciter-suggestions/suggestions.service';
|
||||
import { SuggestionsDataService } from '../core/notifications/reciter-suggestions/suggestions-data.service';
|
||||
} from '../suggestion-notifications/suggestions-notification/suggestions-notification.component';
|
||||
import { SuggestionsService } from '../suggestion-notifications/suggestions.service';
|
||||
import { SuggestionsDataService } from '../core/suggestion-notifications/suggestions-data.service';
|
||||
import {
|
||||
SuggestionSourceDataService
|
||||
} from '../core/notifications/reciter-suggestions/source/suggestion-source-data.service';
|
||||
} from '../core/suggestion-notifications/source/suggestion-source-data.service';
|
||||
import {
|
||||
SuggestionTargetDataService
|
||||
} from '../core/notifications/reciter-suggestions/target/suggestion-target-data.service';
|
||||
} from '../core/suggestion-notifications/target/suggestion-target-data.service';
|
||||
import {
|
||||
SuggestionTargetsStateService
|
||||
} from './reciter-suggestions/suggestion-targets/suggestion-targets.state.service';
|
||||
} from '../suggestion-notifications/suggestion-targets/suggestion-targets.state.service';
|
||||
|
||||
|
||||
const MODULES = [
|
||||
CommonModule,
|
||||
@@ -64,7 +65,7 @@ const COMPONENTS = [
|
||||
QualityAssuranceTopicsComponent,
|
||||
QualityAssuranceEventsComponent,
|
||||
QualityAssuranceSourceComponent,
|
||||
SuggestionTargetsComponent,
|
||||
PublicationClaimComponent,
|
||||
SuggestionActionsComponent,
|
||||
SuggestionListElementComponent,
|
||||
SuggestionEvidencesComponent,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { ActionReducerMap, createFeatureSelector } from '@ngrx/store';
|
||||
import { qualityAssuranceSourceReducer, QualityAssuranceSourceState } from './qa/source/quality-assurance-source.reducer';
|
||||
import { qualityAssuranceTopicsReducer, QualityAssuranceTopicState, } from './qa/topics/quality-assurance-topics.reducer';
|
||||
import { SuggestionTargetsReducer, SuggestionTargetState } from './reciter-suggestions/suggestion-targets/suggestion-targets.reducer';
|
||||
import { SuggestionTargetsReducer, SuggestionTargetState } from '../suggestion-notifications/suggestion-targets/suggestion-targets.reducer';
|
||||
|
||||
/**
|
||||
* The OpenAIRE State
|
||||
|
@@ -1,8 +0,0 @@
|
||||
<ng-container *ngIf="(suggestionsRD$ | async) as suggestions">
|
||||
<ng-container *ngFor="let suggestion of suggestions" class="alert alert-info">
|
||||
<div class="alert alert-success d-block" *ngIf="suggestion.total > 0">
|
||||
<div [innerHTML]="'mydspace.notification.suggestion.page' | translate: getNotificationSuggestionInterpolation(suggestion)">
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
@@ -1 +0,0 @@
|
||||
|
@@ -1,68 +0,0 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { SuggestionTargetsStateService } from '../suggestion-targets/suggestion-targets.state.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { SuggestionsService } from '../suggestions.service';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { SuggestionTarget } from '../../../core/notifications/reciter-suggestions/models/suggestion-target.model';
|
||||
import { isNotEmpty } from '../../../shared/empty.util';
|
||||
import { combineLatest, Subject } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-suggestions-popup',
|
||||
templateUrl: './suggestions-popup.component.html',
|
||||
styleUrls: ['./suggestions-popup.component.scss']
|
||||
})
|
||||
export class SuggestionsPopupComponent implements OnInit, OnDestroy {
|
||||
|
||||
labelPrefix = 'mydspace.';
|
||||
|
||||
subscription;
|
||||
|
||||
constructor(
|
||||
private translateService: TranslateService,
|
||||
private reciterSuggestionStateService: SuggestionTargetsStateService,
|
||||
private notificationsService: NotificationsService,
|
||||
private suggestionsService: SuggestionsService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.initializePopup();
|
||||
}
|
||||
|
||||
public initializePopup() {
|
||||
this.reciterSuggestionStateService.dispatchRefreshUserSuggestionsAction();
|
||||
const notifier = new Subject();
|
||||
this.subscription = combineLatest([
|
||||
this.reciterSuggestionStateService.getCurrentUserSuggestionTargets(),
|
||||
this.reciterSuggestionStateService.hasUserVisitedSuggestions()
|
||||
]).pipe(takeUntil(notifier)).subscribe(([suggestions, visited]) => {
|
||||
if (isNotEmpty(suggestions)) {
|
||||
if (!visited) {
|
||||
suggestions.forEach((suggestionTarget: SuggestionTarget) => this.showNotificationForNewSuggestions(suggestionTarget));
|
||||
this.reciterSuggestionStateService.dispatchMarkUserSuggestionsAsVisitedAction();
|
||||
notifier.next(null);
|
||||
notifier.complete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notification to user for a new suggestions detected
|
||||
* @param suggestionTarget
|
||||
* @private
|
||||
*/
|
||||
private showNotificationForNewSuggestions(suggestionTarget: SuggestionTarget): void {
|
||||
const content = this.translateService.instant(this.labelPrefix + 'notification.suggestion',
|
||||
this.suggestionsService.getNotificationSuggestionInterpolation(suggestionTarget));
|
||||
this.notificationsService.success('', content, {timeOut:0}, true);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -19,10 +19,11 @@
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
<button (click)="notMine()" class="btn btn-danger ml-2"><i class="fa fa-ban"></i>
|
||||
{{ notMineLabel() | translate}}</button>
|
||||
<button *ngIf="!isBulk" (click)="toggleSeeEvidences()" [disabled]="!hasEvidence" class="btn btn-info mt-1 ml-2"><i class="fa fa-eye"></i>
|
||||
<ng-container *ngIf="!seeEvidence">{{'reciter.suggestion.seeEvidence' | translate}}</ng-container>
|
||||
<ng-container *ngIf="seeEvidence">{{'reciter.suggestion.hideEvidence' | translate}}</ng-container>
|
||||
<button (click)="ignoreSuggestion()" class="btn btn-danger ml-2"><i class="fa fa-ban"></i>
|
||||
{{ ignoreSuggestionLabel() | translate}}</button>
|
||||
<button *ngIf="!isBulk" (click)="toggleSeeEvidences()" [disabled]="!hasEvidence" class="btn btn-info ml-2">
|
||||
<i class="fa fa-eye"></i>
|
||||
<ng-container *ngIf="!seeEvidence"> {{ 'suggestion.seeEvidence' | translate}}</ng-container>
|
||||
<ng-container *ngIf="seeEvidence"> {{ 'suggestion.hideEvidence' | translate}}</ng-container>
|
||||
</button>
|
||||
</div>
|
@@ -1,12 +1,19 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { ItemType } from '../../../core/shared/item-relationships/item-type.model';
|
||||
import { ItemType } from '../../core/shared/item-relationships/item-type.model';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.ts
|
||||
import { Suggestion } from '../../../core/notifications/reciter-suggestions/models/suggestion.model';
|
||||
========
|
||||
import { Suggestion } from '../../core/suggestion-notifications/models/suggestion.model';
|
||||
>>>>>>>> main:src/app/notifications/suggestion-actions/suggestion-actions.component.ts
|
||||
import { SuggestionApproveAndImport } from '../suggestion-list-element/suggestion-list-element.component';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { CreateItemParentSelectorComponent } from '../../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
||||
import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
||||
|
||||
/**
|
||||
* Show and trigger the actions to submit for a suggestion
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-suggestion-actions',
|
||||
styleUrls: [ './suggestion-actions.component.scss' ],
|
||||
@@ -27,7 +34,7 @@ export class SuggestionActionsComponent {
|
||||
/**
|
||||
* The component is used to Delete suggestion
|
||||
*/
|
||||
@Output() notMineClicked = new EventEmitter<string>();
|
||||
@Output() ignoreSuggestionClicked = new EventEmitter<string>();
|
||||
|
||||
/**
|
||||
* The component is used to approve & import
|
||||
@@ -71,8 +78,8 @@ export class SuggestionActionsComponent {
|
||||
/**
|
||||
* Delete the suggestion
|
||||
*/
|
||||
notMine() {
|
||||
this.notMineClicked.emit(this.isBulk ? undefined : this.object.id);
|
||||
ignoreSuggestion() {
|
||||
this.ignoreSuggestionClicked.emit(this.isBulk ? undefined : this.object.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,11 +89,11 @@ export class SuggestionActionsComponent {
|
||||
this.seeEvidences.emit(!this.seeEvidence);
|
||||
}
|
||||
|
||||
notMineLabel(): string {
|
||||
return this.isBulk ? 'reciter.suggestion.notMine.bulk' : 'reciter.suggestion.notMine' ;
|
||||
ignoreSuggestionLabel(): string {
|
||||
return this.isBulk ? 'suggestion.ignoreSuggestion.bulk' : 'suggestion.ignoreSuggestion' ;
|
||||
}
|
||||
|
||||
approveAndImportLabel(): string {
|
||||
return this.isBulk ? 'reciter.suggestion.approveAndImport.bulk' : 'reciter.suggestion.approveAndImport';
|
||||
return this.isBulk ? 'suggestion.approveAndImport.bulk' : 'suggestion.approveAndImport';
|
||||
}
|
||||
}
|
@@ -3,9 +3,9 @@
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{'reciter.suggestion.evidence.score' | translate}}</th>
|
||||
<th>{{'reciter.suggestion.evidence.type' | translate}}</th>
|
||||
<th>{{'reciter.suggestion.evidence.notes' | translate}}</th>
|
||||
<th>{{'suggestion.evidence.score' | translate}}</th>
|
||||
<th>{{'suggestion.evidence.type' | translate}}</th>
|
||||
<th>{{'suggestion.evidence.notes' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
@@ -1,7 +1,15 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts
|
||||
import { fadeIn } from '../../../../shared/animations/fade';
|
||||
import { SuggestionEvidences } from '../../../../core/notifications/reciter-suggestions/models/suggestion.model';
|
||||
========
|
||||
import { fadeIn } from '../../../shared/animations/fade';
|
||||
import { SuggestionEvidences } from '../../../core/suggestion-notifications/models/suggestion.model';
|
||||
>>>>>>>> main:src/app/notifications/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts
|
||||
|
||||
/**
|
||||
* Show suggestion evidences such as score (authorScore, dateScore)
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-suggestion-evidences',
|
||||
styleUrls: [ './suggestion-evidences.component.scss' ],
|
@@ -4,13 +4,14 @@
|
||||
<div class="col-1 text-center align-self-center">
|
||||
<div class="">
|
||||
<input type="checkbox" class="form-check-input"
|
||||
[checked]="isSelected" (change)="changeSelected($event)"/>
|
||||
[checked]="isSelected" (change)="changeSelected($event)"
|
||||
[attr.aria-label]="object.display"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Total Score Panel -->
|
||||
<div class="col-2 text-center align-self-center">
|
||||
<div class="">
|
||||
<div><strong> {{'reciter.suggestion.totalScore' | translate}}</strong> </div>
|
||||
<div><strong> {{'suggestion.totalScore' | translate}}</strong> </div>
|
||||
<span class="suggestion-score">{{ object.score }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,6 +20,7 @@
|
||||
<!-- Object Preview -->
|
||||
<ds-item-search-result-list-element
|
||||
[showLabel]="false"
|
||||
[showThumbnails]="false"
|
||||
[object]="listableObject"
|
||||
[linkType]="0"
|
||||
></ds-item-search-result-list-element>
|
||||
@@ -29,7 +31,7 @@
|
||||
[isCollectionFixed]="isCollectionFixed"
|
||||
(approveAndImport)="onApproveAndImport($event)"
|
||||
(seeEvidences)="onSeeEvidences($event)"
|
||||
(notMineClicked)="onNotMine($event)"
|
||||
(ignoreSuggestionClicked)="onIgnoreSuggestion($event)"
|
||||
></ds-suggestion-actions>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,81 @@
|
||||
import { SuggestionListElementComponent } from './suggestion-list-element.component';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { mockSuggestionPublicationOne } from '../../shared/mocks/publication-claim.mock';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
||||
|
||||
describe('SuggestionListElementComponent', () => {
|
||||
let component: SuggestionListElementComponent;
|
||||
let fixture: ComponentFixture<SuggestionListElementComponent>;
|
||||
let scheduler: TestScheduler;
|
||||
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
declarations: [SuggestionListElementComponent],
|
||||
providers: [
|
||||
NgbModal
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents().then();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SuggestionListElementComponent);
|
||||
component = fixture.componentInstance;
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
component.object = mockSuggestionPublicationOne;
|
||||
});
|
||||
|
||||
describe('SuggestionListElementComponent test', () => {
|
||||
|
||||
it('should create', () => {
|
||||
scheduler.schedule(() => fixture.detectChanges());
|
||||
scheduler.flush();
|
||||
const expectedIndexableObject = Object.assign(new Item(), {
|
||||
id: mockSuggestionPublicationOne.id,
|
||||
metadata: mockSuggestionPublicationOne.metadata
|
||||
});
|
||||
expect(component).toBeTruthy();
|
||||
expect(component.listableObject.hitHighlights).toEqual({});
|
||||
expect(component.listableObject.indexableObject).toEqual(expectedIndexableObject);
|
||||
});
|
||||
|
||||
it('should check if has evidence', () => {
|
||||
expect(component.hasEvidences()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should set seeEvidences', () => {
|
||||
component.onSeeEvidences(true);
|
||||
expect(component.seeEvidence).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should emit selection', () => {
|
||||
spyOn(component.selected, 'next');
|
||||
component.changeSelected({target: { checked: true}});
|
||||
expect(component.selected.next).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
it('should emit for deletion', () => {
|
||||
spyOn(component.ignoreSuggestionClicked, 'emit');
|
||||
component.onIgnoreSuggestion('1234');
|
||||
expect(component.ignoreSuggestionClicked.emit).toHaveBeenCalledWith('1234');
|
||||
});
|
||||
|
||||
it('should emit for approve and import', () => {
|
||||
const event = {collectionId:'1234', suggestion: mockSuggestionPublicationOne};
|
||||
spyOn(component.approveAndImport, 'emit');
|
||||
component.onApproveAndImport(event);
|
||||
expect(component.approveAndImport.emit).toHaveBeenCalledWith(event);
|
||||
});
|
||||
});
|
||||
});
|
@@ -2,16 +2,29 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts
|
||||
import { fadeIn } from '../../../shared/animations/fade';
|
||||
import { Suggestion } from '../../../core/notifications/reciter-suggestions/models/suggestion.model';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { isNotEmpty } from '../../../shared/empty.util';
|
||||
========
|
||||
import { fadeIn } from '../../shared/animations/fade';
|
||||
import { Suggestion } from '../../core/suggestion-notifications/models/suggestion.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
>>>>>>>> main:src/app/notifications/suggestion-list-element/suggestion-list-element.component.ts
|
||||
|
||||
/**
|
||||
* A simple interface to unite a specific suggestion and the id of the chosen collection
|
||||
*/
|
||||
export interface SuggestionApproveAndImport {
|
||||
suggestion: Suggestion;
|
||||
collectionId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show all the suggestions by researcher
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-suggestion-list-item',
|
||||
styleUrls: ['./suggestion-list-element.component.scss'],
|
||||
@@ -33,7 +46,7 @@ export class SuggestionListElementComponent implements OnInit {
|
||||
/**
|
||||
* The component is used to Delete suggestion
|
||||
*/
|
||||
@Output() notMineClicked = new EventEmitter();
|
||||
@Output() ignoreSuggestionClicked = new EventEmitter();
|
||||
|
||||
/**
|
||||
* The component is used to approve & import
|
||||
@@ -69,8 +82,8 @@ export class SuggestionListElementComponent implements OnInit {
|
||||
/**
|
||||
* Delete the suggestion
|
||||
*/
|
||||
onNotMine(suggestionId: string) {
|
||||
this.notMineClicked.emit(suggestionId);
|
||||
onIgnoreSuggestion(suggestionId: string) {
|
||||
this.ignoreSuggestionClicked.emit(suggestionId);
|
||||
}
|
||||
|
||||
/**
|
@@ -1,9 +1,9 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h3 id="header" class="border-bottom pb-2">{{'reciter.suggestion.title'| translate}}</h3>
|
||||
<h1 id="header" class="border-bottom pb-2">{{'suggestion.title'| translate}}</h1>
|
||||
|
||||
<ds-loading class="container" *ngIf="(isTargetsLoading() | async)" message="{{'reciter.suggestion.loading' | translate}}"></ds-loading>
|
||||
<ds-loading class="container" *ngIf="(isTargetsLoading() | async)" message="{{'suggestion.loading' | translate}}"></ds-loading>
|
||||
<ds-pagination *ngIf="!(isTargetsLoading() | async)"
|
||||
[paginationOptions]="paginationConfig"
|
||||
[collectionSize]="(totalElements$ | async)"
|
||||
@@ -11,30 +11,31 @@
|
||||
[retainScrollPosition]="false"
|
||||
(paginationChange)="getSuggestionTargets()">
|
||||
|
||||
<ds-loading class="container" *ngIf="(isTargetsProcessing() | async)" message="'reciter.suggestion.loading' | translate"></ds-loading>
|
||||
<ds-loading class="container" *ngIf="(isTargetsProcessing() | async)" message="'suggestion.loading' | translate"></ds-loading>
|
||||
<ng-container *ngIf="!(isTargetsProcessing() | async)">
|
||||
<div *ngIf="!(targets$|async) || (targets$|async)?.length == 0" class="alert alert-info w-100 mb-2 mt-2" role="alert">
|
||||
{{'reciter.suggestion.noTargets' | translate}}
|
||||
{{'suggestion.noTargets' | translate}}
|
||||
</div>
|
||||
<div *ngIf="(targets$|async) && (targets$|async)?.length != 0" class="table-responsive mt-2">
|
||||
<table id="epeople" class="table table-striped table-hover table-bordered">
|
||||
<thead>
|
||||
<tr class="text-center">
|
||||
<th scope="col">{{'reciter.suggestion.table.name' | translate}}</th>
|
||||
<th scope="col">{{'reciter.suggestion.table.actions' | translate}}</th>
|
||||
<th scope="col">{{'suggestion.table.name' | translate}}</th>
|
||||
<th scope="col">{{'suggestion.table.actions' | translate}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let targetElement of (targets$ | async); let i = index" class="text-center">
|
||||
<td>
|
||||
<a target="_blank" [routerLink]="['/items', getTargetUuid(targetElement)]">{{targetElement.display}}</a>
|
||||
<a target="_blank" [routerLink]="['/entities/person/', getTargetUuid(targetElement)]">{{targetElement.display}}</a>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group edit-field">
|
||||
<button (click)="redirectToSuggestions(targetElement.id, targetElement.display)"
|
||||
<button (click)="redirectToSuggestions(targetElement.id)"
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
title="{{'reciter.suggestion.button.review' | translate }}">
|
||||
<span >{{'reciter.suggestion.button.review' | translate: { total: targetElement.total.toString() } }} </span>
|
||||
title="{{('suggestion.button.review.title' | translate: { total: targetElement.total.toString() }) +
|
||||
targetElement.display}}">
|
||||
<span>{{'suggestion.button.review' | translate: { total: targetElement.total.toString() } }} </span>
|
||||
<i class="fas fa-lightbulb"></i>
|
||||
</button>
|
||||
</div>
|
@@ -4,23 +4,27 @@ import { Router } from '@angular/router';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { distinctUntilChanged, take } from 'rxjs/operators';
|
||||
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts
|
||||
import { SuggestionTarget } from '../../../core/notifications/reciter-suggestions/models/suggestion-target.model';
|
||||
========
|
||||
import { SuggestionTarget } from '../../../core/suggestion-notifications/models/suggestion-target.model';
|
||||
>>>>>>>> main:src/app/notifications/suggestion-targets/publication-claim/publication-claim.component.ts
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { SuggestionTargetsStateService } from './suggestion-targets.state.service';
|
||||
import { SuggestionTargetsStateService } from '../suggestion-targets.state.service';
|
||||
import { getSuggestionPageRoute } from '../../../suggestions-page/suggestions-page-routing-paths';
|
||||
import { SuggestionsService } from '../suggestions.service';
|
||||
import { SuggestionsService } from '../../suggestions.service';
|
||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||
|
||||
/**
|
||||
* Component to display the Suggestion Target list.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-suggestion-target',
|
||||
templateUrl: './suggestion-targets.component.html',
|
||||
styleUrls: ['./suggestion-targets.component.scss'],
|
||||
selector: 'ds-publication-claim',
|
||||
templateUrl: './publication-claim.component.html',
|
||||
styleUrls: ['./publication-claim.component.scss'],
|
||||
})
|
||||
export class SuggestionTargetsComponent implements OnInit {
|
||||
export class PublicationClaimComponent implements OnInit {
|
||||
|
||||
/**
|
||||
* The source for which to list targets
|
||||
@@ -69,16 +73,11 @@ export class SuggestionTargetsComponent implements OnInit {
|
||||
* Component initialization.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.targets$ = this.suggestionTargetsStateService.getReciterSuggestionTargets();
|
||||
this.totalElements$ = this.suggestionTargetsStateService.getReciterSuggestionTargetsTotals();
|
||||
}
|
||||
this.targets$ = this.suggestionTargetsStateService.getSuggestionTargets();
|
||||
this.totalElements$ = this.suggestionTargetsStateService.getSuggestionTargetsTotals();
|
||||
|
||||
/**
|
||||
* First Suggestion Targets loading after view initialization.
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.subs.push(
|
||||
this.suggestionTargetsStateService.isReciterSuggestionTargetsLoaded().pipe(
|
||||
this.suggestionTargetsStateService.isSuggestionTargetsLoaded().pipe(
|
||||
take(1)
|
||||
).subscribe(() => {
|
||||
this.getSuggestionTargets();
|
||||
@@ -93,7 +92,7 @@ export class SuggestionTargetsComponent implements OnInit {
|
||||
* 'true' if the targets are loading, 'false' otherwise.
|
||||
*/
|
||||
public isTargetsLoading(): Observable<boolean> {
|
||||
return this.suggestionTargetsStateService.isReciterSuggestionTargetsLoading();
|
||||
return this.suggestionTargetsStateService.isSuggestionTargetsLoading();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,7 +102,7 @@ export class SuggestionTargetsComponent implements OnInit {
|
||||
* 'true' if there are operations running on the targets (ex.: a REST call), 'false' otherwise.
|
||||
*/
|
||||
public isTargetsProcessing(): Observable<boolean> {
|
||||
return this.suggestionTargetsStateService.isReciterSuggestionTargetsProcessing();
|
||||
return this.suggestionTargetsStateService.isSuggestionTargetsProcessing();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,10 +110,8 @@ export class SuggestionTargetsComponent implements OnInit {
|
||||
*
|
||||
* @param {string} id
|
||||
* the id of suggestion target
|
||||
* @param {string} name
|
||||
* the name of suggestion target
|
||||
*/
|
||||
public redirectToSuggestions(id: string, name: string) {
|
||||
public redirectToSuggestions(id: string) {
|
||||
this.router.navigate([getSuggestionPageRoute(id)]);
|
||||
}
|
||||
|
||||
@@ -136,8 +133,7 @@ export class SuggestionTargetsComponent implements OnInit {
|
||||
distinctUntilChanged(),
|
||||
take(1)
|
||||
).subscribe((options: PaginationComponentOptions) => {
|
||||
console.log('HELLO suggestion called!', options);
|
||||
this.suggestionTargetsStateService.dispatchRetrieveReciterSuggestionTargets(
|
||||
this.suggestionTargetsStateService.dispatchRetrieveSuggestionTargets(
|
||||
this.source,
|
||||
options.pageSize,
|
||||
options.currentPage
|
@@ -1,7 +1,12 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import { Action } from '@ngrx/store';
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts
|
||||
import { type } from '../../../shared/ngrx/type';
|
||||
import { SuggestionTarget } from '../../../core/notifications/reciter-suggestions/models/suggestion-target.model';
|
||||
========
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model';
|
||||
>>>>>>>> main:src/app/notifications/suggestion-targets/suggestion-targets.actions.ts
|
||||
|
||||
/**
|
||||
* For each action type in an action group, make a simple
|
@@ -9,15 +9,19 @@ import { of } from 'rxjs';
|
||||
import {
|
||||
AddTargetAction,
|
||||
AddUserSuggestionsAction,
|
||||
RefreshUserSuggestionsAction,
|
||||
RetrieveAllTargetsErrorAction,
|
||||
RetrieveTargetsBySourceAction,
|
||||
SuggestionTargetActionTypes,
|
||||
} from './suggestion-targets.actions';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { SuggestionsService } from '../suggestions.service';
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { SuggestionTarget } from '../../../core/notifications/reciter-suggestions/models/suggestion-target.model';
|
||||
========
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model';
|
||||
>>>>>>>> main:src/app/notifications/suggestion-targets/suggestion-targets.effects.ts
|
||||
|
||||
/**
|
||||
* Provides effect methods for the Suggestion Targets actions.
|
||||
@@ -55,7 +59,7 @@ export class SuggestionTargetsEffects {
|
||||
retrieveAllTargetsErrorAction$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(SuggestionTargetActionTypes.RETRIEVE_TARGETS_BY_SOURCE_ERROR),
|
||||
tap(() => {
|
||||
this.notificationsService.error(null, this.translate.get('reciter.suggestion.target.error.service.retrieve'));
|
||||
this.notificationsService.error(null, this.translate.get('suggestion.target.error.service.retrieve'));
|
||||
})
|
||||
), { dispatch: false });
|
||||
|
||||
@@ -64,7 +68,7 @@ export class SuggestionTargetsEffects {
|
||||
*/
|
||||
refreshUserSuggestionsAction$ = createEffect(() => this.actions$.pipe(
|
||||
ofType(SuggestionTargetActionTypes.REFRESH_USER_SUGGESTIONS),
|
||||
switchMap((action: RefreshUserSuggestionsAction) => {
|
||||
switchMap(() => {
|
||||
return this.store$.select((state: any) => state.core.auth.userId)
|
||||
.pipe(
|
||||
switchMap((userId: string) => {
|
||||
@@ -76,7 +80,12 @@ export class SuggestionTargetsEffects {
|
||||
}),
|
||||
catchError((errors) => of(errors))
|
||||
);
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts
|
||||
})), { dispatch: false });
|
||||
========
|
||||
}))
|
||||
);
|
||||
>>>>>>>> main:src/app/notifications/suggestion-targets/suggestion-targets.effects.ts
|
||||
|
||||
/**
|
||||
* Initialize the effect class variables.
|
@@ -1,5 +1,9 @@
|
||||
import { SuggestionTargetActionTypes, SuggestionTargetsActions } from './suggestion-targets.actions';
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts
|
||||
import { SuggestionTarget } from '../../../core/notifications/reciter-suggestions/models/suggestion-target.model';
|
||||
========
|
||||
import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model';
|
||||
>>>>>>>> main:src/app/notifications/suggestion-targets/suggestion-targets.reducer.ts
|
||||
|
||||
/**
|
||||
* The interface representing the OpenAIRE suggestion targets state.
|
@@ -7,20 +7,28 @@ import { map } from 'rxjs/operators';
|
||||
import {
|
||||
getCurrentUserSuggestionTargetsSelector,
|
||||
getCurrentUserSuggestionTargetsVisitedSelector,
|
||||
getReciterSuggestionTargetCurrentPageSelector,
|
||||
getReciterSuggestionTargetTotalsSelector,
|
||||
isReciterSuggestionTargetLoadedSelector,
|
||||
getSuggestionTargetCurrentPageSelector,
|
||||
getSuggestionTargetTotalsSelector,
|
||||
isSuggestionTargetLoadedSelector,
|
||||
isReciterSuggestionTargetProcessingSelector,
|
||||
reciterSuggestionTargetObjectSelector
|
||||
suggestionTargetObjectSelector
|
||||
} from '../selectors';
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts
|
||||
import { SuggestionTarget } from '../../../core/notifications/reciter-suggestions/models/suggestion-target.model';
|
||||
========
|
||||
import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model';
|
||||
>>>>>>>> main:src/app/notifications/suggestion-targets/suggestion-targets.state.service.ts
|
||||
import {
|
||||
ClearSuggestionTargetsAction,
|
||||
MarkUserSuggestionsAsVisitedAction,
|
||||
RefreshUserSuggestionsAction,
|
||||
RetrieveTargetsBySourceAction
|
||||
} from './suggestion-targets.actions';
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts
|
||||
import { SuggestionNotificationsState } from '../../notifications.reducer';
|
||||
========
|
||||
import { SuggestionNotificationsState } from '../../notifications/notifications.reducer';
|
||||
>>>>>>>> main:src/app/notifications/suggestion-targets/suggestion-targets.state.service.ts
|
||||
|
||||
/**
|
||||
* The service handling the Suggestion targets State.
|
||||
@@ -35,80 +43,80 @@ export class SuggestionTargetsStateService {
|
||||
constructor(private store: Store<SuggestionNotificationsState>) { }
|
||||
|
||||
/**
|
||||
* Returns the list of Reciter Suggestion Targets from the state.
|
||||
* Returns the list of Suggestion Targets from the state.
|
||||
*
|
||||
* @return Observable<OpenaireReciterSuggestionTarget>
|
||||
* The list of Reciter Suggestion Targets.
|
||||
* @return Observable<SuggestionTarget>
|
||||
* The list of Suggestion Targets.
|
||||
*/
|
||||
public getReciterSuggestionTargets(): Observable<SuggestionTarget[]> {
|
||||
return this.store.pipe(select(reciterSuggestionTargetObjectSelector()));
|
||||
public getSuggestionTargets(): Observable<SuggestionTarget[]> {
|
||||
return this.store.pipe(select(suggestionTargetObjectSelector()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about the loading status of the Reciter Suggestion Targets (if it's running or not).
|
||||
* Returns the information about the loading status of the Suggestion Targets (if it's running or not).
|
||||
*
|
||||
* @return Observable<boolean>
|
||||
* 'true' if the targets are loading, 'false' otherwise.
|
||||
*/
|
||||
public isReciterSuggestionTargetsLoading(): Observable<boolean> {
|
||||
public isSuggestionTargetsLoading(): Observable<boolean> {
|
||||
return this.store.pipe(
|
||||
select(isReciterSuggestionTargetLoadedSelector),
|
||||
select(isSuggestionTargetLoadedSelector),
|
||||
map((loaded: boolean) => !loaded)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about the loading status of the Reciter Suggestion Targets (whether or not they were loaded).
|
||||
* Returns the information about the loading status of the Suggestion Targets (whether or not they were loaded).
|
||||
*
|
||||
* @return Observable<boolean>
|
||||
* 'true' if the targets are loaded, 'false' otherwise.
|
||||
*/
|
||||
public isReciterSuggestionTargetsLoaded(): Observable<boolean> {
|
||||
return this.store.pipe(select(isReciterSuggestionTargetLoadedSelector));
|
||||
public isSuggestionTargetsLoaded(): Observable<boolean> {
|
||||
return this.store.pipe(select(isSuggestionTargetLoadedSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the information about the processing status of the Reciter Suggestion Targets (if it's running or not).
|
||||
* Returns the information about the processing status of the Suggestion Targets (if it's running or not).
|
||||
*
|
||||
* @return Observable<boolean>
|
||||
* 'true' if there are operations running on the targets (ex.: a REST call), 'false' otherwise.
|
||||
*/
|
||||
public isReciterSuggestionTargetsProcessing(): Observable<boolean> {
|
||||
public isSuggestionTargetsProcessing(): Observable<boolean> {
|
||||
return this.store.pipe(select(isReciterSuggestionTargetProcessingSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, from the state, the total available pages of the Reciter Suggestion Targets.
|
||||
* Returns, from the state, the total available pages of the Suggestion Targets.
|
||||
*
|
||||
* @return Observable<number>
|
||||
* The number of the Reciter Suggestion Targets pages.
|
||||
* The number of the Suggestion Targets pages.
|
||||
*/
|
||||
public getReciterSuggestionTargetsTotalPages(): Observable<number> {
|
||||
return this.store.pipe(select(getReciterSuggestionTargetTotalsSelector));
|
||||
public getSuggestionTargetsTotalPages(): Observable<number> {
|
||||
return this.store.pipe(select(getSuggestionTargetTotalsSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current page of the Reciter Suggestion Targets, from the state.
|
||||
* Returns the current page of the Suggestion Targets, from the state.
|
||||
*
|
||||
* @return Observable<number>
|
||||
* The number of the current Reciter Suggestion Targets page.
|
||||
* The number of the current Suggestion Targets page.
|
||||
*/
|
||||
public getReciterSuggestionTargetsCurrentPage(): Observable<number> {
|
||||
return this.store.pipe(select(getReciterSuggestionTargetCurrentPageSelector));
|
||||
public getSuggestionTargetsCurrentPage(): Observable<number> {
|
||||
return this.store.pipe(select(getSuggestionTargetCurrentPageSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of the Reciter Suggestion Targets.
|
||||
* Returns the total number of the Suggestion Targets.
|
||||
*
|
||||
* @return Observable<number>
|
||||
* The number of the Reciter Suggestion Targets.
|
||||
* The number of the Suggestion Targets.
|
||||
*/
|
||||
public getReciterSuggestionTargetsTotals(): Observable<number> {
|
||||
return this.store.pipe(select(getReciterSuggestionTargetTotalsSelector));
|
||||
public getSuggestionTargetsTotals(): Observable<number> {
|
||||
return this.store.pipe(select(getSuggestionTargetTotalsSelector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a request to change the Reciter Suggestion Targets state, retrieving the targets from the server.
|
||||
* Dispatch a request to change the Suggestion Targets state, retrieving the targets from the server.
|
||||
*
|
||||
* @param source
|
||||
* the source for which to retrieve suggestion targets
|
||||
@@ -117,15 +125,15 @@ export class SuggestionTargetsStateService {
|
||||
* @param currentPage
|
||||
* The number of the current page.
|
||||
*/
|
||||
public dispatchRetrieveReciterSuggestionTargets(source: string, elementsPerPage: number, currentPage: number): void {
|
||||
public dispatchRetrieveSuggestionTargets(source: string, elementsPerPage: number, currentPage: number): void {
|
||||
this.store.dispatch(new RetrieveTargetsBySourceAction(source, elementsPerPage, currentPage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, from the state, the reciter suggestion targets for the current user.
|
||||
* Returns, from the state, the suggestion targets for the current user.
|
||||
*
|
||||
* @return Observable<OpenaireReciterSuggestionTarget>
|
||||
* The Reciter Suggestion Targets object.
|
||||
* The Suggestion Targets object.
|
||||
*/
|
||||
public getCurrentUserSuggestionTargets(): Observable<SuggestionTarget[]> {
|
||||
return this.store.pipe(select(getCurrentUserSuggestionTargetsSelector));
|
183
src/app/notifications/suggestion.service.spec.ts
Normal file
183
src/app/notifications/suggestion.service.spec.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { SuggestionsService } from './suggestions.service';
|
||||
import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service';
|
||||
import {
|
||||
SuggestionsDataService
|
||||
} from '../core/suggestion-notifications/suggestions-data.service';
|
||||
|
||||
|
||||
import {
|
||||
SuggestionTargetDataService
|
||||
} from '../core/suggestion-notifications/target/suggestion-target-data.service';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { FindListOptions } from '../core/data/find-list-options.model';
|
||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
||||
import { mockSuggestionPublicationOne } from '../shared/mocks/publication-claim.mock';
|
||||
import { ResourceType } from '../core/shared/resource-type';
|
||||
|
||||
|
||||
import {
|
||||
SuggestionTarget
|
||||
} from '../core/suggestion-notifications/models/suggestion-target.model';
|
||||
|
||||
describe('SuggestionsService test', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: SuggestionsService;
|
||||
let researcherProfileService: ResearcherProfileDataService;
|
||||
let suggestionsDataService: SuggestionsDataService;
|
||||
let suggestionTargetDataService: SuggestionTargetDataService;
|
||||
let translateService: any = {
|
||||
instant: (str) => str,
|
||||
};
|
||||
const suggestionTarget = {
|
||||
id: '1234:4321',
|
||||
display: 'display',
|
||||
source: 'source',
|
||||
total: 8,
|
||||
type: new ResourceType('suggestiontarget')
|
||||
};
|
||||
|
||||
const mockResercherProfile = {
|
||||
id: '1234',
|
||||
uuid: '1234',
|
||||
visible: true
|
||||
};
|
||||
|
||||
function initTestService() {
|
||||
return new SuggestionsService(
|
||||
researcherProfileService,
|
||||
suggestionsDataService,
|
||||
suggestionTargetDataService,
|
||||
translateService
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
researcherProfileService = jasmine.createSpyObj('researcherProfileService', {
|
||||
findById: createSuccessfulRemoteDataObject$(mockResercherProfile as ResearcherProfile),
|
||||
findRelatedItemId: observableOf('1234'),
|
||||
});
|
||||
|
||||
suggestionTargetDataService = jasmine.createSpyObj('suggestionTargetsDataService', {
|
||||
getTargets: observableOf(null),
|
||||
findById: observableOf(null),
|
||||
});
|
||||
|
||||
suggestionsDataService = jasmine.createSpyObj('suggestionsDataService', {
|
||||
searchBy: observableOf(null),
|
||||
delete: observableOf(null),
|
||||
deleteSuggestion: createSuccessfulRemoteDataObject$({}),
|
||||
getSuggestionsByTargetAndSource : observableOf(null),
|
||||
clearSuggestionRequests : null,
|
||||
getTargetsByUser: observableOf(null),
|
||||
});
|
||||
|
||||
service = initTestService();
|
||||
|
||||
});
|
||||
|
||||
describe('Suggestion service', () => {
|
||||
it('should create', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
it('should get targets', () => {
|
||||
const sortOptions = new SortOptions('display', SortDirection.ASC);
|
||||
const findListOptions: FindListOptions = {
|
||||
elementsPerPage: 10,
|
||||
currentPage: 1,
|
||||
sort: sortOptions
|
||||
};
|
||||
service.getTargets('source', 10, 1);
|
||||
expect(suggestionTargetDataService.getTargets).toHaveBeenCalledWith('source', findListOptions);
|
||||
});
|
||||
|
||||
it('should get suggestions', () => {
|
||||
const sortOptions = new SortOptions('display', SortDirection.ASC);
|
||||
const findListOptions: FindListOptions = {
|
||||
elementsPerPage: 10,
|
||||
currentPage: 1,
|
||||
sort: sortOptions
|
||||
};
|
||||
service.getSuggestions('source:target', 10, 1, sortOptions);
|
||||
expect(suggestionsDataService.getSuggestionsByTargetAndSource).toHaveBeenCalledWith('target', 'source', findListOptions);
|
||||
});
|
||||
|
||||
it('should clear suggestions', () => {
|
||||
service.clearSuggestionRequests();
|
||||
expect(suggestionsDataService.clearSuggestionRequests).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should delete reviewed suggestion', () => {
|
||||
service.deleteReviewedSuggestion('1234');
|
||||
expect(suggestionsDataService.deleteSuggestion).toHaveBeenCalledWith('1234');
|
||||
});
|
||||
|
||||
it('should retrieve current user suggestions', () => {
|
||||
service.retrieveCurrentUserSuggestions('1234');
|
||||
expect(researcherProfileService.findById).toHaveBeenCalledWith('1234', true);
|
||||
});
|
||||
|
||||
it('should approve and import suggestion', () => {
|
||||
spyOn(service, 'resolveCollectionId');
|
||||
const workspaceitemService = {importExternalSourceEntry: (x,y) => observableOf(null)};
|
||||
service.approveAndImport(workspaceitemService as unknown as WorkspaceitemDataService, mockSuggestionPublicationOne, '1234');
|
||||
expect(service.resolveCollectionId).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should approve and import suggestions', () => {
|
||||
spyOn(service, 'approveAndImport');
|
||||
const workspaceitemService = {importExternalSourceEntry: (x,y) => observableOf(null)};
|
||||
service.approveAndImportMultiple(workspaceitemService as unknown as WorkspaceitemDataService, [mockSuggestionPublicationOne], '1234');
|
||||
expect(service.approveAndImport).toHaveBeenCalledWith(workspaceitemService as unknown as WorkspaceitemDataService, mockSuggestionPublicationOne, '1234');
|
||||
});
|
||||
|
||||
it('should delete suggestion', () => {
|
||||
spyOn(service, 'deleteReviewedSuggestion').and.returnValue(createSuccessfulRemoteDataObject$({}));
|
||||
service.ignoreSuggestion('1234');
|
||||
expect(service.deleteReviewedSuggestion).toHaveBeenCalledWith('1234');
|
||||
});
|
||||
|
||||
it('should delete suggestions', () => {
|
||||
spyOn(service, 'ignoreSuggestion');
|
||||
service.ignoreSuggestionMultiple([mockSuggestionPublicationOne]);
|
||||
expect(service.ignoreSuggestion).toHaveBeenCalledWith(mockSuggestionPublicationOne.id);
|
||||
});
|
||||
|
||||
it('should get target Uuid', () => {
|
||||
expect(service.getTargetUuid(suggestionTarget as SuggestionTarget)).toBe('4321');
|
||||
expect(service.getTargetUuid({id: ''} as SuggestionTarget)).toBe(null);
|
||||
});
|
||||
|
||||
it('should get suggestion interpolation', () => {
|
||||
const result = service.getNotificationSuggestionInterpolation(suggestionTarget as SuggestionTarget);
|
||||
expect(result.count).toEqual(suggestionTarget.total);
|
||||
expect(result.source).toEqual('suggestion.source.' + suggestionTarget.source);
|
||||
expect(result.type).toEqual('suggestion.type.' + suggestionTarget.source);
|
||||
expect(result.suggestionId).toEqual(suggestionTarget.id);
|
||||
expect(result.displayName).toEqual(suggestionTarget.display);
|
||||
});
|
||||
|
||||
it('should translate suggestion type', () => {
|
||||
expect(service.translateSuggestionType('source')).toEqual('suggestion.type.source');
|
||||
});
|
||||
|
||||
it('should translate suggestion source', () => {
|
||||
expect(service.translateSuggestionSource('source')).toEqual('suggestion.source.source');
|
||||
});
|
||||
|
||||
it('should resolve collection id', () => {
|
||||
expect(service.resolveCollectionId(mockSuggestionPublicationOne, '1234')).toEqual('1234');
|
||||
});
|
||||
|
||||
it('should check if collection is fixed', () => {
|
||||
expect(service.isCollectionFixed([mockSuggestionPublicationOne])).toBeFalse();
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,9 @@
|
||||
<ng-container *ngIf="(suggestionsRD$ | async) as suggestions">
|
||||
<ng-container *ngFor="let suggestion of suggestions" class="alert alert-info">
|
||||
<div class="alert alert-success d-block" *ngIf="suggestion.total > 0">
|
||||
<div [innerHTML]="'notification.suggestion' | translate: getNotificationSuggestionInterpolation(suggestion)"></div>
|
||||
{{ 'notification.suggestion.please' | translate }}
|
||||
<a [routerLink]="getNotificationSuggestionInterpolation(suggestion).url"> {{ 'notification.suggestion.review' | translate}} </a>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
@@ -1,11 +1,17 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts
|
||||
import { SuggestionTarget } from '../../../core/notifications/reciter-suggestions/models/suggestion-target.model';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
========
|
||||
import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model';
|
||||
>>>>>>>> main:src/app/notifications/suggestions-notification/suggestions-notification.component.ts
|
||||
import { SuggestionTargetsStateService } from '../suggestion-targets/suggestion-targets.state.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { SuggestionsService } from '../suggestions.service';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Show suggestions notification, used on myDSpace and Profile pages
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-suggestions-notification',
|
||||
templateUrl: './suggestions-notification.component.html',
|
||||
@@ -13,22 +19,19 @@ import { Observable } from 'rxjs';
|
||||
})
|
||||
export class SuggestionsNotificationComponent implements OnInit {
|
||||
|
||||
labelPrefix = 'mydspace.';
|
||||
|
||||
/**
|
||||
* The user suggestion targets.
|
||||
*/
|
||||
suggestionsRD$: Observable<SuggestionTarget[]>;
|
||||
|
||||
constructor(
|
||||
private translateService: TranslateService,
|
||||
private reciterSuggestionStateService: SuggestionTargetsStateService,
|
||||
private notificationsService: NotificationsService,
|
||||
private suggestionTargetsStateService: SuggestionTargetsStateService,
|
||||
private suggestionsService: SuggestionsService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.suggestionsRD$ = this.reciterSuggestionStateService.getCurrentUserSuggestionTargets();
|
||||
this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction();
|
||||
this.suggestionsRD$ = this.suggestionTargetsStateService.getCurrentUserSuggestionTargets();
|
||||
}
|
||||
|
||||
/**
|
@@ -0,0 +1,27 @@
|
||||
<div class="notifications-wrapper position-fixed top right" *ngIf="suggestionsRD$ | async">
|
||||
<div class="notification alert alert-success alert-dismissible m-3 shadow" role="alert">
|
||||
<button (click)="removePopup()"
|
||||
type="button" class="close pt-0 pr-1 pl-0 pb-0" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-flex flex-column justify-content-center align-items-center">
|
||||
<div class="notification-icon d-flex justify-content-center"><i class="fas fa-2x fa-check-circle"></i></div>
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-content-center align-content-stretch text-left p-2">
|
||||
<ng-container *ngIf="(suggestionsRD$ | async) as suggestions">
|
||||
<ng-container *ngFor="let suggestion of suggestions" class="alert alert-info">
|
||||
<div *ngIf="suggestion.total > 0">
|
||||
<div [innerHTML]="'notification.suggestion' | translate: getNotificationSuggestionInterpolation(suggestion)"></div>
|
||||
{{ 'notification.suggestion.please' | translate }}
|
||||
<a [routerLink]="getNotificationSuggestionInterpolation(suggestion).url"> {{ 'notification.suggestion.review' | translate}} </a>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@@ -3,11 +3,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SuggestionsPopupComponent } from './suggestions-popup.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SuggestionTargetsStateService } from '../suggestion-targets/suggestion-targets.state.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { mockSuggestionTargetsObjectOne } from '../../../shared/mocks/reciter-suggestion-targets.mock';
|
||||
import { mockSuggestionTargetsObjectOne } from '../../shared/mocks/publication-claim-targets.mock';
|
||||
import { SuggestionsService } from '../suggestions.service';
|
||||
|
||||
describe('SuggestionsPopupComponent', () => {
|
||||
@@ -34,7 +32,6 @@ describe('SuggestionsPopupComponent', () => {
|
||||
providers: [
|
||||
{ provide: SuggestionTargetsStateService, useValue: suggestionStateService },
|
||||
{ provide: SuggestionsService, useValue: suggestionService },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
|
||||
@@ -61,10 +58,10 @@ describe('SuggestionsPopupComponent', () => {
|
||||
describe('when there are publication suggestions', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
suggestionStateService.hasUserVisitedSuggestions.and.returnValue(observableOf(false));
|
||||
suggestionStateService.getCurrentUserSuggestionTargets.and.returnValue(observableOf([mockSuggestionTargetsObjectOne]));
|
||||
suggestionStateService.dispatchMarkUserSuggestionsAsVisitedAction.and.returnValue(observableOf(null));
|
||||
suggestionStateService.dispatchRefreshUserSuggestionsAction.and.returnValue(observableOf(null));
|
||||
|
||||
fixture = TestBed.createComponent(SuggestionsPopupComponent);
|
||||
component = fixture.componentInstance;
|
||||
@@ -72,7 +69,7 @@ describe('SuggestionsPopupComponent', () => {
|
||||
});
|
||||
|
||||
it('should show a notification when new publication suggestions are available', () => {
|
||||
expect((component as any).notificationsService.success).toHaveBeenCalled();
|
||||
expect(suggestionStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled();
|
||||
expect(suggestionStateService.dispatchMarkUserSuggestionsAsVisitedAction).toHaveBeenCalled();
|
||||
});
|
||||
|
@@ -0,0 +1,82 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { SuggestionTargetsStateService } from '../suggestion-targets/suggestion-targets.state.service';
|
||||
import { SuggestionsService } from '../suggestions.service';
|
||||
import { take, takeUntil } from 'rxjs/operators';
|
||||
import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { combineLatest, Observable, of, Subject } from 'rxjs';
|
||||
import { trigger } from '@angular/animations';
|
||||
|
||||
|
||||
import { fromTopEnter } from '../../shared/animations/fromTop';
|
||||
|
||||
/**
|
||||
* Show suggestions on a popover window, used on the homepage
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-suggestions-popup',
|
||||
templateUrl: './suggestions-popup.component.html',
|
||||
styleUrls: ['./suggestions-popup.component.scss'],
|
||||
animations: [
|
||||
trigger('enterLeave', [
|
||||
fromTopEnter
|
||||
])
|
||||
],
|
||||
})
|
||||
export class SuggestionsPopupComponent implements OnInit, OnDestroy {
|
||||
|
||||
labelPrefix = 'notification.';
|
||||
|
||||
subscription;
|
||||
|
||||
suggestionsRD$: Observable<SuggestionTarget[]>;
|
||||
|
||||
|
||||
constructor(
|
||||
private suggestionTargetsStateService: SuggestionTargetsStateService,
|
||||
private suggestionsService: SuggestionsService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.initializePopup();
|
||||
}
|
||||
|
||||
public initializePopup() {
|
||||
const notifier = new Subject();
|
||||
this.subscription = combineLatest([
|
||||
this.suggestionTargetsStateService.getCurrentUserSuggestionTargets().pipe(take(2)),
|
||||
this.suggestionTargetsStateService.hasUserVisitedSuggestions()
|
||||
]).pipe(takeUntil(notifier)).subscribe(([suggestions, visited]) => {
|
||||
this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction();
|
||||
if (isNotEmpty(suggestions)) {
|
||||
if (!visited) {
|
||||
this.suggestionsRD$ = of(suggestions);
|
||||
this.suggestionTargetsStateService.dispatchMarkUserSuggestionsAsVisitedAction();
|
||||
notifier.next(null);
|
||||
notifier.complete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolated params to build the notification suggestions notification.
|
||||
* @param suggestionTarget
|
||||
*/
|
||||
public getNotificationSuggestionInterpolation(suggestionTarget: SuggestionTarget): any {
|
||||
return this.suggestionsService.getNotificationSuggestionInterpolation(suggestionTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide popup from view
|
||||
*/
|
||||
public removePopup() {
|
||||
this.suggestionsRD$ = null;
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
import { of, forkJoin, Observable } from 'rxjs';
|
||||
import { catchError, map, mergeMap, take } from 'rxjs/operators';
|
||||
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestions.service.ts
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
@@ -10,11 +11,20 @@ import { SuggestionTarget } from '../../core/notifications/reciter-suggestions/m
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model';
|
||||
========
|
||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { PaginatedList } from '../core/data/paginated-list.model';
|
||||
import { SuggestionTarget } from '../core/suggestion-notifications/models/suggestion-target.model';
|
||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { ResearcherProfile } from '../core/profile/model/researcher-profile.model';
|
||||
>>>>>>>> main:src/app/notifications/suggestions.service.ts
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
getFinishedRemoteData,
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getFirstSucceededRemoteListPayload
|
||||
<<<<<<<< HEAD:src/app/notifications/reciter-suggestions/suggestions.service.ts
|
||||
} from '../../core/shared/operators';
|
||||
import { Suggestion } from '../../core/notifications/reciter-suggestions/models/suggestion.model';
|
||||
import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service';
|
||||
@@ -34,7 +44,29 @@ import {
|
||||
import {
|
||||
SuggestionsDataService
|
||||
} from '../../core/notifications/reciter-suggestions/suggestions-data.service';
|
||||
========
|
||||
} from '../core/shared/operators';
|
||||
import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model';
|
||||
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { NoContent } from '../core/shared/NoContent.model';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { WorkspaceItem } from '../core/submission/models/workspaceitem.model';
|
||||
import {FindListOptions} from '../core/data/find-list-options.model';
|
||||
import {SuggestionConfig} from '../../config/suggestion-config.interfaces';
|
||||
import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service';
|
||||
import {
|
||||
SuggestionTargetDataService
|
||||
} from '../core/suggestion-notifications/target/suggestion-target-data.service';
|
||||
import {
|
||||
SuggestionsDataService
|
||||
} from '../core/suggestion-notifications/suggestions-data.service';
|
||||
import { getSuggestionPageRoute } from '../suggestions-page/suggestions-page-routing-paths';
|
||||
>>>>>>>> main:src/app/notifications/suggestions.service.ts
|
||||
|
||||
/**
|
||||
* useful for multiple approvals and ignores operation
|
||||
* */
|
||||
export interface SuggestionBulkResult {
|
||||
success: number;
|
||||
fails: number;
|
||||
@@ -48,17 +80,14 @@ export class SuggestionsService {
|
||||
|
||||
/**
|
||||
* Initialize the service variables.
|
||||
* @param {AuthService} authService
|
||||
* @param {ResearcherProfileDataService} researcherProfileService
|
||||
* @param {SuggestionSourceDataService} suggestionSourceDataService
|
||||
* @param {SuggestionTargetDataService} suggestionTargetDataService
|
||||
* @param {SuggestionsDataService} suggestionsDataService
|
||||
* @param translateService
|
||||
*/
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private researcherProfileService: ResearcherProfileDataService,
|
||||
private suggestionsDataService: SuggestionsDataService,
|
||||
private suggestionSourceDataService: SuggestionSourceDataService,
|
||||
private suggestionTargetDataService: SuggestionTargetDataService,
|
||||
private translateService: TranslateService
|
||||
) {
|
||||
@@ -157,7 +186,7 @@ export class SuggestionsService {
|
||||
* The EPerson id for which to retrieve suggestion targets
|
||||
*/
|
||||
public retrieveCurrentUserSuggestions(userUuid: string): Observable<SuggestionTarget[]> {
|
||||
return this.researcherProfileService.findById(userUuid).pipe(
|
||||
return this.researcherProfileService.findById(userUuid, true).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
mergeMap((profile: ResearcherProfile) => {
|
||||
if (isNotEmpty(profile)) {
|
||||
@@ -191,7 +220,7 @@ export class SuggestionsService {
|
||||
return workspaceitemService.importExternalSourceEntry(suggestion.externalSourceUri, resolvedCollectionId)
|
||||
.pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
catchError((error) => of(null))
|
||||
catchError(() => of(null))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -199,9 +228,9 @@ export class SuggestionsService {
|
||||
* Perform the delete operation over a single suggestion.
|
||||
* @param suggestionId
|
||||
*/
|
||||
public notMine(suggestionId): Observable<RemoteData<NoContent>> {
|
||||
public ignoreSuggestion(suggestionId): Observable<RemoteData<NoContent>> {
|
||||
return this.deleteReviewedSuggestion(suggestionId).pipe(
|
||||
catchError((error) => of(null))
|
||||
catchError(() => of(null))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -226,11 +255,11 @@ export class SuggestionsService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a bulk notMine operation.
|
||||
* Perform a bulk ignoreSuggestion operation.
|
||||
* @param suggestions the array containing the suggestions
|
||||
*/
|
||||
public notMineMultiple(suggestions: Suggestion[]): Observable<SuggestionBulkResult> {
|
||||
return forkJoin(suggestions.map((suggestion: Suggestion) => this.notMine(suggestion.id)))
|
||||
public ignoreSuggestionMultiple(suggestions: Suggestion[]): Observable<SuggestionBulkResult> {
|
||||
return forkJoin(suggestions.map((suggestion: Suggestion) => this.ignoreSuggestion(suggestion.id)))
|
||||
.pipe(map((results: RemoteData<NoContent>[]) => {
|
||||
return {
|
||||
success: results.filter((result) => result != null).length,
|
||||
@@ -260,16 +289,17 @@ export class SuggestionsService {
|
||||
source: this.translateService.instant(this.translateSuggestionSource(suggestionTarget.source)),
|
||||
type: this.translateService.instant(this.translateSuggestionType(suggestionTarget.source)),
|
||||
suggestionId: suggestionTarget.id,
|
||||
displayName: suggestionTarget.display
|
||||
displayName: suggestionTarget.display,
|
||||
url: getSuggestionPageRoute(suggestionTarget.id)
|
||||
};
|
||||
}
|
||||
|
||||
public translateSuggestionType(source: string): string {
|
||||
return 'reciter.suggestion.type.' + source;
|
||||
return 'suggestion.type.' + source;
|
||||
}
|
||||
|
||||
public translateSuggestionSource(source: string): string {
|
||||
return 'reciter.suggestion.source.' + source;
|
||||
return 'suggestion.source.' + source;
|
||||
}
|
||||
|
||||
/**
|
@@ -14,16 +14,15 @@ import { UiSwitchModule } from 'ngx-ui-switch';
|
||||
import { ProfileClaimItemModalComponent } from './profile-claim-item-modal/profile-claim-item-modal.component';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ProfilePageRoutingModule,
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
FormModule,
|
||||
UiSwitchModule,
|
||||
NotificationsModule
|
||||
],
|
||||
imports: [
|
||||
ProfilePageRoutingModule,
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
FormModule,
|
||||
UiSwitchModule,
|
||||
NotificationsModule
|
||||
],
|
||||
exports: [
|
||||
ProfilePageComponent,
|
||||
ThemedProfilePageComponent,
|
||||
|
@@ -4,7 +4,11 @@ import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/r
|
||||
/**
|
||||
* Interface for the route parameters.
|
||||
*/
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service.ts
|
||||
export interface NotificationsSuggestionTargetsPageParams {
|
||||
========
|
||||
export interface AdminNotificationsPublicationClaimPageParams {
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts
|
||||
pageId?: string;
|
||||
pageSize?: number;
|
||||
currentPage?: number;
|
||||
@@ -14,7 +18,11 @@ export interface NotificationsSuggestionTargetsPageParams {
|
||||
* This class represents a resolver that retrieve the route data before the route is activated.
|
||||
*/
|
||||
@Injectable()
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service.ts
|
||||
export class NotificationsSuggestionTargetsPageResolver implements Resolve<NotificationsSuggestionTargetsPageParams> {
|
||||
========
|
||||
export class AdminNotificationsPublicationClaimPageResolver implements Resolve<AdminNotificationsPublicationClaimPageParams> {
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts
|
||||
|
||||
/**
|
||||
* Method for resolving the parameters in the current route.
|
||||
@@ -22,7 +30,11 @@ export class NotificationsSuggestionTargetsPageResolver implements Resolve<Notif
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns AdminNotificationsSuggestionTargetsPageParams Emits the route parameters
|
||||
*/
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page-resolver.service.ts
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): NotificationsSuggestionTargetsPageParams {
|
||||
========
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): AdminNotificationsPublicationClaimPageParams {
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts
|
||||
return {
|
||||
pageId: route.queryParams.pageId,
|
||||
pageSize: parseInt(route.queryParams.pageSize, 10),
|
||||
|
@@ -1,13 +1,23 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page.component.spec.ts
|
||||
import { NotificationsSuggestionTargetsPageComponent } from './notifications-suggestion-targets-page.component';
|
||||
========
|
||||
import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page.component';
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page.component.spec.ts
|
||||
describe('NotificationsSuggestionTargetsPageComponent', () => {
|
||||
let component: NotificationsSuggestionTargetsPageComponent;
|
||||
let fixture: ComponentFixture<NotificationsSuggestionTargetsPageComponent>;
|
||||
========
|
||||
describe('AdminNotificationsPublicationClaimPageComponent', () => {
|
||||
let component: AdminNotificationsPublicationClaimPageComponent;
|
||||
let fixture: ComponentFixture<AdminNotificationsPublicationClaimPageComponent>;
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -16,10 +26,17 @@ describe('NotificationsSuggestionTargetsPageComponent', () => {
|
||||
TranslateModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page.component.spec.ts
|
||||
NotificationsSuggestionTargetsPageComponent
|
||||
],
|
||||
providers: [
|
||||
NotificationsSuggestionTargetsPageComponent
|
||||
========
|
||||
AdminNotificationsPublicationClaimPageComponent
|
||||
],
|
||||
providers: [
|
||||
AdminNotificationsPublicationClaimPageComponent
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
@@ -27,7 +44,11 @@ describe('NotificationsSuggestionTargetsPageComponent', () => {
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
<<<<<<<< HEAD:src/app/quality-assurance-notifications-pages/notifications-suggestion-targets-page/notifications-suggestion-targets-page.component.spec.ts
|
||||
fixture = TestBed.createComponent(NotificationsSuggestionTargetsPageComponent);
|
||||
========
|
||||
fixture = TestBed.createComponent(AdminNotificationsPublicationClaimPageComponent);
|
||||
>>>>>>>> main:src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -5,13 +5,14 @@ import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { DSOSelectorModalWrapperComponent, SelectorActionType } from './dso-selector-modal-wrapper.component';
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { DSOSelectorComponent } from '../dso-selector/dso-selector.component';
|
||||
import { MockComponent } from 'ng-mocks';
|
||||
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||
import { createSuccessfulRemoteDataObject } from '../../remote-data.utils';
|
||||
import { hasValue } from '../../empty.util';
|
||||
|
||||
describe('DSOSelectorModalWrapperComponent', () => {
|
||||
let component: DSOSelectorModalWrapperComponent;
|
||||
@@ -83,6 +84,20 @@ describe('DSOSelectorModalWrapperComponent', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectObject with emit only', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'navigate');
|
||||
spyOn(component, 'close');
|
||||
spyOn(component.select, 'emit');
|
||||
component.emitOnly = true;
|
||||
component.selectObject(item);
|
||||
});
|
||||
it('should call the close and navigate method on the component with the given DSO', () => {
|
||||
expect(component.close).toHaveBeenCalled();
|
||||
expect(component.select.emit).toHaveBeenCalledWith(item);
|
||||
});
|
||||
});
|
||||
|
||||
describe('close', () => {
|
||||
beforeEach(() => {
|
||||
component.close();
|
||||
@@ -113,6 +128,19 @@ describe('DSOSelectorModalWrapperComponent', () => {
|
||||
expect(component.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('should find route data', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(component, 'findRouteData');
|
||||
component.ngOnInit();
|
||||
});
|
||||
it('should call the findRouteData method on the component', () => {
|
||||
expect(component.findRouteData).toHaveBeenCalled();
|
||||
});
|
||||
it('should return undefined', () => {
|
||||
expect(component.findRouteData((route) => hasValue(route.data), {} as unknown as ActivatedRouteSnapshot)).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@Component({
|
||||
|
@@ -96,7 +96,7 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
|
||||
private pageConfigSub: Subscription;
|
||||
|
||||
/**
|
||||
* Initialize instance variables and inject the properly DataService
|
||||
* Initialize instance variables and inject the properly UpdateDataServiceImpl
|
||||
*
|
||||
* @param {DSONameService} dsoNameService
|
||||
* @param {Injector} parentInjector
|
||||
|
@@ -54,4 +54,31 @@ describe('FilterInputSuggestionsComponent', () => {
|
||||
expect(comp.onClickSuggestion).toHaveBeenCalledWith(suggestions[clickedIndex]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('component methods', () => {
|
||||
const testData = {
|
||||
value: 'test-field'
|
||||
} as unknown as any;
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(comp.submitSuggestion, 'emit');
|
||||
spyOn(comp.clickSuggestion, 'emit');
|
||||
spyOn(comp, 'close');
|
||||
});
|
||||
|
||||
it('should properly submit', () => {
|
||||
comp.onSubmit(testData);
|
||||
expect(comp.submitSuggestion.emit).toHaveBeenCalledWith(testData);
|
||||
expect(comp.value).toBe(testData);
|
||||
});
|
||||
|
||||
it('should update value on suggestion clicked', () => {
|
||||
comp.onClickSuggestion(testData);
|
||||
expect(comp.clickSuggestion.emit).toHaveBeenCalledWith(testData);
|
||||
expect(comp.value).toBe(testData.value);
|
||||
expect(comp.blockReopen).toBeTruthy();
|
||||
expect(comp.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ResourceType } from '../../core/shared/resource-type';
|
||||
import { SuggestionTarget } from '../../core/notifications/reciter-suggestions/models/suggestion-target.model';
|
||||
import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model';
|
||||
|
||||
// REST Mock ---------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------------------
|
@@ -2,11 +2,11 @@
|
||||
// REST Mock ---------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
import { Suggestion } from '../../core/notifications/reciter-suggestions/models/suggestion.model';
|
||||
import { SUGGESTION } from '../../core/notifications/reciter-suggestions/models/suggestion-objects.resource-type';
|
||||
import { Suggestion } from '../../core/suggestion-notifications/models/suggestion.model';
|
||||
import { SUGGESTION } from '../../core/suggestion-notifications/models/suggestion-objects.resource-type';
|
||||
|
||||
export const mockSuggestionPublicationOne: Suggestion = {
|
||||
id: '24694772',
|
||||
id: '24694773',
|
||||
display: 'publication one',
|
||||
source: 'reciter',
|
||||
externalSourceUri: 'https://dspace7.4science.cloud/server/api/integration/reciterSourcesEntry/pubmed/entryValues/24694772',
|
@@ -1,6 +1,7 @@
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { SearchResult } from '../search/models/search-result.model';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
|
||||
// REST Mock ---------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------------------
|
||||
@@ -1333,7 +1334,8 @@ export function getMockSuggestionNotificationsStateService(): any {
|
||||
getOpenaireBrokerTopicsCurrentPage: jasmine.createSpy('getOpenaireBrokerTopicsCurrentPage'),
|
||||
getOpenaireBrokerTopicsTotals: jasmine.createSpy('getOpenaireBrokerTopicsTotals'),
|
||||
dispatchRetrieveOpenaireBrokerTopics: jasmine.createSpy('dispatchRetrieveOpenaireBrokerTopics'),
|
||||
dispatchMarkUserSuggestionsAsVisitedAction: jasmine.createSpy('dispatchMarkUserSuggestionsAsVisitedAction')
|
||||
dispatchMarkUserSuggestionsAsVisitedAction: jasmine.createSpy('dispatchMarkUserSuggestionsAsVisitedAction'),
|
||||
dispatchRefreshUserSuggestionsAction: undefined
|
||||
});
|
||||
}
|
||||
/**
|
||||
@@ -1342,10 +1344,17 @@ export function getMockSuggestionNotificationsStateService(): any {
|
||||
export function getMockSuggestionsService(): any {
|
||||
return jasmine.createSpyObj('SuggestionsService', {
|
||||
getTargets: jasmine.createSpy('getTargets'),
|
||||
getSuggestions: jasmine.createSpy('getSuggestions'),
|
||||
getSuggestions: observableOf([]),
|
||||
clearSuggestionRequests: jasmine.createSpy('clearSuggestionRequests'),
|
||||
deleteReviewedSuggestion: jasmine.createSpy('deleteReviewedSuggestion'),
|
||||
retrieveCurrentUserSuggestions: jasmine.createSpy('retrieveCurrentUserSuggestions'),
|
||||
getTargetUuid: jasmine.createSpy('getTargetUuid'),
|
||||
ignoreSuggestion: observableOf(null),
|
||||
ignoreSuggestionMultiple: observableOf({success: 1, fails: 0}),
|
||||
approveAndImportMultiple: observableOf({success: 1, fails: 0}),
|
||||
approveAndImport: observableOf({id: '1234'}),
|
||||
isCollectionFixed: false,
|
||||
translateSuggestionSource: 'testSource',
|
||||
translateSuggestionType: 'testType',
|
||||
});
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ import { CacheableObject } from '../../core/cache/cacheable-object.model';
|
||||
import { IdentifiableDataService } from '../../core/data/base/identifiable-data.service';
|
||||
|
||||
/**
|
||||
* Class to return DataService for given ResourceType
|
||||
* Class to return UpdateDataServiceImpl for given ResourceType
|
||||
*/
|
||||
export class MyDSpaceActionsServiceFactory<T extends CacheableObject, TService extends IdentifiableDataService<T>> {
|
||||
public getConstructor(type: ResourceType): TService {
|
||||
|
@@ -47,7 +47,7 @@ export abstract class MyDSpaceActionsComponent<T extends DSpaceObject, TService
|
||||
public processing$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
/**
|
||||
* Instance of DataService related to mydspace object
|
||||
* Instance of UpdateDataServiceImpl related to mydspace object
|
||||
*/
|
||||
protected objectDataService: TService;
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import { FilterType } from '../../../models/filter-type.model';
|
||||
import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||
import {
|
||||
FILTER_CONFIG,
|
||||
SCOPE,
|
||||
IN_PLACE_SEARCH,
|
||||
REFRESH_FILTER
|
||||
} from '../../../../../core/shared/search/search-filter.service';
|
||||
@@ -35,6 +36,11 @@ export class SearchFacetFilterWrapperComponent implements OnInit {
|
||||
*/
|
||||
@Input() refreshFilters: BehaviorSubject<boolean>;
|
||||
|
||||
/**
|
||||
* The current scope
|
||||
*/
|
||||
@Input() scope: string;
|
||||
|
||||
/**
|
||||
* The constructor of the search facet filter that should be rendered, based on the filter config's type
|
||||
*/
|
||||
@@ -56,7 +62,8 @@ export class SearchFacetFilterWrapperComponent implements OnInit {
|
||||
providers: [
|
||||
{ provide: FILTER_CONFIG, useFactory: () => (this.filterConfig), deps: [] },
|
||||
{ provide: IN_PLACE_SEARCH, useFactory: () => (this.inPlaceSearch), deps: [] },
|
||||
{ provide: REFRESH_FILTER, useFactory: () => (this.refreshFilters), deps: [] }
|
||||
{ provide: REFRESH_FILTER, useFactory: () => (this.refreshFilters), deps: [] },
|
||||
{ provide: SCOPE, useFactory: () => (this.scope), deps: [] },
|
||||
],
|
||||
parent: this.injector
|
||||
});
|
||||
|
@@ -4,6 +4,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import {
|
||||
FILTER_CONFIG,
|
||||
SCOPE,
|
||||
IN_PLACE_SEARCH,
|
||||
REFRESH_FILTER,
|
||||
SearchFilterService
|
||||
@@ -99,6 +100,7 @@ describe('SearchFacetFilterComponent', () => {
|
||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||
{ provide: IN_PLACE_SEARCH, useValue: false },
|
||||
{ provide: REFRESH_FILTER, useValue: new BehaviorSubject<boolean>(false) },
|
||||
{ provide: SCOPE, useValue: undefined },
|
||||
{
|
||||
provide: SearchFilterService, useValue: {
|
||||
getSelectedValuesForFilter: () => observableOf(selectedValues),
|
||||
|
@@ -22,6 +22,7 @@ import { SearchFilterConfig } from '../../../models/search-filter-config.model';
|
||||
import { SearchService } from '../../../../../core/shared/search/search.service';
|
||||
import {
|
||||
FILTER_CONFIG,
|
||||
SCOPE,
|
||||
IN_PLACE_SEARCH,
|
||||
REFRESH_FILTER,
|
||||
SearchFilterService
|
||||
@@ -104,7 +105,9 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||
@Inject(IN_PLACE_SEARCH) public inPlaceSearch: boolean,
|
||||
@Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig,
|
||||
@Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject<boolean>) {
|
||||
@Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject<boolean>,
|
||||
@Inject(SCOPE) public scope: string,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,8 +117,11 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
||||
this.currentUrl = this.router.url;
|
||||
this.filterValues$ = new BehaviorSubject(createPendingRemoteDataObject());
|
||||
this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged());
|
||||
|
||||
this.searchOptions$ = this.searchConfigService.searchOptions;
|
||||
this.searchOptions$ = this.searchConfigService.searchOptions.pipe(
|
||||
map((options: SearchOptions) => hasNoValue(this.scope) ? options : Object.assign({}, options, {
|
||||
scope: this.scope,
|
||||
})),
|
||||
);
|
||||
this.subs.push(
|
||||
this.searchOptions$.subscribe(() => this.updateFilterValueList()),
|
||||
this.refreshFilters.asObservable().pipe(
|
||||
|
@@ -19,6 +19,7 @@
|
||||
(@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)"
|
||||
class="search-filter-wrapper" [ngClass]="{ 'closed' : closed, 'notab': notab }">
|
||||
<ds-search-facet-filter-wrapper
|
||||
[scope]="scope"
|
||||
[filterConfig]="filter"
|
||||
[inPlaceSearch]="inPlaceSearch"
|
||||
[refreshFilters]="refreshFilters" >
|
||||
|
@@ -6,7 +6,7 @@ import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { SearchFilterConfig } from '../../models/search-filter-config.model';
|
||||
import { SearchFilterService } from '../../../../core/shared/search/search-filter.service';
|
||||
import { slide } from '../../../animations/slide';
|
||||
import { isNotEmpty } from '../../../empty.util';
|
||||
import { isNotEmpty, hasValue } from '../../../empty.util';
|
||||
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../../../my-dspace-page/my-dspace-page.component';
|
||||
@@ -38,6 +38,11 @@ export class SearchFilterComponent implements OnInit {
|
||||
*/
|
||||
@Input() refreshFilters: BehaviorSubject<boolean>;
|
||||
|
||||
/**
|
||||
* The current scope
|
||||
*/
|
||||
@Input() scope: string;
|
||||
|
||||
/**
|
||||
* True when the filter is 100% collapsed in the UI
|
||||
*/
|
||||
@@ -171,6 +176,9 @@ export class SearchFilterComponent implements OnInit {
|
||||
} else {
|
||||
return this.searchConfigService.searchOptions.pipe(
|
||||
switchMap((options) => {
|
||||
if (hasValue(this.scope)) {
|
||||
options.scope = this.scope;
|
||||
}
|
||||
return this.searchService.getFacetValuesFor(this.filter, 1, options).pipe(
|
||||
filter((RD) => !RD.isLoading),
|
||||
map((valuesRD) => {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user