refactor, fix tests

This commit is contained in:
FrancescoMolinaro
2024-01-25 11:50:02 +01:00
parent afa6559b19
commit 66cd035f87
40 changed files with 143 additions and 490 deletions

View File

@@ -1 +1 @@
<ds-suggestion-target [source]="'openaire'"></ds-suggestion-target> <ds-publication-claim [source]="'openaire'"></ds-publication-claim>

View File

@@ -1,13 +1,13 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-publication-claim-page.component'; import { AdminNotificationsPublicationClaimPageComponent } from './admin-notifications-publication-claim-page.component';
import { NO_ERRORS_SCHEMA } from '@angular/core'; import { NO_ERRORS_SCHEMA } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
describe('AdminNotificationsSuggestionTargetsPageComponent', () => { describe('AdminNotificationsPublicationClaimPageComponent', () => {
let component: AdminNotificationsSuggestionTargetsPageComponent; let component: AdminNotificationsPublicationClaimPageComponent;
let fixture: ComponentFixture<AdminNotificationsSuggestionTargetsPageComponent>; let fixture: ComponentFixture<AdminNotificationsPublicationClaimPageComponent>;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -16,10 +16,10 @@ describe('AdminNotificationsSuggestionTargetsPageComponent', () => {
TranslateModule.forRoot() TranslateModule.forRoot()
], ],
declarations: [ declarations: [
AdminNotificationsSuggestionTargetsPageComponent AdminNotificationsPublicationClaimPageComponent
], ],
providers: [ providers: [
AdminNotificationsSuggestionTargetsPageComponent AdminNotificationsPublicationClaimPageComponent
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
@@ -27,7 +27,7 @@ describe('AdminNotificationsSuggestionTargetsPageComponent', () => {
})); }));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AdminNotificationsSuggestionTargetsPageComponent); fixture = TestBed.createComponent(AdminNotificationsPublicationClaimPageComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@@ -5,6 +5,6 @@ import { Component } from '@angular/core';
templateUrl: './admin-notifications-publication-claim-page.component.html', templateUrl: './admin-notifications-publication-claim-page.component.html',
styleUrls: ['./admin-notifications-publication-claim-page.component.scss'] styleUrls: ['./admin-notifications-publication-claim-page.component.scss']
}) })
export class AdminNotificationsSuggestionTargetsPageComponent { export class AdminNotificationsPublicationClaimPageComponent {
} }

View File

@@ -5,7 +5,7 @@ import { AuthenticatedGuard } from '../../core/auth/authenticated.guard';
import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver';
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service'; import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
import { PUBLICATION_CLAIMS_PATH } from './admin-notifications-routing-paths'; import { PUBLICATION_CLAIMS_PATH } from './admin-notifications-routing-paths';
import { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component'; 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 { 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 { QUALITY_ASSURANCE_EDIT_PATH } from './admin-notifications-routing-paths';
import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component'; import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component';
@@ -26,7 +26,7 @@ import {
{ {
canActivate: [ AuthenticatedGuard ], canActivate: [ AuthenticatedGuard ],
path: `${PUBLICATION_CLAIMS_PATH}`, path: `${PUBLICATION_CLAIMS_PATH}`,
component: AdminNotificationsSuggestionTargetsPageComponent, component: AdminNotificationsPublicationClaimPageComponent,
pathMatch: 'full', pathMatch: 'full',
resolve: { resolve: {
breadcrumb: I18nBreadcrumbResolver, breadcrumb: I18nBreadcrumbResolver,

View File

@@ -3,7 +3,7 @@ import { NgModule } from '@angular/core';
import { CoreModule } from '../../core/core.module'; import { CoreModule } from '../../core/core.module';
import { SharedModule } from '../../shared/shared.module'; import { SharedModule } from '../../shared/shared.module';
import { AdminNotificationsRoutingModule } from './admin-notifications-routing.module'; import { AdminNotificationsRoutingModule } from './admin-notifications-routing.module';
import { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component'; 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 { 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 { 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 { AdminQualityAssuranceSourcePageComponent } from './admin-quality-assurance-source-page-component/admin-quality-assurance-source-page.component';
@@ -18,7 +18,7 @@ import { NotificationsModule } from '../../notifications/notifications.module';
NotificationsModule NotificationsModule
], ],
declarations: [ declarations: [
AdminNotificationsSuggestionTargetsPageComponent, AdminNotificationsPublicationClaimPageComponent,
AdminQualityAssuranceTopicsPageComponent, AdminQualityAssuranceTopicsPageComponent,
AdminQualityAssuranceEventsPageComponent, AdminQualityAssuranceEventsPageComponent,
AdminQualityAssuranceSourcePageComponent AdminQualityAssuranceSourcePageComponent

View File

@@ -29,11 +29,11 @@ export const EMBED_SEPARATOR = '%2F';
/** /**
* Common functionality for data services. * Common functionality for data services.
* Specific functionality that not all services would need * 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}) * - 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> { * 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 * 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' * e.g. if the api root links to 'foo', and the endpoint at 'foo'
* links to 'bar' the linkPath for the BarDataService would be * links to 'bar' the linkPath for the BarDataService would be

View File

@@ -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} * Concrete data services can use this feature by implementing {@link CreateData}
* and delegating its method to an inner instance of this class. * and delegating its method to an inner instance of this class.

View File

@@ -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} * Concrete data services can use this feature by implementing {@link FindAllData}
* and delegating its method to an inner instance of this class. * and delegating its method to an inner instance of this class.

View File

@@ -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} * Concrete data services can use this feature by implementing {@link PatchData}
* and delegating its method to an inner instance of this class. * and delegating its method to an inner instance of this class.

View File

@@ -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} * Concrete data services can use this feature by implementing {@link PutData}
* and delegating its method to an inner instance of this class. * and delegating its method to an inner instance of this class.

View File

@@ -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} * Concrete data services can use this feature by implementing {@link SearchData}
* and delegating its method to an inner instance of this class. * and delegating its method to an inner instance of this class.

View File

@@ -17,8 +17,8 @@ import { HALDataService } from './base/hal-data-service.interface';
import { dataService } from './base/data-service.decorator'; import { dataService } from './base/data-service.decorator';
/** /**
* A DataService with only findByHref methods. Its purpose is to be used for resources that don't * 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 DataService in order * 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. * for their links to be resolved by the LinkService.
* *
* an @dataService annotation can be added for any number of these resource types * an @dataService annotation can be added for any number of these resource types

View File

@@ -42,45 +42,60 @@ import {
} from './request.models'; } from './request.models';
import { RequestService } from './request.service'; import { RequestService } from './request.service';
import { RestRequestMethod } from './rest-request-method'; import { RestRequestMethod } from './rest-request-method';
import { UpdateDataService } from './update-data.service';
import { GenericConstructor } from '../shared/generic-constructor'; import { GenericConstructor } from '../shared/generic-constructor';
import { NoContent } from '../shared/NoContent.model'; import { NoContent } from '../shared/NoContent.model';
import { CacheableObject } from '../cache/cacheable-object.model'; import { CacheableObject } from '../cache/cacheable-object.model';
import { CoreState } from '../core-state.model'; import { CoreState } from '../core-state.model';
import { FindListOptions } from './find-list-options.model'; import { FindListOptions } from './find-list-options.model';
import { BaseDataService } from "./base/base-data.service";
import { FindAllData, FindAllDataImpl } from "./base/find-all-data";
import { SearchData, SearchDataImpl } from "./base/search-data";
import { CreateData, CreateDataImpl } from "./base/create-data";
export abstract class DataService<T extends CacheableObject> implements UpdateDataService<T> { export interface UpdateDataService<T> {
protected abstract requestService: RequestService; patch(dso: T, operations: Operation[]): Observable<RemoteData<T>>;
protected abstract rdbService: RemoteDataBuildService; update(object: T): Observable<RemoteData<T>>;
commitUpdates(method?: RestRequestMethod);
}
/**
* 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
*
* Custom methods are:
*
* patch - Sends a patch request to modify current object
* update - Add a new patch to the object cache, diff between given object and cache
* commitUpdates - Sends the updates to the server
*/
export abstract class UpdateDataServiceImpl<T extends CacheableObject> extends BaseDataService<T> implements UpdateDataService<T>, FindAllData<T>, SearchData<T>, CreateData<T> {
protected abstract store: Store<CoreState>; protected abstract store: Store<CoreState>;
protected abstract linkPath: string;
protected abstract halService: HALEndpointService;
protected abstract objectCache: ObjectCacheService;
protected abstract notificationsService: NotificationsService;
protected abstract http: HttpClient; protected abstract http: HttpClient;
protected abstract comparator: ChangeAnalyzer<T>; protected abstract comparator: ChangeAnalyzer<T>;
/** private findAllData: FindAllDataImpl<T>;
* Allows subclasses to reset the response cache time. private searchData: SearchDataImpl<T>;
*/ private createData: CreateData<T>;
protected responseMsToLive: number;
/**
* Get the endpoint for browsing constructor(
* @param options The [[FindListOptions]] object protected linkPath: string,
* @param linkPath The link path for the object protected requestService: RequestService,
* @returns {Observable<string>} protected rdbService: RemoteDataBuildService,
*/ protected objectCache: ObjectCacheService,
getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable<string> { protected halService: HALEndpointService,
return this.getEndpoint(); protected notificationsService: NotificationsService,
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);
} }
/**
* Get the base endpoint for all requests
*/
protected getEndpoint(): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
}
/** /**
* Create the HREF with given options object * Create the HREF with given options object
@@ -92,16 +107,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved * @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> { public getFindAllHref(options: FindListOptions = {}, linkPath?: string, ...linksToFollow: FollowLinkConfig<T>[]): Observable<string> {
let endpoint$: Observable<string>; return this.findAllData.getFindAllHref(options, linkPath, ...linksToFollow)
const args = [];
endpoint$ = this.getBrowseEndpoint(options).pipe(
filter((href: string) => isNotEmpty(href)),
map((href: string) => isNotEmpty(linkPath) ? `${href}/${linkPath}` : href),
distinctUntilChanged()
);
return endpoint$.pipe(map((result: string) => this.buildHrefFromFindOptions(result, options, args, ...linksToFollow)));
} }
/** /**
@@ -114,149 +120,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved * @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> { public getSearchByHref(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<T>[]): Observable<string> {
let result$: Observable<string>; return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow)
const args = [];
result$ = this.getSearchEndpoint(searchMethod);
return result$.pipe(map((result: string) => this.buildHrefFromFindOptions(result, options, args, ...linksToFollow)));
}
/**
* Turn an options object into a query string and combine it with the given HREF
*
* @param href The HREF to which the query string should be appended
* @param options The [[FindListOptions]] object
* @param extraArgs Array with additional params to combine with query string
* @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 buildHrefFromFindOptions(href: string, options: FindListOptions, extraArgs: string[] = [], ...linksToFollow: FollowLinkConfig<T>[]): string {
let args = [...extraArgs];
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
args = this.addHrefArg(href, args, `page=${options.currentPage - 1}`);
}
if (hasValue(options.elementsPerPage)) {
args = this.addHrefArg(href, args, `size=${options.elementsPerPage}`);
}
if (hasValue(options.sort)) {
args = this.addHrefArg(href, args, `sort=${options.sort.field},${options.sort.direction}`);
}
if (hasValue(options.startsWith)) {
args = this.addHrefArg(href, args, `startsWith=${options.startsWith}`);
}
if (hasValue(options.searchParams)) {
options.searchParams.forEach((param: RequestParam) => {
args = this.addHrefArg(href, args, `${param.fieldName}=${param.fieldValue}`);
});
}
args = this.addEmbedParams(href, args, ...linksToFollow);
if (isNotEmpty(args)) {
return new URLCombiner(href, `?${args.join('&')}`).toString();
} else {
return href;
}
}
/**
* Turn an array of RequestParam into a query string and combine it with the given HREF
*
* @param href The HREF to which the query string should be appended
* @param params Array with additional params to combine with query string
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*
* @return {Observable<string>}
* Return an observable that emits created HREF
*/
buildHrefWithParams(href: string, params: RequestParam[], ...linksToFollow: FollowLinkConfig<T>[]): string {
let args = [];
if (hasValue(params)) {
params.forEach((param: RequestParam) => {
args = this.addHrefArg(href, args, `${param.fieldName}=${param.fieldValue}`);
});
}
args = this.addEmbedParams(href, args, ...linksToFollow);
if (isNotEmpty(args)) {
return new URLCombiner(href, `?${args.join('&')}`).toString();
} else {
return href;
}
}
/**
* Adds the embed options to the link for the request
* @param href The href the params are to be added to
* @param args params for the query string
* @param linksToFollow links we want to embed in query string if shouldEmbed is true
*/
protected addEmbedParams(href: string, args: string[], ...linksToFollow: FollowLinkConfig<T>[]) {
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
if (hasValue(linkToFollow) && linkToFollow.shouldEmbed) {
const embedString = 'embed=' + String(linkToFollow.name);
// Add the embeds size if given in the FollowLinkConfig.FindListOptions
if (hasValue(linkToFollow.findListOptions) && hasValue(linkToFollow.findListOptions.elementsPerPage)) {
args = this.addHrefArg(href, args,
'embed.size=' + String(linkToFollow.name) + '=' + linkToFollow.findListOptions.elementsPerPage);
}
// Adds the nested embeds and their size if given
if (isNotEmpty(linkToFollow.linksToFollow)) {
args = this.addNestedEmbeds(embedString, href, args, ...linkToFollow.linksToFollow);
} else {
args = this.addHrefArg(href, args, embedString);
}
}
});
return args;
}
/**
* Add a new argument to the list of arguments, only if it doesn't already exist in the given href,
* or the current list of arguments
*
* @param href The href the arguments are to be added to
* @param currentArgs The current list of arguments
* @param newArg The new argument to add
* @return The next list of arguments, with newArg included if it wasn't already.
* Note this function will not modify any of the input params.
*/
protected addHrefArg(href: string, currentArgs: string[], newArg: string): string[] {
if (href.includes(newArg) || currentArgs.includes(newArg)) {
return [...currentArgs];
} else {
return [...currentArgs, newArg];
}
}
/**
* Add the nested followLinks to the embed param, separated by a /, and their sizes, recursively
* @param embedString embedString so far (recursive)
* @param href The href the params are to be added to
* @param args params for the query string
* @param linksToFollow links we want to embed in query string if shouldEmbed is true
*/
protected addNestedEmbeds(embedString: string, href: string, args: string[], ...linksToFollow: FollowLinkConfig<T>[]): string[] {
let nestEmbed = embedString;
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
if (hasValue(linkToFollow) && linkToFollow.shouldEmbed) {
nestEmbed = nestEmbed + '/' + String(linkToFollow.name);
// Add the nested embeds size if given in the FollowLinkConfig.FindListOptions
if (hasValue(linkToFollow.findListOptions) && hasValue(linkToFollow.findListOptions.elementsPerPage)) {
const nestedEmbedSize = 'embed.size=' + nestEmbed.split('=')[1] + '=' + linkToFollow.findListOptions.elementsPerPage;
args = this.addHrefArg(href, args, nestedEmbedSize);
}
if (hasValue(linkToFollow.linksToFollow) && isNotEmpty(linkToFollow.linksToFollow)) {
args = this.addNestedEmbeds(nestEmbed, href, args, ...linkToFollow.linksToFollow);
} else {
args = this.addHrefArg(href, args, nestEmbed);
}
}
});
return args;
} }
/** /**
@@ -274,7 +138,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
* Return an observable that emits object list * Return an observable that emits object list
*/ */
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> { findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
return this.findAllByHref(this.getFindAllHref(options), options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)
} }
/** /**
@@ -337,118 +201,6 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
}; };
} }
/**
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
* @param href$ The url of object we want to retrieve. Can be a string or
* an Observable<string>
* @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
*/
findByHref(href$: string | Observable<string>, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
if (typeof href$ === 'string') {
href$ = observableOf(href$);
}
const requestHref$ = href$.pipe(
isNotEmptyOperator(),
take(1),
map((href: string) => this.buildHrefFromFindOptions(href, {}, [], ...linksToFollow))
);
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
return this.rdbService.buildSingle<T>(requestHref$, ...linksToFollow).pipe(
// This skip ensures that if a stale object is present in the cache when you do a
// call it isn't immediately returned, but we wait until the remote data for the new request
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
// cached completed object
skipWhile((rd: RemoteData<T>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted),
this.reRequestStaleRemoteData(reRequestOnStale, () =>
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
);
}
/**
* Returns a list of observables of {@link RemoteData} of objects, based on an href, with a list
* of {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
* @param href$ The url of object we want to retrieve. Can be a string or
* an Observable<string>
* @param findListOptions 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
*/
findAllByHref(href$: string | Observable<string>, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
if (typeof href$ === 'string') {
href$ = observableOf(href$);
}
const requestHref$ = href$.pipe(
isNotEmptyOperator(),
take(1),
map((href: string) => this.buildHrefFromFindOptions(href, findListOptions, [], ...linksToFollow))
);
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
return this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
// This skip ensures that if a stale object is present in the cache when you do a
// call it isn't immediately returned, but we wait until the remote data for the new request
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
// cached completed object
skipWhile((rd: RemoteData<PaginatedList<T>>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted),
this.reRequestStaleRemoteData(reRequestOnStale, () =>
this.findAllByHref(href$, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
);
}
/**
* Create a GET request for the given href, and send it.
*
* @param href$ The url of object we want to retrieve. Can be a string or
* an Observable<string>
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
*/
protected createAndSendGetRequest(href$: string | Observable<string>, useCachedVersionIfAvailable = true): void {
if (isNotEmpty(href$)) {
if (typeof href$ === 'string') {
href$ = observableOf(href$);
}
href$.pipe(
isNotEmptyOperator(),
take(1)
).subscribe((href: string) => {
const requestId = this.requestService.generateRequestId();
const request = new GetRequest(requestId, href);
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.send(request, useCachedVersionIfAvailable);
});
}
}
/**
* Return object search endpoint by given search method
*
* @param searchMethod The search method for the object
*/
protected getSearchEndpoint(searchMethod: string): Observable<string> {
return this.halService.getEndpoint(this.linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
map((href: string) => `${href}/search/${searchMethod}`));
}
/** /**
* Make a new FindListRequest with given search method * Make a new FindListRequest with given search method
* *
@@ -464,9 +216,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
* Return an observable that emits response from the server * 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>>> { searchBy(searchMethod: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow); return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)
return this.findAllByHref(hrefObs, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
} }
/** /**
@@ -548,38 +298,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
* Array with additional params to combine with query string * Array with additional params to combine with query string
*/ */
create(object: T, ...params: RequestParam[]): Observable<RemoteData<T>> { create(object: T, ...params: RequestParam[]): Observable<RemoteData<T>> {
const requestId = this.requestService.generateRequestId(); return this.createData.create(object, ...params)
const endpoint$ = this.getEndpoint().pipe(
isNotEmptyOperator(),
distinctUntilChanged(),
map((endpoint: string) => this.buildHrefWithParams(endpoint, params))
);
const serializedObject = new DSpaceSerializer(getClassForType(object.type)).serialize(object);
endpoint$.pipe(
take(1)
).subscribe((endpoint: string) => {
const request = new CreateRequest(requestId, endpoint, JSON.stringify(serializedObject));
if (hasValue(this.responseMsToLive)) {
request.responseMsToLive = this.responseMsToLive;
}
this.requestService.send(request);
});
const result$ = this.rdbService.buildFromRequestUUID<T>(requestId);
// TODO a dataservice is not the best place to show a notification,
// this should move up to the components that use this method
result$.pipe(
takeWhile((rd: RemoteData<T>) => rd.isLoading, true)
).subscribe((rd: RemoteData<T>) => {
if (rd.hasFailed) {
this.notificationsService.error('Server Error:', rd.errorMessage, new NotificationOptions(-1));
}
});
return result$;
} }
/** /**
@@ -732,16 +451,4 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
commitUpdates(method?: RestRequestMethod) { commitUpdates(method?: RestRequestMethod) {
this.requestService.commit(method); this.requestService.commit(method);
} }
/**
* Return the links to traverse from the root of the api to the
* endpoint this DataService 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
* 'foo/bar'
*/
getLinkPath(): string {
return this.linkPath;
}
} }

View File

@@ -1,13 +0,0 @@
import { Observable } from 'rxjs';
import { RemoteData } from './remote-data';
import { RestRequestMethod } from './rest-request-method';
import { Operation } from 'fast-json-patch';
/**
* Represents a data service to update a given object
*/
export interface UpdateDataService<T> {
patch(dso: T, operations: Operation[]): Observable<RemoteData<T>>;
update(object: T): Observable<RemoteData<T>>;
commitUpdates(method?: RestRequestMethod);
}

View File

@@ -128,7 +128,7 @@ describe('VersionDataService test', () => {
}); });
describe('getHistoryFromVersion', () => { 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.schedule(() => service.getHistoryFromVersion(mockVersion, true, true));
scheduler.flush(); scheduler.flush();

View File

@@ -315,7 +315,7 @@ describe('EPersonDataService', () => {
service.deleteEPerson(EPersonMock).subscribe(); 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); expect(service.delete).toHaveBeenCalledWith(EPersonMock.id);
}); });
}); });

View File

@@ -126,7 +126,7 @@ describe('WorkflowItemDataService test', () => {
}); });
describe('findByItem', () => { 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.schedule(() => service.findByItem('1234-1234', true, true, pageInfo));
scheduler.flush(); scheduler.flush();

View File

@@ -141,7 +141,7 @@ describe('WorkspaceitemDataService test', () => {
}); });
describe('findByItem', () => { 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.schedule(() => service.findByItem('1234-1234', true, true, pageInfo));
scheduler.flush(); scheduler.flush();
const searchUrl = service.getIDHref('item', [new RequestParam('uuid', encodeURIComponent('1234-1234'))]); const searchUrl = service.getIDHref('item', [new RequestParam('uuid', encodeURIComponent('1234-1234'))]);

View File

@@ -11,7 +11,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
import { ObjectCacheService } from '../cache/object-cache.service'; import { ObjectCacheService } from '../cache/object-cache.service';
import { dataService } from '../cache/builders/build-decorators'; import { dataService } from '../cache/builders/build-decorators';
import { RequestService } from '../data/request.service'; import { RequestService } from '../data/request.service';
import { DataService } from '../data/data.service'; import { UpdateDataServiceImpl } from '../data/update-data-service';
import { ChangeAnalyzer } from '../data/change-analyzer'; import { ChangeAnalyzer } from '../data/change-analyzer';
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
@@ -31,13 +31,9 @@ import { SuggestionTargetDataService } from './target/suggestion-target-data.ser
/* tslint:disable:max-classes-per-file */ /* tslint:disable:max-classes-per-file */
/** /**
* A private DataService implementation to delegate specific methods to. * A private UpdateDataServiceImpl implementation to delegate specific methods to.
*/ */
export class SuggestionDataServiceImpl extends DataService<Suggestion> { export class SuggestionDataServiceImpl extends UpdateDataServiceImpl<Suggestion> {
/**
* The REST endpoint.
*/
protected linkPath = 'suggestions';
/** /**
* Initialize service variables * Initialize service variables
@@ -49,6 +45,7 @@ export class SuggestionDataServiceImpl extends DataService<Suggestion> {
* @param {NotificationsService} notificationsService * @param {NotificationsService} notificationsService
* @param {HttpClient} http * @param {HttpClient} http
* @param {ChangeAnalyzer<Suggestion>} comparator * @param {ChangeAnalyzer<Suggestion>} comparator
* @param responseMsToLive
*/ */
constructor( constructor(
protected requestService: RequestService, protected requestService: RequestService,
@@ -58,8 +55,10 @@ export class SuggestionDataServiceImpl extends DataService<Suggestion> {
protected halService: HALEndpointService, protected halService: HALEndpointService,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected http: HttpClient, protected http: HttpClient,
protected comparator: ChangeAnalyzer<Suggestion>) { protected comparator: ChangeAnalyzer<Suggestion>,
super(); protected responseMsToLive: number,
) {
super('suggestions', requestService, rdbService, objectCache, halService, notificationsService ,responseMsToLive);
} }
} }
@@ -73,20 +72,22 @@ export class SuggestionsDataService {
protected searchFindByTargetAndSourceMethod = 'findByTargetAndSource'; protected searchFindByTargetAndSourceMethod = 'findByTargetAndSource';
/** /**
* A private DataService implementation to delegate specific methods to. * A private UpdateDataServiceImpl implementation to delegate specific methods to.
*/ */
private suggestionsDataService: SuggestionDataServiceImpl; private suggestionsDataService: SuggestionDataServiceImpl;
/** /**
* A private DataService implementation to delegate specific methods to. * A private UpdateDataServiceImpl implementation to delegate specific methods to.
*/ */
private suggestionSourcesDataService: SuggestionSourceDataService; 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 suggestionTargetsDataService: SuggestionTargetDataService;
private responseMsToLive = 10 * 1000;
/** /**
* Initialize service variables * Initialize service variables
* @param {RequestService} requestService * @param {RequestService} requestService
@@ -98,6 +99,7 @@ export class SuggestionsDataService {
* @param {DefaultChangeAnalyzer<Suggestion>} comparatorSuggestions * @param {DefaultChangeAnalyzer<Suggestion>} comparatorSuggestions
* @param {DefaultChangeAnalyzer<SuggestionSource>} comparatorSources * @param {DefaultChangeAnalyzer<SuggestionSource>} comparatorSources
* @param {DefaultChangeAnalyzer<SuggestionTarget>} comparatorTargets * @param {DefaultChangeAnalyzer<SuggestionTarget>} comparatorTargets
* @param responseMsToLive
*/ */
constructor( constructor(
protected requestService: RequestService, protected requestService: RequestService,
@@ -110,7 +112,7 @@ export class SuggestionsDataService {
protected comparatorSources: DefaultChangeAnalyzer<SuggestionSource>, protected comparatorSources: DefaultChangeAnalyzer<SuggestionSource>,
protected comparatorTargets: DefaultChangeAnalyzer<SuggestionTarget>, 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.suggestionSourcesDataService = new SuggestionSourceDataService(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSources);
this.suggestionTargetsDataService = new SuggestionTargetDataService(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorTargets); this.suggestionTargetsDataService = new SuggestionTargetDataService(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorTargets);
} }

View File

@@ -12,7 +12,6 @@ import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { import {
getFirstCompletedRemoteData, getFirstCompletedRemoteData,
} from '../../core/shared/operators'; } from '../../core/shared/operators';
import { UpdateDataService } from '../../core/data/update-data.service';
import { ResourceType } from '../../core/shared/resource-type'; import { ResourceType } from '../../core/shared/resource-type';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core'; 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 { DATA_SERVICE_FACTORY } from '../../core/data/base/data-service.decorator';
import { GenericConstructor } from '../../core/shared/generic-constructor'; import { GenericConstructor } from '../../core/shared/generic-constructor';
import { HALDataService } from '../../core/data/base/hal-data-service.interface'; import { HALDataService } from '../../core/data/base/hal-data-service.interface';
import { UpdateDataService } from "../../core/data/update-data-service";
@Component({ @Component({
selector: 'ds-dso-edit-metadata', selector: 'ds-dso-edit-metadata',

View File

@@ -2,7 +2,7 @@ import { ThemedComponent } from '../../shared/theme-support/themed.component';
import { DsoEditMetadataComponent } from './dso-edit-metadata.component'; import { DsoEditMetadataComponent } from './dso-edit-metadata.component';
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { UpdateDataService } from '../../core/data/update-data.service'; import { UpdateDataService } from "../../core/data/update-data-service";
@Component({ @Component({
selector: 'ds-themed-dso-edit-metadata', selector: 'ds-themed-dso-edit-metadata',

View File

@@ -541,7 +541,7 @@ export class MenuResolver implements Resolve<boolean> {
{ {
id: 'notifications', id: 'notifications',
active: false, active: false,
visible: authorized && canSeeQA, visible: authorized && true,
model: { model: {
type: MenuItemType.TEXT, type: MenuItemType.TEXT,
text: 'menu.section.notifications' text: 'menu.section.notifications'

View File

@@ -26,7 +26,7 @@ import { QualityAssuranceSourceService } from './qa/source/quality-assurance-sou
import { import {
QualityAssuranceSourceDataService QualityAssuranceSourceDataService
} from '../core/notifications/qa/source/quality-assurance-source-data.service'; } from '../core/notifications/qa/source/quality-assurance-source-data.service';
import { SuggestionTargetsComponent } from '../suggestion-notifications/suggestion-targets/suggestion-targets.component'; import { PublicationClaimComponent } from '../suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component';
import { SuggestionActionsComponent } from '../suggestion-notifications/suggestion-actions/suggestion-actions.component'; import { SuggestionActionsComponent } from '../suggestion-notifications/suggestion-actions/suggestion-actions.component';
import { import {
SuggestionListElementComponent SuggestionListElementComponent
@@ -65,7 +65,7 @@ const COMPONENTS = [
QualityAssuranceTopicsComponent, QualityAssuranceTopicsComponent,
QualityAssuranceEventsComponent, QualityAssuranceEventsComponent,
QualityAssuranceSourceComponent, QualityAssuranceSourceComponent,
SuggestionTargetsComponent, PublicationClaimComponent,
SuggestionActionsComponent, SuggestionActionsComponent,
SuggestionListElementComponent, SuggestionListElementComponent,
SuggestionEvidencesComponent, SuggestionEvidencesComponent,

View File

@@ -96,7 +96,7 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
private pageConfigSub: Subscription; private pageConfigSub: Subscription;
/** /**
* Initialize instance variables and inject the properly DataService * Initialize instance variables and inject the properly UpdateDataServiceImpl
* *
* @param {DSONameService} dsoNameService * @param {DSONameService} dsoNameService
* @param {Injector} parentInjector * @param {Injector} parentInjector

View File

@@ -13,7 +13,7 @@ import { CacheableObject } from '../../core/cache/cacheable-object.model';
import { IdentifiableDataService } from '../../core/data/base/identifiable-data.service'; 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>> { export class MyDSpaceActionsServiceFactory<T extends CacheableObject, TService extends IdentifiableDataService<T>> {
public getConstructor(type: ResourceType): TService { public getConstructor(type: ResourceType): TService {

View File

@@ -47,7 +47,7 @@ export abstract class MyDSpaceActionsComponent<T extends DSpaceObject, TService
public processing$ = new BehaviorSubject<boolean>(false); public processing$ = new BehaviorSubject<boolean>(false);
/** /**
* Instance of DataService related to mydspace object * Instance of UpdateDataServiceImpl related to mydspace object
*/ */
protected objectDataService: TService; protected objectDataService: TService;

View File

@@ -23,7 +23,7 @@
{{ ignoreSuggestionLabel() | translate}}</button> {{ ignoreSuggestionLabel() | translate}}</button>
<button *ngIf="!isBulk" (click)="toggleSeeEvidences()" [disabled]="!hasEvidence" class="btn btn-info ml-2"> <button *ngIf="!isBulk" (click)="toggleSeeEvidences()" [disabled]="!hasEvidence" class="btn btn-info ml-2">
<i class="fa fa-eye"></i> <i class="fa fa-eye"></i>
<ng-container *ngIf="!seeEvidence"> {{ 'reciter.suggestion.seeEvidence' | translate}}</ng-container> <ng-container *ngIf="!seeEvidence"> {{ 'suggestion.seeEvidence' | translate}}</ng-container>
<ng-container *ngIf="seeEvidence"> {{ 'reciter.suggestion.hideEvidence' | translate}}</ng-container> <ng-container *ngIf="seeEvidence"> {{ 'suggestion.hideEvidence' | translate}}</ng-container>
</button> </button>
</div> </div>

View File

@@ -1,9 +1,9 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h1 id="header" class="border-bottom pb-2">{{'reciter.suggestion.title'| translate}}</h1> <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)" <ds-pagination *ngIf="!(isTargetsLoading() | async)"
[paginationOptions]="paginationConfig" [paginationOptions]="paginationConfig"
[collectionSize]="(totalElements$ | async)" [collectionSize]="(totalElements$ | async)"
@@ -11,17 +11,17 @@
[retainScrollPosition]="false" [retainScrollPosition]="false"
(paginationChange)="getSuggestionTargets()"> (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)"> <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"> <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>
<div *ngIf="(targets$|async) && (targets$|async)?.length != 0" class="table-responsive mt-2"> <div *ngIf="(targets$|async) && (targets$|async)?.length != 0" class="table-responsive mt-2">
<table id="epeople" class="table table-striped table-hover table-bordered"> <table id="epeople" class="table table-striped table-hover table-bordered">
<thead> <thead>
<tr class="text-center"> <tr class="text-center">
<th scope="col">{{'reciter.suggestion.table.name' | translate}}</th> <th scope="col">{{'suggestion.table.name' | translate}}</th>
<th scope="col">{{'reciter.suggestion.table.actions' | translate}}</th> <th scope="col">{{'suggestion.table.actions' | translate}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -33,9 +33,9 @@
<div class="btn-group edit-field"> <div class="btn-group edit-field">
<button (click)="redirectToSuggestions(targetElement.id, targetElement.display)" <button (click)="redirectToSuggestions(targetElement.id, targetElement.display)"
class="btn btn-outline-primary btn-sm" class="btn btn-outline-primary btn-sm"
title="{{('reciter.suggestion.button.review.title' | translate: { total: targetElement.total.toString() }) + title="{{('suggestion.button.review.title' | translate: { total: targetElement.total.toString() }) +
targetElement.display}}"> targetElement.display}}">
<span>{{'reciter.suggestion.button.review' | translate: { total: targetElement.total.toString() } }} </span> <span>{{'suggestion.button.review' | translate: { total: targetElement.total.toString() } }} </span>
<i class="fas fa-lightbulb"></i> <i class="fas fa-lightbulb"></i>
</button> </button>
</div> </div>

View File

@@ -4,23 +4,23 @@ import { Router } from '@angular/router';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { distinctUntilChanged, take } from 'rxjs/operators'; import { distinctUntilChanged, take } from 'rxjs/operators';
import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; import { SuggestionTarget } from '../../../core/suggestion-notifications/models/suggestion-target.model';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../../shared/empty.util';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; 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 { 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'; import { PaginationService } from '../../../core/pagination/pagination.service';
/** /**
* Component to display the Suggestion Target list. * Component to display the Suggestion Target list.
*/ */
@Component({ @Component({
selector: 'ds-suggestion-target', selector: 'ds-publication-claim',
templateUrl: './suggestion-targets.component.html', templateUrl: './publication-claim.component.html',
styleUrls: ['./suggestion-targets.component.scss'], styleUrls: ['./publication-claim.component.scss'],
}) })
export class SuggestionTargetsComponent implements OnInit { export class PublicationClaimComponent implements OnInit {
/** /**
* The source for which to list targets * The source for which to list targets

View File

@@ -156,7 +156,7 @@ describe('SuggestionsService test', () => {
}); });
it('should delete suggestions', () => { it('should delete suggestions', () => {
spyOn(service, 'notMine'); spyOn(service, 'ignoreSuggestion');
service.ignoreSuggestionMultiple([mockSuggestionPublicationOne]); service.ignoreSuggestionMultiple([mockSuggestionPublicationOne]);
expect(service.ignoreSuggestion).toHaveBeenCalledWith(mockSuggestionPublicationOne.id); expect(service.ignoreSuggestion).toHaveBeenCalledWith(mockSuggestionPublicationOne.id);
}); });

View File

@@ -18,7 +18,7 @@ import { combineLatest, Subject } from 'rxjs';
}) })
export class SuggestionsPopupComponent implements OnInit, OnDestroy { export class SuggestionsPopupComponent implements OnInit, OnDestroy {
labelPrefix = 'mydspace.'; labelPrefix = 'notification.';
subscription; subscription;
@@ -57,7 +57,7 @@ export class SuggestionsPopupComponent implements OnInit, OnDestroy {
* @private * @private
*/ */
private showNotificationForNewSuggestions(suggestionTarget: SuggestionTarget): void { private showNotificationForNewSuggestions(suggestionTarget: SuggestionTarget): void {
const content = this.translateService.instant(this.labelPrefix + 'notification.suggestion', const content = this.translateService.instant(this.labelPrefix + 'suggestion',
this.suggestionsService.getNotificationSuggestionInterpolation(suggestionTarget)); this.suggestionsService.getNotificationSuggestionInterpolation(suggestionTarget));
this.notificationsService.success('', content, {timeOut:0}, true); this.notificationsService.success('', content, {timeOut:0}, true);
} }

View File

@@ -23,7 +23,7 @@ import { NoContent } from '../core/shared/NoContent.model';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { WorkspaceItem } from '../core/submission/models/workspaceitem.model'; import { WorkspaceItem } from '../core/submission/models/workspaceitem.model';
import {FindListOptions} from '../core/data/find-list-options.model'; import {FindListOptions} from '../core/data/find-list-options.model';
import {SuggestionConfig} from '../../config/layout-config.interfaces'; import {SuggestionConfig} from '../../config/suggestion-config.interfaces';
import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service'; import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service';
import { import {
SuggestionSourceDataService SuggestionSourceDataService
@@ -229,7 +229,7 @@ export class SuggestionsService {
} }
/** /**
* Perform a bulk notMine operation. * Perform a bulk ignoreSuggestion operation.
* @param suggestions the array containing the suggestions * @param suggestions the array containing the suggestions
*/ */
public ignoreSuggestionMultiple(suggestions: Suggestion[]): Observable<SuggestionBulkResult> { public ignoreSuggestionMultiple(suggestions: Suggestion[]): Observable<SuggestionBulkResult> {

View File

@@ -41,7 +41,6 @@ describe('SuggestionPageComponent', () => {
let scheduler: TestScheduler; let scheduler: TestScheduler;
const mockSuggestionsService = getMockSuggestionsService(); const mockSuggestionsService = getMockSuggestionsService();
const mockSuggestionsTargetStateService = getMockSuggestionNotificationsStateService(); const mockSuggestionsTargetStateService = getMockSuggestionNotificationsStateService();
const suggestionTargetsList: PaginatedList<Suggestion> = buildPaginatedList(new PageInfo(), [mockSuggestionPublicationOne, mockSuggestionPublicationTwo]);
const router = new RouterStub(); const router = new RouterStub();
const routeStub = { const routeStub = {
data: observableOf({ data: observableOf({
@@ -139,7 +138,7 @@ describe('SuggestionPageComponent', () => {
scheduler.schedule(() => fixture.detectChanges()); scheduler.schedule(() => fixture.detectChanges());
scheduler.flush(); scheduler.flush();
component.ignoreSuggestion('1'); component.ignoreSuggestion('1');
expect(mockSuggestionsService.notMine).toHaveBeenCalledWith('1'); expect(mockSuggestionsService.ignoreSuggestion).toHaveBeenCalledWith('1');
expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled();
expect(component.updatePage).toHaveBeenCalled(); expect(component.updatePage).toHaveBeenCalled();
}); });
@@ -150,7 +149,7 @@ describe('SuggestionPageComponent', () => {
scheduler.schedule(() => fixture.detectChanges()); scheduler.schedule(() => fixture.detectChanges());
scheduler.flush(); scheduler.flush();
component.ignoreSuggestionAllSelected(); component.ignoreSuggestionAllSelected();
expect(mockSuggestionsService.notMineMultiple).toHaveBeenCalled(); expect(mockSuggestionsService.ignoreSuggestionMultiple).toHaveBeenCalled();
expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled();
expect(component.updatePage).toHaveBeenCalled(); expect(component.updatePage).toHaveBeenCalled();
}); });

View File

@@ -28,6 +28,11 @@ import {redirectOn4xx} from '../core/shared/authorized.operators';
templateUrl: './suggestions-page.component.html', templateUrl: './suggestions-page.component.html',
styleUrls: ['./suggestions-page.component.scss'], styleUrls: ['./suggestions-page.component.scss'],
}) })
/**
* Component used to visualize one of the suggestions from the publication claim page or from the notification pop up
*/
export class SuggestionsPageComponent implements OnInit { export class SuggestionsPageComponent implements OnInit {
/** /**
@@ -139,15 +144,6 @@ export class SuggestionsPageComponent implements OnInit {
this.processing$.next(false); this.processing$.next(false);
this.suggestionsRD$.next(results); this.suggestionsRD$.next(results);
this.suggestionService.clearSuggestionRequests(); this.suggestionService.clearSuggestionRequests();
// navigate to the mydspace if no suggestions remains
// if (results.totalElements === 0) {
// const content = this.translateService.instant('reciter.suggestion.empty',
// this.suggestionService.getNotificationSuggestionInterpolation(this.suggestionTarget));
// this.notificationService.success('', content, {timeOut:0}, true);
// TODO if the target is not the current use route to the suggestion target page
// this.router.navigate(['/mydspace']);
// }
}); });
} }

View File

@@ -3084,9 +3084,9 @@
"mydspace.import": "Import", "mydspace.import": "Import",
"mydspace.notification.suggestion": "We found <b>{{count}} publications</b><br> in the {{source}} that seems to be related to your profile.<br> Please <a href='/suggestions/{{suggestionId}}'>review the suggestions</a>", "notification.suggestion": "We found <b>{{count}} publications</b><br> in the {{source}} that seems to be related to your profile.<br> Please <a href='/suggestions/{{suggestionId}}'>review the suggestions</a>",
"mydspace.notification.suggestion.page": "We found <b>{{count}} {{type}}</b> in the {{source}} that seems to be related to your profile. Please <a href='/suggestions/{{suggestionId}}'>review the suggestions.</a>", "notification.suggestion.page": "We found <b>{{count}} {{type}}</b> in the {{source}} that seems to be related to your profile. Please <a href='/suggestions/{{suggestionId}}'>review the suggestions.</a>",
"nav.browse.header": "All of DSpace", "nav.browse.header": "All of DSpace",

View File

@@ -14,7 +14,7 @@ import { AuthConfig } from './auth-config.interfaces';
import { UIServerConfig } from './ui-server-config.interface'; import { UIServerConfig } from './ui-server-config.interface';
import { MediaViewerConfig } from './media-viewer-config.interface'; import { MediaViewerConfig } from './media-viewer-config.interface';
import { BrowseByConfig } from './browse-by-config.interface'; import { BrowseByConfig } from './browse-by-config.interface';
import { SuggestionConfig } from './layout-config.interfaces'; import { SuggestionConfig } from './suggestion-config.interfaces';
import { BundleConfig } from './bundle-config.interface'; import { BundleConfig } from './bundle-config.interface';
import { ActuatorsConfig } from './actuators.config'; import { ActuatorsConfig } from './actuators.config';
import { InfoConfig } from './info-config.interface'; import { InfoConfig } from './info-config.interface';

View File

@@ -14,7 +14,7 @@ import { ServerConfig } from './server-config.interface';
import { SubmissionConfig } from './submission-config.interface'; import { SubmissionConfig } from './submission-config.interface';
import { ThemeConfig } from './theme.config'; import { ThemeConfig } from './theme.config';
import { UIServerConfig } from './ui-server-config.interface'; import { UIServerConfig } from './ui-server-config.interface';
import {SuggestionConfig} from './layout-config.interfaces'; import {SuggestionConfig} from './suggestion-config.interfaces';
import { BundleConfig } from './bundle-config.interface'; import { BundleConfig } from './bundle-config.interface';
import { ActuatorsConfig } from './actuators.config'; import { ActuatorsConfig } from './actuators.config';
import { InfoConfig } from './info-config.interface'; import { InfoConfig } from './info-config.interface';
@@ -304,6 +304,8 @@ export class DefaultAppConfig implements AppConfig {
// source: 'suggestionSource', // source: 'suggestionSource',
// collectionId: 'collectionUUID' // collectionId: 'collectionUUID'
// } // }
// If not mapped the suggestion service won't be able to approve and import the related suggestion
// or load the fixed suggestions collections that can be configured here adding the source and the UUID of the collection
]; ];
// Theme Config // Theme Config

View File

@@ -1,46 +0,0 @@
import { Config } from './config.interface';
export interface UrnConfig extends Config {
name: string;
baseUrl: string;
}
export interface CrisRefConfig extends Config {
entityType: string;
icon: string;
}
export interface CrisLayoutMetadataBoxConfig extends Config {
defaultMetadataLabelColStyle: string;
defaultMetadataValueColStyle: string;
}
export interface CrisLayoutTypeConfig {
orientation: string;
}
export interface NavbarConfig extends Config {
showCommunityCollection: boolean;
}
export interface CrisItemPageConfig extends Config {
[entity: string]: CrisLayoutTypeConfig;
default: CrisLayoutTypeConfig;
}
export interface CrisLayoutConfig extends Config {
urn: UrnConfig[];
crisRef: CrisRefConfig[];
itemPage: CrisItemPageConfig;
metadataBox: CrisLayoutMetadataBoxConfig;
}
export interface LayoutConfig extends Config {
navbar: NavbarConfig;
}
export interface SuggestionConfig extends Config {
source: string;
collectionId: string;
}

View File

@@ -0,0 +1,6 @@
import { Config } from "./config.interface";
export interface SuggestionConfig extends Config {
source: string;
collectionId: string;
}