From 5d8d3e35d58f2151e37faff9191159b44c5a33c9 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Tue, 29 Mar 2022 16:16:22 +0200 Subject: [PATCH 01/94] [CST-5249] Added suggestions from openaire --- .../admin-notifications-routing-paths.ts | 9 + .../admin-notifications-routing.module.ts | 42 + ...uggestion-targets-page-resolver.service.ts | 32 + ...ons-suggestion-targets-page.component.html | 1 + ...ons-suggestion-targets-page.component.scss | 0 ...-suggestion-targets-page.component.spec.ts | 38 + ...tions-suggestion-targets-page.component.ts | 10 + .../admin-notifications.module.ts | 27 + src/app/admin/admin-routing-paths.ts | 5 + src/app/admin/admin-routing.module.ts | 7 +- .../admin-sidebar/admin-sidebar.component.ts | 38 +- src/app/app-routing.module.ts | 6 + src/app/core/core.module.ts | 6 + ...enaire-suggestion-objects.resource-type.ts | 25 + .../openaire-suggestion-source.model.ts | 47 + .../openaire-suggestion-target.model.ts | 60 + .../models/openaire-suggestion.model.ts | 85 + .../openaire-suggestions-data.service.ts | 301 +++ src/app/openaire/openaire.module.ts | 72 + src/app/openaire/openaire.reducer.ts | 19 + .../openaire/reciter-suggestions/selectors.ts | 97 + .../suggestion-actions.component.html | 28 + .../suggestion-actions.component.scss | 1 + .../suggestion-actions.component.ts | 92 + .../suggestion-evidences.component.html | 20 + .../suggestion-evidences.component.scss | 0 .../suggestion-evidences.component.ts | 15 + .../suggestion-list-element.component.html | 44 + .../suggestion-list-element.component.scss | 16 + .../suggestion-list-element.component.ts | 98 + .../suggestion-targets.actions.ts | 154 ++ .../suggestion-targets.component.html | 51 + .../suggestion-targets.component.scss | 0 .../suggestion-targets.component.ts | 150 ++ .../suggestion-targets.effects.ts | 110 + .../suggestion-targets.reducer.ts | 100 + .../suggestion-targets.state.service.ts | 164 ++ .../suggestions-notification.component.html | 8 + .../suggestions-notification.component.scss | 0 .../suggestions-notification.component.ts | 42 + .../suggestions-popup.component.html | 1 + .../suggestions-popup.component.scss | 0 .../suggestions-popup.component.spec.ts | 79 + .../suggestions-popup.component.ts | 67 + .../suggestions.service.ts | 296 +++ src/app/shared/mocks/openaire.mock.ts | 1797 +++++++++++++++++ .../mocks/reciter-suggestion-targets.mock.ts | 42 + .../shared/mocks/reciter-suggestion.mock.ts | 210 ++ .../suggestions-page-routing-paths.ts | 11 + .../suggestions-page-routing.module.ts | 36 + .../suggestions-page.component.html | 48 + .../suggestions-page.component.scss | 0 .../suggestions-page.component.spec.ts | 107 + .../suggestions-page.component.ts | 285 +++ .../suggestions-page.module.ts | 24 + .../suggestions-page.resolver.ts | 32 + src/assets/i18n/en.json5 | 65 + 57 files changed, 5112 insertions(+), 8 deletions(-) create mode 100644 src/app/admin/admin-notifications/admin-notifications-routing-paths.ts create mode 100644 src/app/admin/admin-notifications/admin-notifications-routing.module.ts create mode 100644 src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service.ts create mode 100644 src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.html create mode 100644 src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.scss create mode 100644 src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.spec.ts create mode 100644 src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.ts create mode 100644 src/app/admin/admin-notifications/admin-notifications.module.ts create mode 100644 src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-objects.resource-type.ts create mode 100644 src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts create mode 100644 src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts create mode 100644 src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts create mode 100644 src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts create mode 100644 src/app/openaire/openaire.module.ts create mode 100644 src/app/openaire/openaire.reducer.ts create mode 100644 src/app/openaire/reciter-suggestions/selectors.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.html create mode 100644 src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.scss create mode 100644 src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.html create mode 100644 src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.scss create mode 100644 src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html create mode 100644 src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.scss create mode 100644 src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.html create mode 100644 src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.scss create mode 100644 src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.html create mode 100644 src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.scss create mode 100644 src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.html create mode 100644 src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.scss create mode 100644 src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts create mode 100644 src/app/openaire/reciter-suggestions/suggestions.service.ts create mode 100644 src/app/shared/mocks/openaire.mock.ts create mode 100644 src/app/shared/mocks/reciter-suggestion-targets.mock.ts create mode 100644 src/app/shared/mocks/reciter-suggestion.mock.ts create mode 100644 src/app/suggestions-page/suggestions-page-routing-paths.ts create mode 100644 src/app/suggestions-page/suggestions-page-routing.module.ts create mode 100644 src/app/suggestions-page/suggestions-page.component.html create mode 100644 src/app/suggestions-page/suggestions-page.component.scss create mode 100644 src/app/suggestions-page/suggestions-page.component.spec.ts create mode 100644 src/app/suggestions-page/suggestions-page.component.ts create mode 100644 src/app/suggestions-page/suggestions-page.module.ts create mode 100644 src/app/suggestions-page/suggestions-page.resolver.ts diff --git a/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts b/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts new file mode 100644 index 0000000000..614b2ef49b --- /dev/null +++ b/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts @@ -0,0 +1,9 @@ +import { URLCombiner } from '../../core/url-combiner/url-combiner'; +import { getNotificationsModuleRoute } from '../admin-routing-paths'; + +export const NOTIFICATIONS_EDIT_PATH = 'openaire-broker'; +export const NOTIFICATIONS_RECITER_SUGGESTION_PATH = 'suggestion-targets'; + +export function getNotificationsOpenairebrokerRoute(id: string) { + return new URLCombiner(getNotificationsModuleRoute(), NOTIFICATIONS_EDIT_PATH, id).toString(); +} diff --git a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts new file mode 100644 index 0000000000..12bc7b9ec7 --- /dev/null +++ b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts @@ -0,0 +1,42 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { AuthenticatedGuard } from '../../core/auth/authenticated.guard'; +import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service'; +import { NOTIFICATIONS_EDIT_PATH, NOTIFICATIONS_RECITER_SUGGESTION_PATH } from './admin-notifications-routing-paths'; +import { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component'; +import { AdminNotificationsSuggestionTargetsPageResolver } from './admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + canActivate: [ AuthenticatedGuard ], + path: `${NOTIFICATIONS_RECITER_SUGGESTION_PATH}`, + component: AdminNotificationsSuggestionTargetsPageComponent, + pathMatch: 'full', + resolve: { + breadcrumb: I18nBreadcrumbResolver, + reciterSuggestionTargetParams: AdminNotificationsSuggestionTargetsPageResolver + }, + data: { + title: 'admin.notifications.recitersuggestion.page.title', + breadcrumbKey: 'admin.notifications.recitersuggestion', + showBreadcrumbsFluid: false + } + }, + ]) + ], + providers: [ + I18nBreadcrumbResolver, + I18nBreadcrumbsService, + AdminNotificationsSuggestionTargetsPageResolver + ] +}) +/** + * Routing module for the Notifications section of the admin sidebar + */ +export class AdminNotificationsRoutingModule { + +} diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service.ts b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service.ts new file mode 100644 index 0000000000..df1f4b81e6 --- /dev/null +++ b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; + +/** + * Interface for the route parameters. + */ +export interface AdminNotificationsSuggestionTargetsPageParams { + pageId?: string; + pageSize?: number; + currentPage?: number; +} + +/** + * This class represents a resolver that retrieve the route data before the route is activated. + */ +@Injectable() +export class AdminNotificationsSuggestionTargetsPageResolver implements Resolve { + + /** + * 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 + */ + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): AdminNotificationsSuggestionTargetsPageParams { + return { + pageId: route.queryParams.pageId, + pageSize: parseInt(route.queryParams.pageSize, 10), + currentPage: parseInt(route.queryParams.page, 10) + }; + } +} diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.html b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.html new file mode 100644 index 0000000000..5d06a1a6bd --- /dev/null +++ b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.html @@ -0,0 +1 @@ + diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.scss b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.spec.ts b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.spec.ts new file mode 100644 index 0000000000..f9e407f402 --- /dev/null +++ b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.spec.ts @@ -0,0 +1,38 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-suggestion-targets-page.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('AdminNotificationsSuggestionTargetsPageComponent', () => { + let component: AdminNotificationsSuggestionTargetsPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + CommonModule, + TranslateModule.forRoot() + ], + declarations: [ + AdminNotificationsSuggestionTargetsPageComponent + ], + providers: [ + AdminNotificationsSuggestionTargetsPageComponent + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AdminNotificationsSuggestionTargetsPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.ts b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.ts new file mode 100644 index 0000000000..a9a77f5089 --- /dev/null +++ b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ds-admin-notifications-reciter-page', + templateUrl: './admin-notifications-suggestion-targets-page.component.html', + styleUrls: ['./admin-notifications-suggestion-targets-page.component.scss'] +}) +export class AdminNotificationsSuggestionTargetsPageComponent { + +} diff --git a/src/app/admin/admin-notifications/admin-notifications.module.ts b/src/app/admin/admin-notifications/admin-notifications.module.ts new file mode 100644 index 0000000000..47125daad6 --- /dev/null +++ b/src/app/admin/admin-notifications/admin-notifications.module.ts @@ -0,0 +1,27 @@ +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 { OpenaireModule } from '../../openaire/openaire.module'; +import { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component'; + +@NgModule({ + imports: [ + CommonModule, + SharedModule, + CoreModule.forRoot(), + AdminNotificationsRoutingModule, + OpenaireModule + ], + declarations: [ + AdminNotificationsSuggestionTargetsPageComponent + ], + entryComponents: [] +}) +/** + * This module handles all components related to the notifications pages + */ +export class AdminNotificationsModule { + +} diff --git a/src/app/admin/admin-routing-paths.ts b/src/app/admin/admin-routing-paths.ts index 3168ea93c9..30f801cecb 100644 --- a/src/app/admin/admin-routing-paths.ts +++ b/src/app/admin/admin-routing-paths.ts @@ -2,7 +2,12 @@ import { URLCombiner } from '../core/url-combiner/url-combiner'; import { getAdminModuleRoute } from '../app-routing-paths'; export const REGISTRIES_MODULE_PATH = 'registries'; +export const NOTIFICATIONS_MODULE_PATH = 'notifications'; export function getRegistriesModuleRoute() { return new URLCombiner(getAdminModuleRoute(), REGISTRIES_MODULE_PATH).toString(); } + +export function getNotificationsModuleRoute() { + return new URLCombiner(getAdminModuleRoute(), NOTIFICATIONS_MODULE_PATH).toString(); +} diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts index ee5cb8737b..782a7faa38 100644 --- a/src/app/admin/admin-routing.module.ts +++ b/src/app/admin/admin-routing.module.ts @@ -6,11 +6,16 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component'; import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service'; import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component'; -import { REGISTRIES_MODULE_PATH } from './admin-routing-paths'; +import { REGISTRIES_MODULE_PATH, NOTIFICATIONS_MODULE_PATH } from './admin-routing-paths'; @NgModule({ imports: [ RouterModule.forChild([ + { + path: NOTIFICATIONS_MODULE_PATH, + loadChildren: () => import('./admin-notifications/admin-notifications.module') + .then((m) => m.AdminNotificationsModule), + }, { path: REGISTRIES_MODULE_PATH, loadChildren: () => import('./admin-registries/admin-registries.module') diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index dc9d2a817f..dd5d7e507a 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -22,6 +22,7 @@ import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { Router, ActivatedRoute } from '@angular/router'; +import {NOTIFICATIONS_RECITER_SUGGESTION_PATH} from "../admin-notifications/admin-notifications-routing-paths"; /** * Component representing the admin sidebar @@ -277,7 +278,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { // link: '' // } as LinkMenuItemModel, // icon: 'chart-bar', - // index: 8 + // index: 9 // }, /* Control Panel */ @@ -292,7 +293,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { // link: '' // } as LinkMenuItemModel, // icon: 'cogs', - // index: 9 + // index: 10 // }, /* Processes */ @@ -306,7 +307,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { link: '/processes' } as LinkMenuItemModel, icon: 'terminal', - index: 10 + index: 12 }, ]; menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { @@ -465,6 +466,29 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { createSiteAdministratorMenuSections() { this.authorizationService.isAuthorized(FeatureID.AdministratorOf).subscribe((authorized) => { const menuList = [ + /* Notifications */ + { + id: 'notifications', + active: false, + visible: authorized, + model: { + type: MenuItemType.TEXT, + text: 'menu.section.notifications' + } as TextMenuItemModel, + icon: 'bell', + index: 4 + }, + { + id: 'notifications_reciter', + parentID: 'notifications', + active: false, + visible: authorized, + model: { + type: MenuItemType.LINK, + text: 'menu.section.notifications_reciter', + link: '/admin/notifications/' + NOTIFICATIONS_RECITER_SUGGESTION_PATH + } as LinkMenuItemModel, + }, /* Admin Search */ { id: 'admin_search', @@ -476,7 +500,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { link: '/admin/search' } as LinkMenuItemModel, icon: 'search', - index: 5 + index: 6 }, /* Registries */ { @@ -488,7 +512,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { text: 'menu.section.registries' } as TextMenuItemModel, icon: 'list', - index: 6 + index: 7 }, { id: 'registries_metadata', @@ -524,7 +548,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { link: 'admin/curation-tasks' } as LinkMenuItemModel, icon: 'filter', - index: 7 + index: 8 }, /* Workflow */ @@ -601,7 +625,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit { text: 'menu.section.access_control' } as TextMenuItemModel, icon: 'key', - index: 4 + index: 5 }, ]; diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 88f7791b1b..9bf5518558 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -30,6 +30,7 @@ import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; import { ThemedPageInternalServerErrorComponent } from './page-internal-server-error/themed-page-internal-server-error.component'; import { ServerCheckGuard } from './core/server-check/server-check.guard'; +import { SUGGESTION_MODULE_PATH } from './suggestions-page/suggestions-page-routing-paths'; @NgModule({ imports: [ @@ -190,6 +191,11 @@ import { ServerCheckGuard } from './core/server-check/server-check.guard'; .then((m) => m.ProcessPageModule), canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] }, + { path: SUGGESTION_MODULE_PATH, + loadChildren: () => import('./suggestions-page/suggestions-page.module') + .then((m) => m.SuggestionsPageModule), + canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] + }, { path: INFO_MODULE_PATH, loadChildren: () => import('./info/info.module').then((m) => m.InfoModule) diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 8d8a614a89..6293fbd109 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -162,6 +162,9 @@ import { SearchConfig } from './shared/search/search-filters/search-config.model import { SequenceService } from './shared/sequence.service'; import { GroupDataService } from './eperson/group-data.service'; import { SubmissionAccessesModel } from './config/models/config-submission-accesses.model'; +import { OpenaireSuggestionTarget } from './openaire/reciter-suggestions/models/openaire-suggestion-target.model'; +import { OpenaireSuggestion } from './openaire/reciter-suggestions/models/openaire-suggestion.model'; +import { OpenaireSuggestionSource } from './openaire/reciter-suggestions/models/openaire-suggestion-source.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -343,6 +346,9 @@ export const models = ShortLivedToken, Registration, UsageReport, + OpenaireSuggestion, + OpenaireSuggestionTarget, + OpenaireSuggestionSource, Root, SearchConfig, SubmissionAccessesModel diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-objects.resource-type.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-objects.resource-type.ts new file mode 100644 index 0000000000..e31006959f --- /dev/null +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-objects.resource-type.ts @@ -0,0 +1,25 @@ +import { ResourceType } from '../../../shared/resource-type'; + +/** + * The resource type for the Suggestion Target object + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const SUGGESTION_TARGET = new ResourceType('suggestiontarget'); + +/** + * The resource type for the Suggestion Source object + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const SUGGESTION_SOURCE = new ResourceType('suggestionsource'); + +/** + * The resource type for the Suggestion object + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const SUGGESTION = new ResourceType('suggestion'); diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts new file mode 100644 index 0000000000..6da9fd47b9 --- /dev/null +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts @@ -0,0 +1,47 @@ +import { autoserialize, deserialize } from 'cerialize'; + +import { CacheableObject } from '../../../cache/object-cache.reducer'; +import { SUGGESTION_SOURCE } from './openaire-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'; + +/** + * The interface representing the Suggestion Source model + */ +@typedObject +export class OpenaireSuggestionSource implements CacheableObject { + /** + * A string representing the kind of object, e.g. community, item, … + */ + static type = SUGGESTION_SOURCE; + + /** + * The Suggestion Target id + */ + @autoserialize + id: string; + + /** + * The total number of suggestions provided by Suggestion Target for + */ + @autoserialize + total: number; + + /** + * The type of this ConfigObject + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The links to all related resources returned by the rest api. + */ + @deserialize + _links: { + self: HALLink, + suggestiontargets: HALLink + }; +} diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts new file mode 100644 index 0000000000..e35972bc79 --- /dev/null +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts @@ -0,0 +1,60 @@ +import { autoserialize, deserialize } from 'cerialize'; + +import { CacheableObject } from '../../../cache/object-cache.reducer'; +import { SUGGESTION_TARGET } from './openaire-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'; + +/** + * The interface representing the Suggestion Target model + */ +@typedObject +export class OpenaireSuggestionTarget implements CacheableObject { + /** + * A string representing the kind of object, e.g. community, item, … + */ + static type = SUGGESTION_TARGET; + + /** + * The Suggestion Target id + */ + @autoserialize + id: string; + + /** + * The Suggestion Target name to display + */ + @autoserialize + display: string; + + /** + * The Suggestion Target source to display + */ + @autoserialize + source: string; + + /** + * The total number of suggestions provided by Suggestion Target for + */ + @autoserialize + total: number; + + /** + * The type of this ConfigObject + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The links to all related resources returned by the rest api. + */ + @deserialize + _links: { + self: HALLink, + suggestions: HALLink, + target: HALLink + }; +} diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts new file mode 100644 index 0000000000..3ff5d7b630 --- /dev/null +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts @@ -0,0 +1,85 @@ +import { autoserialize, autoserializeAs, deserialize } from 'cerialize'; + +import { CacheableObject } from '../../../cache/object-cache.reducer'; +import { SUGGESTION } from './openaire-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'; + +export interface SuggestionEvidences { + [sectionId: string]: { + score: string; + notes: string + }; +} +/** + * The interface representing the Suggestion Source model + */ +@typedObject +export class OpenaireSuggestion implements CacheableObject { + /** + * A string representing the kind of object, e.g. community, item, … + */ + static type = SUGGESTION; + + /** + * The Suggestion id + */ + @autoserialize + id: string; + + /** + * The Suggestion name to display + */ + @autoserialize + display: string; + + /** + * The Suggestion source to display + */ + @autoserialize + source: string; + + /** + * The Suggestion external source uri + */ + @autoserialize + externalSourceUri: string; + + /** + * The Total Score of the suggestion + */ + @autoserialize + score: string; + + /** + * The total number of suggestions provided by Suggestion Target for + */ + @autoserialize + evidences: SuggestionEvidences; + + /** + * All metadata of this suggestion object + */ + @excludeFromEquals + @autoserializeAs(MetadataMapSerializer) + metadata: MetadataMap; + + /** + * The type of this ConfigObject + */ + @excludeFromEquals + @autoserialize + type: ResourceType; + + /** + * The links to all related resources returned by the rest api. + */ + @deserialize + _links: { + self: HALLink, + target: HALLink + }; +} diff --git a/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts b/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts new file mode 100644 index 0000000000..d961eaf9b5 --- /dev/null +++ b/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts @@ -0,0 +1,301 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Store } from '@ngrx/store'; + +import { Observable } from 'rxjs'; + +import { CoreState } from '../../core.reducers'; +import { HALEndpointService } from '../../shared/hal-endpoint.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../../cache/object-cache.service'; +import { dataService } from '../../cache/builders/build-decorators'; +import { RequestService } from '../../data/request.service'; +import { FindListOptions } from '../../data/request.models'; +import { DataService } from '../../data/data.service'; +import { ChangeAnalyzer } from '../../data/change-analyzer'; +import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service'; +import { RemoteData } from '../../data/remote-data'; +import { SUGGESTION_TARGET } from './models/openaire-suggestion-objects.resource-type'; +import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; +import { PaginatedList } from '../../data/paginated-list.model'; +import { OpenaireSuggestionSource } from './models/openaire-suggestion-source.model'; +import { OpenaireSuggestionTarget } from './models/openaire-suggestion-target.model'; +import { OpenaireSuggestion } from './models/openaire-suggestion.model'; +import { RequestParam } from '../../cache/models/request-param.model'; +import { NoContent } from '../../shared/NoContent.model'; + +/* tslint:disable:max-classes-per-file */ + +/** + * A private DataService implementation to delegate specific methods to. + */ +class SuggestionDataServiceImpl extends DataService { + /** + * The REST endpoint. + */ + protected linkPath = 'suggestions'; + + /** + * Initialize service variables + * @param {RequestService} requestService + * @param {RemoteDataBuildService} rdbService + * @param {Store} store + * @param {ObjectCacheService} objectCache + * @param {HALEndpointService} halService + * @param {NotificationsService} notificationsService + * @param {HttpClient} http + * @param {ChangeAnalyzer} comparator + */ + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: ChangeAnalyzer) { + super(); + } +} + +/** + * A private DataService implementation to delegate specific methods to. + */ +class SuggestionTargetsDataServiceImpl extends DataService { + /** + * The REST endpoint. + */ + protected linkPath = 'suggestiontargets'; + + /** + * Initialize service variables + * @param {RequestService} requestService + * @param {RemoteDataBuildService} rdbService + * @param {Store} store + * @param {ObjectCacheService} objectCache + * @param {HALEndpointService} halService + * @param {NotificationsService} notificationsService + * @param {HttpClient} http + * @param {ChangeAnalyzer} comparator + */ + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: ChangeAnalyzer) { + super(); + } +} + +/** + * A private DataService implementation to delegate specific methods to. + */ +class SuggestionSourcesDataServiceImpl extends DataService { + /** + * The REST endpoint. + */ + protected linkPath = 'suggestionsources'; + + /** + * Initialize service variables + * @param {RequestService} requestService + * @param {RemoteDataBuildService} rdbService + * @param {Store} store + * @param {ObjectCacheService} objectCache + * @param {HALEndpointService} halService + * @param {NotificationsService} notificationsService + * @param {HttpClient} http + * @param {ChangeAnalyzer} comparator + */ + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: ChangeAnalyzer) { + super(); + } +} + +/** + * The service handling all Suggestion Target REST requests. + */ +@Injectable() +@dataService(SUGGESTION_TARGET) +export class OpenaireSuggestionsDataService { + protected searchFindBySourceMethod = 'findBySource'; + protected searchFindByTargetMethod = 'findByTarget'; + protected searchFindByTargetAndSourceMethod = 'findByTargetAndSource'; + + /** + * A private DataService implementation to delegate specific methods to. + */ + private suggestionsDataService: SuggestionDataServiceImpl; + + /** + * A private DataService implementation to delegate specific methods to. + */ + private suggestionSourcesDataService: SuggestionSourcesDataServiceImpl; + + /** + * A private DataService implementation to delegate specific methods to. + */ + private suggestionTargetsDataService: SuggestionTargetsDataServiceImpl; + + /** + * Initialize service variables + * @param {RequestService} requestService + * @param {RemoteDataBuildService} rdbService + * @param {ObjectCacheService} objectCache + * @param {HALEndpointService} halService + * @param {NotificationsService} notificationsService + * @param {HttpClient} http + * @param {DefaultChangeAnalyzer} comparatorSuggestions + * @param {DefaultChangeAnalyzer} comparatorSources + * @param {DefaultChangeAnalyzer} comparatorTargets + */ + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparatorSuggestions: DefaultChangeAnalyzer, + protected comparatorSources: DefaultChangeAnalyzer, + protected comparatorTargets: DefaultChangeAnalyzer, + ) { + this.suggestionsDataService = new SuggestionDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSuggestions); + this.suggestionSourcesDataService = new SuggestionSourcesDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSources); + this.suggestionTargetsDataService = new SuggestionTargetsDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorTargets); + } + + /** + * Return the list of Suggestion Target + * + * @param options + * Find list options object. + * @return Observable>> + * The list of Suggestion Sources. + */ + public getSources(options: FindListOptions = {}): Observable>> { + return this.suggestionSourcesDataService.findAll(options); + } + + /** + * 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>> + * The list of Suggestion Target. + */ + public getTargets( + source: string, + options: FindListOptions = {}, + ...linksToFollow: FollowLinkConfig[] + ): Observable>> { + options.searchParams = [new RequestParam('source', source)]; + + return this.suggestionTargetsDataService.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>> + * The list of Suggestion Target. + */ + public getTargetsByUser( + userId: string, + options: FindListOptions = {}, + ...linksToFollow: FollowLinkConfig[] + ): Observable>> { + options.searchParams = [new RequestParam('target', userId)]; + + return this.suggestionTargetsDataService.searchBy(this.searchFindByTargetMethod, options, true, true, ...linksToFollow); + } + + /** + * Return a Suggestion Target for a given id + * + * @param targetId + * The target id to retrieve. + * + * @return Observable> + * The list of Suggestion Target. + */ + public getTargetById(targetId: string): Observable> { + return this.suggestionTargetsDataService.findById(targetId); + } + + /** + * Used to delete Suggestion + * @suggestionId + */ + public deleteSuggestion(suggestionId: string): Observable> { + return this.suggestionsDataService.delete(suggestionId); + } + + /** + * Used to fetch Suggestion notification for user + * @suggestionId + */ + public getSuggestion(suggestionId: string, ...linksToFollow: FollowLinkConfig[]): Observable> { + return this.suggestionsDataService.findById(suggestionId, true, true, ...linksToFollow); + } + + /** + * Return the list of Suggestion for a given target and source + * + * @param target + * The target for which to find suggestions. + * @param source + * The source for which to find suggestions. + * @param options + * Find list options object. + * @param linksToFollow + * List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved. + * @return Observable>> + * The list of Suggestion. + */ + public getSuggestionsByTargetAndSource( + target: string, + source: string, + options: FindListOptions = {}, + ...linksToFollow: FollowLinkConfig[] + ): Observable>> { + options.searchParams = [ + new RequestParam('target', target), + new RequestParam('source', source) + ]; + + return this.suggestionsDataService.searchBy(this.searchFindByTargetAndSourceMethod, options, true, true, ...linksToFollow); + } + + /** + * Clear findByTargetAndSource suggestions requests from cache + */ + public clearSuggestionRequests() { + this.requestService.setStaleByHrefSubstring(this.searchFindByTargetAndSourceMethod); + } +} diff --git a/src/app/openaire/openaire.module.ts b/src/app/openaire/openaire.module.ts new file mode 100644 index 0000000000..22d04f3002 --- /dev/null +++ b/src/app/openaire/openaire.module.ts @@ -0,0 +1,72 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Action, StoreConfig, StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { CoreModule } from '../core/core.module'; +import { SharedModule } from '../shared/shared.module'; +import { storeModuleConfig } from '../app.reducer'; +import { openaireReducers, OpenaireState } from './openaire.reducer'; +import { SuggestionTargetsStateService } from './reciter-suggestions/suggestion-targets/suggestion-targets.state.service'; +import { SuggestionsService } from './reciter-suggestions/suggestions.service'; +import { OpenaireSuggestionsDataService } from '../core/openaire/reciter-suggestions/openaire-suggestions-data.service'; +import { SuggestionTargetsComponent } from './reciter-suggestions/suggestion-targets/suggestion-targets.component'; +import { SuggestionListElementComponent } from './reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; +import { SuggestionEvidencesComponent } from './reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component'; +import { SuggestionActionsComponent } from './reciter-suggestions/suggestion-actions/suggestion-actions.component'; +import { SuggestionsPopupComponent } from './reciter-suggestions/suggestions-popup/suggestions-popup.component'; +import { SuggestionsNotificationComponent } from './reciter-suggestions/suggestions-notification/suggestions-notification.component'; +import { TranslateModule } from '@ngx-translate/core'; +import { SearchModule } from '../shared/search/search.module'; + +const MODULES = [ + CommonModule, + SharedModule, + CoreModule.forRoot(), + StoreModule.forFeature('openaire', openaireReducers, storeModuleConfig as StoreConfig), + TranslateModule +]; + +const COMPONENTS = [ + SuggestionTargetsComponent, + SuggestionActionsComponent, + SuggestionListElementComponent, + SuggestionEvidencesComponent, + SuggestionsPopupComponent, + SuggestionsNotificationComponent +]; + +const DIRECTIVES = [ ]; + + +const PROVIDERS = [ + SuggestionTargetsStateService, + SuggestionsService, + OpenaireSuggestionsDataService +]; + +@NgModule({ + imports: [ + ...MODULES, + SearchModule + ], + declarations: [ + ...COMPONENTS, + ...DIRECTIVES, + ], + providers: [ + ...PROVIDERS + ], + entryComponents: [ + ], + exports: [ + ...COMPONENTS, + ...DIRECTIVES + ] +}) + +/** + * This module handles all components that are necessary for the OpenAIRE components + */ +export class OpenaireModule { +} diff --git a/src/app/openaire/openaire.reducer.ts b/src/app/openaire/openaire.reducer.ts new file mode 100644 index 0000000000..cacf3c7283 --- /dev/null +++ b/src/app/openaire/openaire.reducer.ts @@ -0,0 +1,19 @@ +import { ActionReducerMap, createFeatureSelector } from '@ngrx/store'; + +import { + SuggestionTargetsReducer, + SuggestionTargetState +} from './reciter-suggestions/suggestion-targets/suggestion-targets.reducer'; + +/** + * The OpenAIRE State + */ +export interface OpenaireState { + 'suggestionTarget': SuggestionTargetState; +} + +export const openaireReducers: ActionReducerMap = { + suggestionTarget: SuggestionTargetsReducer, +}; + +export const openaireSelector = createFeatureSelector('openaire'); diff --git a/src/app/openaire/reciter-suggestions/selectors.ts b/src/app/openaire/reciter-suggestions/selectors.ts new file mode 100644 index 0000000000..e699b27dba --- /dev/null +++ b/src/app/openaire/reciter-suggestions/selectors.ts @@ -0,0 +1,97 @@ +import { createSelector, MemoizedSelector } from '@ngrx/store'; +import { subStateSelector } from '../../shared/selector.util'; +import { openaireSelector, OpenaireState } from '../openaire.reducer'; +import { OpenaireSuggestionTarget } from '../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTargetState } from './suggestion-targets/suggestion-targets.reducer'; + +/** + * Returns the Reciter Suggestion Target state. + * @function _getReciterSuggestionTargetState + * @param {AppState} state Top level state. + * @return {OpenaireState} + */ +const _getReciterSuggestionTargetState = (state: any) => state.openaire; + +// Reciter Suggestion Targets +// ---------------------------------------------------------------------------- + +/** + * Returns the Reciter Suggestion Targets State. + * @function reciterSuggestionTargetStateSelector + * @return {OpenaireState} + */ +export function reciterSuggestionTargetStateSelector(): MemoizedSelector { + return subStateSelector(openaireSelector, 'suggestionTarget'); +} + +/** + * Returns the Reciter Suggestion Targets list. + * @function reciterSuggestionTargetObjectSelector + * @return {OpenaireReciterSuggestionTarget[]} + */ +export function reciterSuggestionTargetObjectSelector(): MemoizedSelector { + return subStateSelector(reciterSuggestionTargetStateSelector(), 'targets'); +} + +/** + * Returns true if the Reciter Suggestion Targets are loaded. + * @function isReciterSuggestionTargetLoadedSelector + * @return {boolean} + */ +export const isReciterSuggestionTargetLoadedSelector = createSelector(_getReciterSuggestionTargetState, + (state: OpenaireState) => state.suggestionTarget.loaded +); + +/** + * Returns true if the deduplication sets are processing. + * @function isDeduplicationSetsProcessingSelector + * @return {boolean} + */ +export const isreciterSuggestionTargetProcessingSelector = createSelector(_getReciterSuggestionTargetState, + (state: OpenaireState) => state.suggestionTarget.processing +); + +/** + * Returns the total available pages of Reciter Suggestion Targets. + * @function getreciterSuggestionTargetTotalPagesSelector + * @return {number} + */ +export const getreciterSuggestionTargetTotalPagesSelector = createSelector(_getReciterSuggestionTargetState, + (state: OpenaireState) => state.suggestionTarget.totalPages +); + +/** + * Returns the current page of Reciter Suggestion Targets. + * @function getreciterSuggestionTargetCurrentPageSelector + * @return {number} + */ +export const getreciterSuggestionTargetCurrentPageSelector = createSelector(_getReciterSuggestionTargetState, + (state: OpenaireState) => state.suggestionTarget.currentPage +); + +/** + * Returns the total number of Reciter Suggestion Targets. + * @function getreciterSuggestionTargetTotalsSelector + * @return {number} + */ +export const getreciterSuggestionTargetTotalsSelector = createSelector(_getReciterSuggestionTargetState, + (state: OpenaireState) => state.suggestionTarget.totalElements +); + +/** + * Returns Suggestion Targets for the current user. + * @function getCurrentUserReciterSuggestionTargetSelector + * @return {OpenaireSuggestionTarget[]} + */ +export const getCurrentUserSuggestionTargetsSelector = createSelector(_getReciterSuggestionTargetState, + (state: OpenaireState) => state.suggestionTarget.currentUserTargets +); + +/** + * Returns whether or not the user has consulted their suggestions + * @function getCurrentUserReciterSuggestionTargetSelector + * @return {boolean} + */ +export const getCurrentUserSuggestionTargetsVisitedSelector = createSelector(_getReciterSuggestionTargetState, + (state: OpenaireState) => state.suggestionTarget.currentUserTargetsVisited +); diff --git a/src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.html b/src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.html new file mode 100644 index 0000000000..7ec3e61395 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.html @@ -0,0 +1,28 @@ +
+
+ + + + + + + +
+ + +
diff --git a/src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.scss b/src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.scss new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.scss @@ -0,0 +1 @@ + diff --git a/src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.ts b/src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.ts new file mode 100644 index 0000000000..cf21901123 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-actions/suggestion-actions.component.ts @@ -0,0 +1,92 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ItemType } from '../../../core/shared/item-relationships/item-type.model'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { OpenaireSuggestion } from '../../../core/openaire/reciter-suggestions/models/openaire-suggestion.model'; +import { SuggestionApproveAndImport } from '../suggestion-list-element/suggestion-list-element.component'; +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'; + +@Component({ + selector: 'ds-suggestion-actions', + styleUrls: [ './suggestion-actions.component.scss' ], + templateUrl: './suggestion-actions.component.html' +}) +export class SuggestionActionsComponent { + + @Input() object: OpenaireSuggestion; + + @Input() isBulk = false; + + @Input() hasEvidence = false; + + @Input() seeEvidence = false; + + @Input() isCollectionFixed = false; + + /** + * The component is used to Delete suggestion + */ + @Output() notMineClicked = new EventEmitter(); + + /** + * The component is used to approve & import + */ + @Output() approveAndImport = new EventEmitter(); + + /** + * The component is used to approve & import + */ + @Output() seeEvidences = new EventEmitter(); + + constructor(private modalService: NgbModal) { } + + /** + * Method called on clicking the button "approve & import", It opens a dialog for + * select a collection and it emits an approveAndImport event. + */ + openDialog(entity: ItemType) { + + const modalRef = this.modalService.open(CreateItemParentSelectorComponent); + modalRef.componentInstance.emitOnly = true; + modalRef.componentInstance.entityType = entity.label; + + modalRef.componentInstance.select.pipe(take(1)) + .subscribe((collection: Collection) => { + this.approveAndImport.emit({ + suggestion: this.isBulk ? undefined : this.object, + collectionId: collection.id + }); + }); + } + + approveAndImportCollectionFixed() { + this.approveAndImport.emit({ + suggestion: this.isBulk ? undefined : this.object, + collectionId: null + }); + } + + + /** + * Delete the suggestion + */ + notMine() { + this.notMineClicked.emit(this.isBulk ? undefined : this.object.id); + } + + /** + * Toggle See Evidence + */ + toggleSeeEvidences() { + this.seeEvidences.emit(!this.seeEvidence); + } + + notMineLabel(): string { + return this.isBulk ? 'reciter.suggestion.notMine.bulk' : 'reciter.suggestion.notMine' ; + } + + approveAndImportLabel(): string { + return this.isBulk ? 'reciter.suggestion.approveAndImport.bulk' : 'reciter.suggestion.approveAndImport'; + } +} diff --git a/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.html b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.html new file mode 100644 index 0000000000..5ad4f0a978 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.html @@ -0,0 +1,20 @@ +
+
+ + + + + + + + + + + + + + + +
{{'reciter.suggestion.evidence.score' | translate}}{{'reciter.suggestion.evidence.type' | translate}}{{'reciter.suggestion.evidence.notes' | translate}}
{{evidences[evidence].score}}{{evidence | translate}}{{evidences[evidence].notes}}
+
+
diff --git a/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.scss b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts new file mode 100644 index 0000000000..e0536ae723 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts @@ -0,0 +1,15 @@ +import { Component, Input } from '@angular/core'; +import { fadeIn } from '../../../../shared/animations/fade'; +import { SuggestionEvidences } from '../../../../core/openaire/reciter-suggestions/models/openaire-suggestion.model'; + +@Component({ + selector: 'ds-suggestion-evidences', + styleUrls: [ './suggestion-evidences.component.scss' ], + templateUrl: './suggestion-evidences.component.html', + animations: [fadeIn] +}) +export class SuggestionEvidencesComponent { + + @Input() evidences: SuggestionEvidences; + +} diff --git a/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html new file mode 100644 index 0000000000..05f9c0ac77 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html @@ -0,0 +1,44 @@ +
+
+ +
+
+ +
+
+ +
+
+
{{'reciter.suggestion.totalScore' | translate}}
+ {{ object.score }} +
+
+ +
+ + + + +
+
+ +
+
+ +
+
+
+
diff --git a/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.scss b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.scss new file mode 100644 index 0000000000..1c52209518 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.scss @@ -0,0 +1,16 @@ +.issue-date { + color: #c8c8c8; +} + +.parent { + display: flex; + gap:10px; +} + +.import { + flex: initial; +} + +.suggestion-score { + font-size: 1.5rem; +} diff --git a/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts new file mode 100644 index 0000000000..8227dc3213 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts @@ -0,0 +1,98 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { fadeIn } from '../../../shared/animations/fade'; +import { OpenaireSuggestion } from '../../../core/openaire/reciter-suggestions/models/openaire-suggestion.model'; +import { Item } from '../../../core/shared/item.model'; +import { isNotEmpty } from '../../../shared/empty.util'; + +export interface SuggestionApproveAndImport { + suggestion: OpenaireSuggestion; + collectionId: string; +} + +@Component({ + selector: 'ds-suggestion-list-item', + styleUrls: ['./suggestion-list-element.component.scss'], + templateUrl: './suggestion-list-element.component.html', + animations: [fadeIn] +}) +export class SuggestionListElementComponent implements OnInit { + + @Input() object: OpenaireSuggestion; + + @Input() isSelected = false; + + @Input() isCollectionFixed = false; + + public listableObject: any; + + public seeEvidence = false; + + /** + * The component is used to Delete suggestion + */ + @Output() notMineClicked = new EventEmitter(); + + /** + * The component is used to approve & import + */ + @Output() approveAndImport = new EventEmitter(); + + /** + * New value whether the element is selected + */ + @Output() selected = new EventEmitter(); + + /** + * Initialize instance variables + * + * @param {NgbModal} modalService + */ + constructor(private modalService: NgbModal) { } + + ngOnInit() { + this.listableObject = { + indexableObject: Object.assign(new Item(), {id: this.object.id, metadata: this.object.metadata}), + hitHighlights: {} + }; + } + + /** + * Approve and import the suggestion + */ + onApproveAndImport(event: SuggestionApproveAndImport) { + this.approveAndImport.emit(event); + } + + /** + * Delete the suggestion + */ + onNotMine(suggestionId: string) { + this.notMineClicked.emit(suggestionId); + } + + /** + * Change is selected value. + */ + changeSelected(event) { + this.isSelected = event.target.checked; + this.selected.next(this.isSelected); + } + + /** + * See the Evidence + */ + hasEvidences() { + return isNotEmpty(this.object.evidences); + } + + /** + * Set the see evidence variable. + */ + onSeeEvidences(seeEvidence: boolean) { + this.seeEvidence = seeEvidence; + } + +} diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts new file mode 100644 index 0000000000..6c44d40b91 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts @@ -0,0 +1,154 @@ +import { Action } from '@ngrx/store'; +import { type } from '../../../shared/ngrx/type'; +import { OpenaireSuggestionTarget } from '../../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; + +/** + * For each action type in an action group, make a simple + * enum object for all of this group's action types. + * + * The 'type' utility function coerces strings into string + * literal types and runs a simple check to guarantee all + * action types in the application are unique. + */ +export const SuggestionTargetActionTypes = { + ADD_TARGETS: type('dspace/integration/openaire/suggestions/target/ADD_TARGETS'), + CLEAR_TARGETS: type('dspace/integration/openaire/suggestions/target/CLEAR_TARGETS'), + RETRIEVE_TARGETS_BY_SOURCE: type('dspace/integration/openaire/suggestions/target/RETRIEVE_TARGETS_BY_SOURCE'), + RETRIEVE_TARGETS_BY_SOURCE_ERROR: type('dspace/integration/openaire/suggestions/target/RETRIEVE_TARGETS_BY_SOURCE_ERROR'), + ADD_USER_SUGGESTIONS: type('dspace/integration/openaire/suggestions/target/ADD_USER_SUGGESTIONS'), + REFRESH_USER_SUGGESTIONS: type('dspace/integration/openaire/suggestions/target/REFRESH_USER_SUGGESTIONS'), + MARK_USER_SUGGESTIONS_AS_VISITED: type('dspace/integration/openaire/suggestions/target/MARK_USER_SUGGESTIONS_AS_VISITED') +}; + +/* tslint:disable:max-classes-per-file */ + +/** + * An ngrx action to retrieve all the Suggestion Targets. + */ +export class RetrieveTargetsBySourceAction implements Action { + type = SuggestionTargetActionTypes.RETRIEVE_TARGETS_BY_SOURCE; + payload: { + source: string; + elementsPerPage: number; + currentPage: number; + }; + + /** + * Create a new RetrieveTargetsBySourceAction. + * + * @param source + * the source for which to retrieve suggestion targets + * @param elementsPerPage + * the number of targets per page + * @param currentPage + * The page number to retrieve + */ + constructor(source: string, elementsPerPage: number, currentPage: number) { + this.payload = { + source, + elementsPerPage, + currentPage + }; + } +} + +/** + * An ngrx action for retrieving 'all Suggestion Targets' error. + */ +export class RetrieveAllTargetsErrorAction implements Action { + type = SuggestionTargetActionTypes.RETRIEVE_TARGETS_BY_SOURCE_ERROR; +} + +/** + * An ngrx action to load the Suggestion Target objects. + */ +export class AddTargetAction implements Action { + type = SuggestionTargetActionTypes.ADD_TARGETS; + payload: { + targets: OpenaireSuggestionTarget[]; + totalPages: number; + currentPage: number; + totalElements: number; + }; + + /** + * Create a new AddTargetAction. + * + * @param targets + * the list of targets + * @param totalPages + * the total available pages of targets + * @param currentPage + * the current page + * @param totalElements + * the total available Suggestion Targets + */ + constructor(targets: OpenaireSuggestionTarget[], totalPages: number, currentPage: number, totalElements: number) { + this.payload = { + targets, + totalPages, + currentPage, + totalElements + }; + } + +} + +/** + * An ngrx action to load the user Suggestion Target object. + * Called by the ??? effect. + */ +export class AddUserSuggestionsAction implements Action { + type = SuggestionTargetActionTypes.ADD_USER_SUGGESTIONS; + payload: { + suggestionTargets: OpenaireSuggestionTarget[]; + }; + + /** + * Create a new AddUserSuggestionsAction. + * + * @param suggestionTargets + * the user suggestions target + */ + constructor(suggestionTargets: OpenaireSuggestionTarget[]) { + this.payload = { suggestionTargets }; + } + +} + +/** + * An ngrx action to reload the user Suggestion Target object. + * Called by the ??? effect. + */ +export class RefreshUserSuggestionsAction implements Action { + type = SuggestionTargetActionTypes.REFRESH_USER_SUGGESTIONS; +} + +/** + * An ngrx action to Mark User Suggestions As Visited. + * Called by the ??? effect. + */ +export class MarkUserSuggestionsAsVisitedAction implements Action { + type = SuggestionTargetActionTypes.MARK_USER_SUGGESTIONS_AS_VISITED; +} + +/** + * An ngrx action to clear targets state. + */ +export class ClearSuggestionTargetsAction implements Action { + type = SuggestionTargetActionTypes.CLEAR_TARGETS; +} + +/* tslint:enable:max-classes-per-file */ + +/** + * Export a type alias of all actions in this action group + * so that reducers can easily compose action types. + */ +export type SuggestionTargetsActions + = AddTargetAction + | AddUserSuggestionsAction + | ClearSuggestionTargetsAction + | MarkUserSuggestionsAsVisitedAction + | RetrieveTargetsBySourceAction + | RetrieveAllTargetsErrorAction; diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.html b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.html new file mode 100644 index 0000000000..791e694ba9 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.html @@ -0,0 +1,51 @@ +
+
+
+ + + + + + + + +
+ + + + + + + + + + + + + +
{{'reciter.suggestion.table.name' | translate}}{{'reciter.suggestion.table.actions' | translate}}
+ {{targetElement.display}} + +
+ +
+
+
+
+
+
+
+
diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.scss b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts new file mode 100644 index 0000000000..b9ed6c4e87 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts @@ -0,0 +1,150 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { Observable, Subscription } from 'rxjs'; +import { distinctUntilChanged, take } from 'rxjs/operators'; + +import { OpenaireSuggestionTarget } from '../../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; +import { hasValue } from '../../../shared/empty.util'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SuggestionTargetsStateService } from './suggestion-targets.state.service'; +import { getSuggestionPageRoute } from '../../../suggestions-page/suggestions-page-routing-paths'; +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'], +}) +export class SuggestionTargetsComponent implements OnInit { + + /** + * The source for which to list targets + */ + @Input() source: string; + + /** + * The pagination system configuration for HTML listing. + * @type {PaginationComponentOptions} + */ + public paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { + id: 'stp', + pageSizeOptions: [5, 10, 20, 40, 60] + }); + + /** + * The Suggestion Target list. + */ + public targets$: Observable; + /** + * The total number of Suggestion Targets. + */ + public totalElements$: Observable; + /** + * Array to track all the component subscriptions. Useful to unsubscribe them with 'onDestroy'. + * @type {Array} + */ + protected subs: Subscription[] = []; + + /** + * Initialize the component variables. + * @param {PaginationService} paginationService + * @param {SuggestionTargetsStateService} suggestionTargetsStateService + * @param {SuggestionsService} suggestionService + * @param {Router} router + */ + constructor( + private paginationService: PaginationService, + private suggestionTargetsStateService: SuggestionTargetsStateService, + private suggestionService: SuggestionsService, + private router: Router + ) { + } + + /** + * Component initialization. + */ + ngOnInit(): void { + this.targets$ = this.suggestionTargetsStateService.getReciterSuggestionTargets(); + this.totalElements$ = this.suggestionTargetsStateService.getReciterSuggestionTargetsTotals(); + } + + /** + * First Suggestion Targets loading after view initialization. + */ + ngAfterViewInit(): void { + this.subs.push( + this.suggestionTargetsStateService.isReciterSuggestionTargetsLoaded().pipe( + take(1) + ).subscribe(() => { + this.getSuggestionTargets(); + }) + ); + } + + /** + * Returns the information about the loading status of the Suggestion Targets (if it's running or not). + * + * @return Observable + * 'true' if the targets are loading, 'false' otherwise. + */ + public isTargetsLoading(): Observable { + return this.suggestionTargetsStateService.isReciterSuggestionTargetsLoading(); + } + + /** + * Returns the information about the processing status of the Suggestion Targets (if it's running or not). + * + * @return Observable + * 'true' if there are operations running on the targets (ex.: a REST call), 'false' otherwise. + */ + public isTargetsProcessing(): Observable { + return this.suggestionTargetsStateService.isReciterSuggestionTargetsProcessing(); + } + + /** + * Redirect to suggestion page. + * + * @param {string} id + * the id of suggestion target + * @param {string} name + * the name of suggestion target + */ + public redirectToSuggestions(id: string, name: string) { + this.router.navigate([getSuggestionPageRoute(id)]); + } + + /** + * Unsubscribe from all subscriptions. + */ + ngOnDestroy(): void { + this.suggestionTargetsStateService.dispatchClearSuggestionTargetsAction(); + this.subs + .filter((sub) => hasValue(sub)) + .forEach((sub) => sub.unsubscribe()); + } + + /** + * Dispatch the Suggestion Targets retrival. + */ + public getSuggestionTargets(): void { + this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig).pipe( + distinctUntilChanged(), + take(1) + ).subscribe((options: PaginationComponentOptions) => { + this.suggestionTargetsStateService.dispatchRetrieveReciterSuggestionTargets( + this.source, + options.pageSize, + options.currentPage + ); + }); + } + + public getTargetUuid(target: OpenaireSuggestionTarget) { + return this.suggestionService.getTargetUuid(target); + } +} diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts new file mode 100644 index 0000000000..85e871403c --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts @@ -0,0 +1,110 @@ +import { Injectable } from '@angular/core'; + +import { Store } from '@ngrx/store'; +import { Actions, Effect, ofType } from '@ngrx/effects'; +import { TranslateService } from '@ngx-translate/core'; +import { catchError, map, switchMap, tap } from 'rxjs/operators'; +import { of } from 'rxjs'; + +import { + AddTargetAction, + AddUserSuggestionsAction, + RefreshUserSuggestionsAction, + RetrieveAllTargetsErrorAction, + RetrieveTargetsBySourceAction, + SuggestionTargetActionTypes, +} from './suggestion-targets.actions'; +import { PaginatedList } from '../../../core/data/paginated-list.model'; +import { SuggestionsService } from '../suggestions.service'; +import { NotificationsService } from '../../../shared/notifications/notifications.service'; +import { AuthActionTypes, RetrieveAuthenticatedEpersonSuccessAction } from '../../../core/auth/auth.actions'; +import { OpenaireSuggestionTarget } from '../../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; +import { EPerson } from '../../../core/eperson/models/eperson.model'; + +/** + * Provides effect methods for the Suggestion Targets actions. + */ +@Injectable() +export class SuggestionTargetsEffects { + + /** + * Retrieve all Suggestion Targets managing pagination and errors. + */ + @Effect() retrieveTargetsBySource$ = this.actions$.pipe( + ofType(SuggestionTargetActionTypes.RETRIEVE_TARGETS_BY_SOURCE), + switchMap((action: RetrieveTargetsBySourceAction) => { + return this.suggestionsService.getTargets( + action.payload.source, + action.payload.elementsPerPage, + action.payload.currentPage + ).pipe( + map((targets: PaginatedList) => + new AddTargetAction(targets.page, targets.totalPages, targets.currentPage, targets.totalElements) + ), + catchError((error: Error) => { + if (error) { + console.error(error.message); + } + return of(new RetrieveAllTargetsErrorAction()); + }) + ); + }) + ); + + /** + * Show a notification on error. + */ + @Effect({ dispatch: false }) retrieveAllTargetsErrorAction$ = this.actions$.pipe( + ofType(SuggestionTargetActionTypes.RETRIEVE_TARGETS_BY_SOURCE_ERROR), + tap(() => { + this.notificationsService.error(null, this.translate.get('reciter.suggestion.target.error.service.retrieve')); + }) + ); + + /** + * Show a notification on error. + */ + @Effect() retrieveUserTargets$ = this.actions$.pipe( + ofType(AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS), + switchMap((action: RetrieveAuthenticatedEpersonSuccessAction) => { + return this.suggestionsService.retrieveCurrentUserSuggestions(action.payload).pipe( + map((suggestionTargets: OpenaireSuggestionTarget[]) => new AddUserSuggestionsAction(suggestionTargets)) + ); + })); + + /** + * Fetch the current user suggestion + */ + @Effect() refreshUserTargets$ = this.actions$.pipe( + ofType(SuggestionTargetActionTypes.REFRESH_USER_SUGGESTIONS), + switchMap((action: RefreshUserSuggestionsAction) => { + return this.store$.select((state: any) => state.core.auth.user) + .pipe( + switchMap((user: EPerson) => { + return this.suggestionsService.retrieveCurrentUserSuggestions(user) + .pipe( + map((suggestionTargets: OpenaireSuggestionTarget[]) => new AddUserSuggestionsAction(suggestionTargets)), + catchError((errors) => of(errors)) + ); + }), + catchError((errors) => of(errors)) + ); + })); + + /** + * Initialize the effect class variables. + * @param {Actions} actions$ + * @param {Store} store$ + * @param {TranslateService} translate + * @param {NotificationsService} notificationsService + * @param {SuggestionsService} suggestionsService + */ + constructor( + private actions$: Actions, + private store$: Store, + private translate: TranslateService, + private notificationsService: NotificationsService, + private suggestionsService: SuggestionsService + ) { + } +} diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts new file mode 100644 index 0000000000..f8bd53ec05 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts @@ -0,0 +1,100 @@ +import { SuggestionTargetActionTypes, SuggestionTargetsActions } from './suggestion-targets.actions'; +import { OpenaireSuggestionTarget } from '../../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; + +/** + * The interface representing the OpenAIRE suggestion targets state. + */ +export interface SuggestionTargetState { + targets: OpenaireSuggestionTarget[]; + processing: boolean; + loaded: boolean; + totalPages: number; + currentPage: number; + totalElements: number; + currentUserTargets: OpenaireSuggestionTarget[]; + currentUserTargetsVisited: boolean; +} + +/** + * Used for the OpenAIRE Suggestion Target state initialization. + */ +const SuggestionTargetInitialState: SuggestionTargetState = { + targets: [], + processing: false, + loaded: false, + totalPages: 0, + currentPage: 0, + totalElements: 0, + currentUserTargets: null, + currentUserTargetsVisited: false +}; + +/** + * The OpenAIRE Broker Topic Reducer + * + * @param state + * the current state initialized with SuggestionTargetInitialState + * @param action + * the action to perform on the state + * @return SuggestionTargetState + * the new state + */ +export function SuggestionTargetsReducer(state = SuggestionTargetInitialState, action: SuggestionTargetsActions): SuggestionTargetState { + switch (action.type) { + case SuggestionTargetActionTypes.RETRIEVE_TARGETS_BY_SOURCE: { + return Object.assign({}, state, { + targets: [], + processing: true + }); + } + + case SuggestionTargetActionTypes.ADD_TARGETS: { + return Object.assign({}, state, { + targets: state.targets.concat(action.payload.targets), + processing: false, + loaded: true, + totalPages: action.payload.totalPages, + currentPage: state.currentPage, + totalElements: action.payload.totalElements + }); + } + + case SuggestionTargetActionTypes.RETRIEVE_TARGETS_BY_SOURCE_ERROR: { + return Object.assign({}, state, { + targets: [], + processing: false, + loaded: true, + totalPages: 0, + currentPage: 0, + totalElements: 0, + }); + } + + case SuggestionTargetActionTypes.ADD_USER_SUGGESTIONS: { + return Object.assign({}, state, { + currentUserTargets: action.payload.suggestionTargets + }); + } + + case SuggestionTargetActionTypes.MARK_USER_SUGGESTIONS_AS_VISITED: { + return Object.assign({}, state, { + currentUserTargetsVisited: true + }); + } + + case SuggestionTargetActionTypes.CLEAR_TARGETS: { + return Object.assign({}, state, { + targets: [], + processing: false, + loaded: false, + totalPages: 0, + currentPage: 0, + totalElements: 0, + }); + } + + default: { + return state; + } + } +} diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts new file mode 100644 index 0000000000..2e05bce0a9 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts @@ -0,0 +1,164 @@ +import { Injectable } from '@angular/core'; + +import { select, Store } from '@ngrx/store'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { + getCurrentUserSuggestionTargetsSelector, + getCurrentUserSuggestionTargetsVisitedSelector, + getreciterSuggestionTargetCurrentPageSelector, + getreciterSuggestionTargetTotalsSelector, + isReciterSuggestionTargetLoadedSelector, + isreciterSuggestionTargetProcessingSelector, + reciterSuggestionTargetObjectSelector +} from '../selectors'; +import { OpenaireSuggestionTarget } from '../../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; +import { + ClearSuggestionTargetsAction, + MarkUserSuggestionsAsVisitedAction, + RefreshUserSuggestionsAction, + RetrieveTargetsBySourceAction +} from './suggestion-targets.actions'; +import { OpenaireState } from '../../openaire.reducer'; + +/** + * The service handling the Suggestion targets State. + */ +@Injectable() +export class SuggestionTargetsStateService { + + /** + * Initialize the service variables. + * @param {Store} store + */ + constructor(private store: Store) { } + + /** + * Returns the list of Reciter Suggestion Targets from the state. + * + * @return Observable + * The list of Reciter Suggestion Targets. + */ + public getReciterSuggestionTargets(): Observable { + return this.store.pipe(select(reciterSuggestionTargetObjectSelector())); + } + + /** + * Returns the information about the loading status of the Reciter Suggestion Targets (if it's running or not). + * + * @return Observable + * 'true' if the targets are loading, 'false' otherwise. + */ + public isReciterSuggestionTargetsLoading(): Observable { + return this.store.pipe( + select(isReciterSuggestionTargetLoadedSelector), + map((loaded: boolean) => !loaded) + ); + } + + /** + * Returns the information about the loading status of the Reciter Suggestion Targets (whether or not they were loaded). + * + * @return Observable + * 'true' if the targets are loaded, 'false' otherwise. + */ + public isReciterSuggestionTargetsLoaded(): Observable { + return this.store.pipe(select(isReciterSuggestionTargetLoadedSelector)); + } + + /** + * Returns the information about the processing status of the Reciter Suggestion Targets (if it's running or not). + * + * @return Observable + * 'true' if there are operations running on the targets (ex.: a REST call), 'false' otherwise. + */ + public isReciterSuggestionTargetsProcessing(): Observable { + return this.store.pipe(select(isreciterSuggestionTargetProcessingSelector)); + } + + /** + * Returns, from the state, the total available pages of the Reciter Suggestion Targets. + * + * @return Observable + * The number of the Reciter Suggestion Targets pages. + */ + public getReciterSuggestionTargetsTotalPages(): Observable { + return this.store.pipe(select(getreciterSuggestionTargetTotalsSelector)); + } + + /** + * Returns the current page of the Reciter Suggestion Targets, from the state. + * + * @return Observable + * The number of the current Reciter Suggestion Targets page. + */ + public getReciterSuggestionTargetsCurrentPage(): Observable { + return this.store.pipe(select(getreciterSuggestionTargetCurrentPageSelector)); + } + + /** + * Returns the total number of the Reciter Suggestion Targets. + * + * @return Observable + * The number of the Reciter Suggestion Targets. + */ + public getReciterSuggestionTargetsTotals(): Observable { + return this.store.pipe(select(getreciterSuggestionTargetTotalsSelector)); + } + + /** + * Dispatch a request to change the Reciter Suggestion Targets state, retrieving the targets from the server. + * + * @param source + * the source for which to retrieve suggestion targets + * @param elementsPerPage + * The number of the targets per page. + * @param currentPage + * The number of the current page. + */ + public dispatchRetrieveReciterSuggestionTargets(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. + * + * @return Observable + * The Reciter Suggestion Targets object. + */ + public getCurrentUserSuggestionTargets(): Observable { + return this.store.pipe(select(getCurrentUserSuggestionTargetsSelector)); + } + + /** + * Returns, from the state, whether or not the user has consulted their suggestion targets. + * + * @return Observable + * True if user already visited, false otherwise. + */ + public hasUserVisitedSuggestions(): Observable { + return this.store.pipe(select(getCurrentUserSuggestionTargetsVisitedSelector)); + } + + /** + * Dispatch a new MarkUserSuggestionsAsVisitedAction + */ + public dispatchMarkUserSuggestionsAsVisitedAction(): void { + this.store.dispatch(new MarkUserSuggestionsAsVisitedAction()); + } + + /** + * Dispatch an action to clear the Reciter Suggestion Targets state. + */ + public dispatchClearSuggestionTargetsAction(): void { + this.store.dispatch(new ClearSuggestionTargetsAction()); + } + + /** + * Dispatch an action to refresh the user suggestions. + */ + public dispatchRefreshUserSuggestionsAction(): void { + this.store.dispatch(new RefreshUserSuggestionsAction()); + } +} diff --git a/src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.html b/src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.html new file mode 100644 index 0000000000..577aa496b3 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.html @@ -0,0 +1,8 @@ + + +
+
+
+
+
+
diff --git a/src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.scss b/src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts b/src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts new file mode 100644 index 0000000000..094dfab017 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit } from '@angular/core'; +import { OpenaireSuggestionTarget } from '../../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; +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 { Observable } from 'rxjs'; + +@Component({ + selector: 'ds-suggestions-notification', + templateUrl: './suggestions-notification.component.html', + styleUrls: ['./suggestions-notification.component.scss'] +}) +export class SuggestionsNotificationComponent implements OnInit { + + labelPrefix = 'mydspace.'; + + /** + * The user suggestion targets. + */ + suggestionsRD$: Observable; + + constructor( + private translateService: TranslateService, + private reciterSuggestionStateService: SuggestionTargetsStateService, + private notificationsService: NotificationsService, + private suggestionsService: SuggestionsService + ) { } + + ngOnInit() { + this.suggestionsRD$ = this.reciterSuggestionStateService.getCurrentUserSuggestionTargets(); + } + + /** + * Interpolated params to build the notification suggestions notification. + * @param suggestionTarget + */ + public getNotificationSuggestionInterpolation(suggestionTarget: OpenaireSuggestionTarget): any { + return this.suggestionsService.getNotificationSuggestionInterpolation(suggestionTarget); + } + +} diff --git a/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.html b/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.html new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.html @@ -0,0 +1 @@ + diff --git a/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.scss b/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts b/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts new file mode 100644 index 0000000000..67678354ca --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts @@ -0,0 +1,79 @@ +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 { SuggestionsService } from '../suggestions.service'; + +describe('SuggestionsPopupComponent', () => { + let component: SuggestionsPopupComponent; + let fixture: ComponentFixture; + + const suggestionStateService = jasmine.createSpyObj('SuggestionTargetsStateService', { + hasUserVisitedSuggestions: jasmine.createSpy('hasUserVisitedSuggestions'), + getCurrentUserSuggestionTargets: jasmine.createSpy('getCurrentUserSuggestionTargets'), + dispatchMarkUserSuggestionsAsVisitedAction: jasmine.createSpy('dispatchMarkUserSuggestionsAsVisitedAction') + }); + + const mockNotificationInterpolation = { count: 12, source: 'source', suggestionId: 'id', displayName: 'displayName' }; + const suggestionService = jasmine.createSpyObj('SuggestionService', { + getNotificationSuggestionInterpolation: + jasmine.createSpy('getNotificationSuggestionInterpolation').and.returnValue(mockNotificationInterpolation) + }); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot()], + declarations: [ SuggestionsPopupComponent ], + providers: [ + { provide: SuggestionTargetsStateService, useValue: suggestionStateService }, + { provide: SuggestionsService, useValue: suggestionService }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + ], + schemas: [NO_ERRORS_SCHEMA] + + }) + .compileComponents(); + })); + + describe('should create', () => { + + beforeEach(() => { + fixture = TestBed.createComponent(SuggestionsPopupComponent); + component = fixture.componentInstance; + spyOn(component, 'initializePopup').and.returnValue(null); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + expect(component.initializePopup).toHaveBeenCalled(); + }); + + }); + + 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)); + + fixture = TestBed.createComponent(SuggestionsPopupComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should show a notification when new publication suggestions are available', () => { + expect((component as any).notificationsService.success).toHaveBeenCalled(); + expect(suggestionStateService.dispatchMarkUserSuggestionsAsVisitedAction).toHaveBeenCalled(); + }); + + }); +}); diff --git a/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts b/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts new file mode 100644 index 0000000000..6135cd99ea --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts @@ -0,0 +1,67 @@ +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 { OpenaireSuggestionTarget } from '../../../core/openaire/reciter-suggestions/models/openaire-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() { + 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: OpenaireSuggestionTarget) => this.showNotificationForNewSuggestions(suggestionTarget)); + this.reciterSuggestionStateService.dispatchMarkUserSuggestionsAsVisitedAction(); + notifier.next(); + notifier.complete(); + } + } + }); + } + + /** + * Show a notification to user for a new suggestions detected + * @param suggestionTarget + * @private + */ + private showNotificationForNewSuggestions(suggestionTarget: OpenaireSuggestionTarget): 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(); + } + } + +} diff --git a/src/app/openaire/reciter-suggestions/suggestions.service.ts b/src/app/openaire/reciter-suggestions/suggestions.service.ts new file mode 100644 index 0000000000..de743d95e3 --- /dev/null +++ b/src/app/openaire/reciter-suggestions/suggestions.service.ts @@ -0,0 +1,296 @@ +import { Injectable } from '@angular/core'; + +import { of, forkJoin, Observable } from 'rxjs'; +import { catchError, map, mergeMap, take } from 'rxjs/operators'; + +import { OpenaireSuggestionsDataService } from '../../core/openaire/reciter-suggestions/openaire-suggestions-data.service'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; +import { FindListOptions } from '../../core/data/request.models'; +import { RemoteData } from '../../core/data/remote-data'; +import { PaginatedList } from '../../core/data/paginated-list.model'; +import { OpenaireSuggestionTarget } from '../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; +import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; +import { AuthService } from '../../core/auth/auth.service'; +import { EPerson } from '../../core/eperson/models/eperson.model'; +import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model'; +import { + getAllSucceededRemoteDataPayload, + getFinishedRemoteData, + getFirstSucceededRemoteDataPayload, + getFirstSucceededRemoteListPayload +} from '../../core/shared/operators'; +import { OpenaireSuggestion } from '../../core/openaire/reciter-suggestions/models/openaire-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 { SuggestionConfig } from '../../../config/layout-config.interfaces'; +import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model'; + +export interface SuggestionBulkResult { + success: number; + fails: number; +} + +/** + * The service handling all Suggestion Target requests to the REST service. + */ +@Injectable() +export class SuggestionsService { + + /** + * Initialize the service variables. + * @param {AuthService} authService + * @param {ResearcherProfileService} researcherProfileService + * @param {OpenaireSuggestionsDataService} suggestionsDataService + */ + constructor( + private authService: AuthService, + private researcherProfileService: ResearcherProfileService, + private suggestionsDataService: OpenaireSuggestionsDataService, + private translateService: TranslateService + ) { + } + + /** + * Return the list of Suggestion Target managing pagination and errors. + * + * @param source + * The source for which to retrieve targets + * @param elementsPerPage + * The number of the target per page + * @param currentPage + * The page number to retrieve + * @return Observable> + * The list of Suggestion Targets. + */ + public getTargets(source, elementsPerPage, currentPage): Observable> { + const sortOptions = new SortOptions('display', SortDirection.ASC); + + const findListOptions: FindListOptions = { + elementsPerPage: elementsPerPage, + currentPage: currentPage, + sort: sortOptions + }; + + return this.suggestionsDataService.getTargets(source, findListOptions).pipe( + getFinishedRemoteData(), + take(1), + map((rd: RemoteData>) => { + if (rd.hasSucceeded) { + return rd.payload; + } else { + throw new Error('Can\'t retrieve Suggestion Target from the Search Target REST service'); + } + }) + ); + } + + /** + * Return the list of review suggestions Target managing pagination and errors. + * + * @param targetId + * The target id for which to find suggestions. + * @param elementsPerPage + * The number of the target per page + * @param currentPage + * The page number to retrieve + * @param sortOptions + * The sort options + * @return Observable>> + * The list of Suggestion. + */ + public getSuggestions(targetId: string, elementsPerPage, currentPage, sortOptions: SortOptions): Observable> { + const [source, target] = targetId.split(':'); + + const findListOptions: FindListOptions = { + elementsPerPage: elementsPerPage, + currentPage: currentPage, + sort: sortOptions + }; + + return this.suggestionsDataService.getSuggestionsByTargetAndSource(target, source, findListOptions).pipe( + getAllSucceededRemoteDataPayload() + ); + } + + /** + * Clear suggestions requests from cache + */ + public clearSuggestionRequests() { + this.suggestionsDataService.clearSuggestionRequests(); + } + + /** + * Used to delete Suggestion + * @suggestionId + */ + public deleteReviewedSuggestion(suggestionId: string): Observable> { + return this.suggestionsDataService.deleteSuggestion(suggestionId).pipe( + map((response: RemoteData) => { + if (response.isSuccess) { + return response; + } else { + throw new Error('Can\'t delete Suggestion from the Search Target REST service'); + } + }), + take(1) + ); + } + + /** + * Retrieve suggestion targets for the given user + * + * @param user + * The EPerson object for which to retrieve suggestion targets + */ + public retrieveCurrentUserSuggestions(user: EPerson): Observable { + return this.researcherProfileService.findById(user.uuid).pipe( + mergeMap((profile: ResearcherProfile) => { + if (isNotEmpty(profile)) { + return this.researcherProfileService.findRelatedItemId(profile).pipe( + mergeMap((itemId: string) => { + return this.suggestionsDataService.getTargetsByUser(itemId).pipe( + getFirstSucceededRemoteListPayload() + ); + }) + ); + } else { + return of([]); + } + }), + take(1) + ); + } + + /** + * Perform the approve and import operation over a single suggestion + * @param suggestion target suggestion + * @param collectionId the collectionId + * @param workspaceitemService injected dependency + * @private + */ + public approveAndImport(workspaceitemService: WorkspaceitemDataService, + suggestion: OpenaireSuggestion, + collectionId: string): Observable { + + const resolvedCollectionId = this.resolveCollectionId(suggestion, collectionId); + return workspaceitemService.importExternalSourceEntry(suggestion.externalSourceUri, resolvedCollectionId) + .pipe( + getFirstSucceededRemoteDataPayload(), + catchError((error) => of(null)) + ); + } + + /** + * Perform the delete operation over a single suggestion. + * @param suggestionId + */ + public notMine(suggestionId): Observable> { + return this.deleteReviewedSuggestion(suggestionId).pipe( + catchError((error) => of(null)) + ); + } + + /** + * Perform a bulk approve and import operation. + * @param workspaceitemService injected dependency + * @param suggestions the array containing the suggestions + * @param collectionId the collectionId + */ + public approveAndImportMultiple(workspaceitemService: WorkspaceitemDataService, + suggestions: OpenaireSuggestion[], + collectionId: string): Observable { + + return forkJoin(suggestions.map((suggestion: OpenaireSuggestion) => + this.approveAndImport(workspaceitemService, suggestion, collectionId))) + .pipe(map((results: WorkspaceItem[]) => { + return { + success: results.filter((result) => result != null).length, + fails: results.filter((result) => result == null).length + }; + }), take(1)); + } + + /** + * Perform a bulk notMine operation. + * @param suggestions the array containing the suggestions + */ + public notMineMultiple(suggestions: OpenaireSuggestion[]): Observable { + return forkJoin(suggestions.map((suggestion: OpenaireSuggestion) => this.notMine(suggestion.id))) + .pipe(map((results: RemoteData[]) => { + return { + success: results.filter((result) => result != null).length, + fails: results.filter((result) => result == null).length + }; + }), take(1)); + } + + /** + * Get the researcher uuid (for navigation purpose) from a target instance. + * TODO Find a better way + * @param target + * @return the researchUuid + */ + public getTargetUuid(target: OpenaireSuggestionTarget): string { + const tokens = target.id.split(':'); + return tokens.length === 2 ? tokens[1] : null; + } + + /** + * Interpolated params to build the notification suggestions notification. + * @param suggestionTarget + */ + public getNotificationSuggestionInterpolation(suggestionTarget: OpenaireSuggestionTarget): any { + return { + count: suggestionTarget.total, + source: this.translateService.instant(this.translateSuggestionSource(suggestionTarget.source)), + type: this.translateService.instant(this.translateSuggestionType(suggestionTarget.source)), + suggestionId: suggestionTarget.id, + displayName: suggestionTarget.display + }; + } + + public translateSuggestionType(source: string): string { + return 'reciter.suggestion.type.' + source; + } + + public translateSuggestionSource(source: string): string { + return 'reciter.suggestion.source.' + source; + } + + /** + * If the provided collectionId ha no value, tries to resolve it by suggestion source. + * @param suggestion + * @param collectionId + */ + public resolveCollectionId(suggestion: OpenaireSuggestion, collectionId): string { + if (hasValue(collectionId)) { + return collectionId; + } + return environment.suggestion + .find((suggestionConf: SuggestionConfig) => suggestionConf.source === suggestion.source) + .collectionId; + } + + /** + * Return true if all the suggestion are configured with the same fixed collection + * in the configuration. + * @param suggestions + */ + public isCollectionFixed(suggestions: OpenaireSuggestion[]): boolean { + return this.getFixedCollectionIds(suggestions).length === 1; + } + + private getFixedCollectionIds(suggestions: OpenaireSuggestion[]): string[] { + const collectionIds = {}; + suggestions.forEach((suggestion: OpenaireSuggestion) => { + const conf = environment.suggestion.find((suggestionConf: SuggestionConfig) => suggestionConf.source === suggestion.source); + if (hasValue(conf)) { + collectionIds[conf.collectionId] = true; + } + }); + return Object.keys(collectionIds); + } +} diff --git a/src/app/shared/mocks/openaire.mock.ts b/src/app/shared/mocks/openaire.mock.ts new file mode 100644 index 0000000000..95ea871727 --- /dev/null +++ b/src/app/shared/mocks/openaire.mock.ts @@ -0,0 +1,1797 @@ +import { of as observableOf } from 'rxjs'; +import { ResourceType } from '../../core/shared/resource-type'; +import { OpenaireBrokerTopicObject } from '../../core/openaire/broker/models/openaire-broker-topic.model'; +import { OpenaireBrokerEventObject } from '../../core/openaire/broker/models/openaire-broker-event.model'; +import { OpenaireBrokerTopicRestService } from '../../core/openaire/broker/topics/openaire-broker-topic-rest.service'; +import { OpenaireBrokerEventRestService } from '../../core/openaire/broker/events/openaire-broker-event-rest.service'; +import { DSpaceObject } from '../../core/shared/dspace-object.model'; +import { OpenaireStateService } from '../../openaire/openaire-state.service'; +import { Item } from '../../core/shared/item.model'; +import { + createNoContentRemoteDataObject$, + createSuccessfulRemoteDataObject, + createSuccessfulRemoteDataObject$ +} from '../remote-data.utils'; +import { SearchResult } from '../search/models/search-result.model'; +import { SuggestionsService } from '../../openaire/reciter-suggestions/suggestions.service'; + +// REST Mock --------------------------------------------------------------------- +// ------------------------------------------------------------------------------- + +// Items +// ------------------------------------------------------------------------------- + +const ItemMockPid1: Item = Object.assign( + new Item(), + { + handle: '10077/21486', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'ITEM4567-e89b-12d3-a456-426614174001', + uuid: 'ITEM4567-e89b-12d3-a456-426614174001', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Index nominum et rerum' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +const ItemMockPid2: Item = Object.assign( + new Item(), + { + handle: '10077/21486', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'ITEM4567-e89b-12d3-a456-426614174004', + uuid: 'ITEM4567-e89b-12d3-a456-426614174004', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'UNA NUOVA RILETTURA DELL\u0027 ARISTOTELE DI FRANZ BRENTANO ALLA LUCE DI ALCUNI INEDITI' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +const ItemMockPid3: Item = Object.assign( + new Item(), + { + handle: '10077/21486', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'ITEM4567-e89b-12d3-a456-426614174005', + uuid: 'ITEM4567-e89b-12d3-a456-426614174005', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Sustainable development' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +const ItemMockPid4: Item = Object.assign( + new Item(), + { + handle: '10077/21486', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'ITEM4567-e89b-12d3-a456-426614174006', + uuid: 'ITEM4567-e89b-12d3-a456-426614174006', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Reply to Critics' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +const ItemMockPid5: Item = Object.assign( + new Item(), + { + handle: '10077/21486', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'ITEM4567-e89b-12d3-a456-426614174007', + uuid: 'ITEM4567-e89b-12d3-a456-426614174007', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'PROGETTAZIONE, SINTESI E VALUTAZIONE DELL\u0027ATTIVITA\u0027 ANTIMICOBATTERICA ED ANTIFUNGINA DI NUOVI DERIVATI ETEROCICLICI' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +const ItemMockPid6: Item = Object.assign( + new Item(), + { + handle: '10077/21486', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'ITEM4567-e89b-12d3-a456-426614174008', + uuid: 'ITEM4567-e89b-12d3-a456-426614174008', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Donald Davidson' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +const ItemMockPid7: Item = Object.assign( + new Item(), + { + handle: '10077/21486', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'ITEM4567-e89b-12d3-a456-426614174009', + uuid: 'ITEM4567-e89b-12d3-a456-426614174009', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Missing abstract article' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +export const ItemMockPid8: Item = Object.assign( + new Item(), + { + handle: '10077/21486', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'ITEM4567-e89b-12d3-a456-426614174002', + uuid: 'ITEM4567-e89b-12d3-a456-426614174002', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Egypt, crossroad of translations and literary interweavings (3rd-6th centuries). A reconsideration of earlier Coptic literature' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +export const ItemMockPid9: Item = Object.assign( + new Item(), + { + handle: '10077/21486', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'ITEM4567-e89b-12d3-a456-426614174003', + uuid: 'ITEM4567-e89b-12d3-a456-426614174003', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Morocco, crossroad of translations and literary interweavings (3rd-6th centuries). A reconsideration of earlier Coptic literature' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +export const ItemMockPid10: Item = Object.assign( + new Item(), + { + handle: '10713/29832', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'P23e4567-e89b-12d3-a456-426614174002', + uuid: 'P23e4567-e89b-12d3-a456-426614174002', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Tracking Papyrus and Parchment Paths: An Archaeological Atlas of Coptic Literature.\nLiterary Texts in their Geographical Context: Production, Copying, Usage, Dissemination and Storage' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +export const OpenaireMockDspaceObject: SearchResult = Object.assign( + new SearchResult(), + { + handle: '10713/29832', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + _links:{ + self: { + href: 'https://rest.api/rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357' + } + }, + id: 'P23e4567-e89b-12d3-a456-426614174002', + uuid: 'P23e4567-e89b-12d3-a456-426614174002', + type: 'item', + metadata: { + 'dc.creator': [ + { + language: 'en_US', + value: 'Doe, Jane' + } + ], + 'dc.date.accessioned': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.available': [ + { + language: null, + value: '1650-06-26T19:58:25Z' + } + ], + 'dc.date.issued': [ + { + language: null, + value: '1650-06-26' + } + ], + 'dc.identifier.issn': [ + { + language: 'en_US', + value: '123456789' + } + ], + 'dc.identifier.uri': [ + { + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + } + ], + 'dc.description.abstract': [ + { + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + } + ], + 'dc.description.provenance': [ + { + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + } + ], + 'dc.language': [ + { + language: 'en_US', + value: 'en' + } + ], + 'dc.rights': [ + { + language: 'en_US', + value: '© Jane Doe' + } + ], + 'dc.subject': [ + { + language: 'en_US', + value: 'keyword1' + }, + { + language: 'en_US', + value: 'keyword2' + }, + { + language: 'en_US', + value: 'keyword3' + } + ], + 'dc.title': [ + { + language: 'en_US', + value: 'Tracking Papyrus and Parchment Paths: An Archaeological Atlas of Coptic Literature.\nLiterary Texts in their Geographical Context: Production, Copying, Usage, Dissemination and Storage' + } + ], + 'dc.type': [ + { + language: 'en_US', + value: 'text' + } + ] + } + } +); + +// Topics +// ------------------------------------------------------------------------------- + +export const openaireBrokerTopicObjectMorePid: OpenaireBrokerTopicObject = { + type: new ResourceType('nbtopic'), + id: 'ENRICH!MORE!PID', + name: 'ENRICH/MORE/PID', + lastEvent: '2020/10/09 10:11 UTC', + totalEvents: 33, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MORE!PID' + } + } +}; + +export const openaireBrokerTopicObjectMoreAbstract: OpenaireBrokerTopicObject = { + type: new ResourceType('nbtopic'), + id: 'ENRICH!MORE!ABSTRACT', + name: 'ENRICH/MORE/ABSTRACT', + lastEvent: '2020/09/08 21:14 UTC', + totalEvents: 5, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MORE!ABSTRACT' + } + } +}; + +export const openaireBrokerTopicObjectMissingPid: OpenaireBrokerTopicObject = { + type: new ResourceType('nbtopic'), + id: 'ENRICH!MISSING!PID', + name: 'ENRICH/MISSING/PID', + lastEvent: '2020/10/01 07:36 UTC', + totalEvents: 4, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MISSING!PID' + } + } +}; + +export const openaireBrokerTopicObjectMissingAbstract: OpenaireBrokerTopicObject = { + type: new ResourceType('nbtopic'), + id: 'ENRICH!MISSING!ABSTRACT', + name: 'ENRICH/MISSING/ABSTRACT', + lastEvent: '2020/10/08 16:14 UTC', + totalEvents: 71, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MISSING!ABSTRACT' + } + } +}; + +export const openaireBrokerTopicObjectMissingAcm: OpenaireBrokerTopicObject = { + type: new ResourceType('nbtopic'), + id: 'ENRICH!MISSING!SUBJECT!ACM', + name: 'ENRICH/MISSING/SUBJECT/ACM', + lastEvent: '2020/09/21 17:51 UTC', + totalEvents: 18, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MISSING!SUBJECT!ACM' + } + } +}; + +export const openaireBrokerTopicObjectMissingProject: OpenaireBrokerTopicObject = { + type: new ResourceType('nbtopic'), + id: 'ENRICH!MISSING!PROJECT', + name: 'ENRICH/MISSING/PROJECT', + lastEvent: '2020/09/17 10:28 UTC', + totalEvents: 6, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MISSING!PROJECT' + } + } +}; + +// Events +// ------------------------------------------------------------------------------- + +export const openaireBrokerEventObjectMissingPid: OpenaireBrokerEventObject = { + id: '123e4567-e89b-12d3-a456-426614174001', + uuid: '123e4567-e89b-12d3-a456-426614174001', + type: new ResourceType('nbevent'), + originalId: 'oai:www.openstarts.units.it:10077/21486', + title: 'Index nominum et rerum', + trust: 0.375, + eventDate: '2020/10/09 10:11 UTC', + status: 'PENDING', + message: { + type: 'doi', + value: '10.18848/1447-9494/cgp/v15i09/45934', + abstract: null, + openaireId: null, + acronym: null, + code: null, + funder: null, + fundingProgram: null, + jurisdiction: null, + title: null + }, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174001', + }, + target: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174001/target' + }, + related: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174001/related' + } + }, + target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid1)), + related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) +}; + +export const openaireBrokerEventObjectMissingPid2: OpenaireBrokerEventObject = { + id: '123e4567-e89b-12d3-a456-426614174004', + uuid: '123e4567-e89b-12d3-a456-426614174004', + type: new ResourceType('openaireBrokerEvent'), + originalId: 'oai:www.openstarts.units.it:10077/21486', + title: 'UNA NUOVA RILETTURA DELL\u0027 ARISTOTELE DI FRANZ BRENTANO ALLA LUCE DI ALCUNI INEDITI', + trust: 1.0, + eventDate: '2020/10/09 10:11 UTC', + status: 'PENDING', + message: { + type: 'urn', + value: 'http://thesis2.sba.units.it/store/handle/item/12238', + abstract: null, + openaireId: null, + acronym: null, + code: null, + funder: null, + fundingProgram: null, + jurisdiction: null, + title: null + }, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174004' + }, + target: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174004/target' + }, + related: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174004/related' + } + }, + target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid2)), + related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) +}; + +export const openaireBrokerEventObjectMissingPid3: OpenaireBrokerEventObject = { + id: '123e4567-e89b-12d3-a456-426614174005', + uuid: '123e4567-e89b-12d3-a456-426614174005', + type: new ResourceType('openaireBrokerEvent'), + originalId: 'oai:www.openstarts.units.it:10077/554', + title: 'Sustainable development', + trust: 0.375, + eventDate: '2020/10/09 10:11 UTC', + status: 'PENDING', + message: { + type: 'doi', + value: '10.4324/9780203408889', + abstract: null, + openaireId: null, + acronym: null, + code: null, + funder: null, + fundingProgram: null, + jurisdiction: null, + title: null + }, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174005' + }, + target: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174005/target' + }, + related: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174005/related' + } + }, + target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid3)), + related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) +}; + +export const openaireBrokerEventObjectMissingPid4: OpenaireBrokerEventObject = { + id: '123e4567-e89b-12d3-a456-426614174006', + uuid: '123e4567-e89b-12d3-a456-426614174006', + type: new ResourceType('openaireBrokerEvent'), + originalId: 'oai:www.openstarts.units.it:10077/10787', + title: 'Reply to Critics', + trust: 1.0, + eventDate: '2020/10/09 10:11 UTC', + status: 'PENDING', + message: { + type: 'doi', + value: '10.1080/13698230.2018.1430104', + abstract: null, + openaireId: null, + acronym: null, + code: null, + funder: null, + fundingProgram: null, + jurisdiction: null, + title: null + }, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174006' + }, + target: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174006/target' + }, + related: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174006/related' + } + }, + target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid4)), + related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) +}; + +export const openaireBrokerEventObjectMissingPid5: OpenaireBrokerEventObject = { + id: '123e4567-e89b-12d3-a456-426614174007', + uuid: '123e4567-e89b-12d3-a456-426614174007', + type: new ResourceType('openaireBrokerEvent'), + originalId: 'oai:www.openstarts.units.it:10077/11339', + title: 'PROGETTAZIONE, SINTESI E VALUTAZIONE DELL\u0027ATTIVITA\u0027 ANTIMICOBATTERICA ED ANTIFUNGINA DI NUOVI DERIVATI ETEROCICLICI', + trust: 0.375, + eventDate: '2020/10/09 10:11 UTC', + status: 'PENDING', + message: { + type: 'urn', + value: 'http://thesis2.sba.units.it/store/handle/item/12477', + abstract: null, + openaireId: null, + acronym: null, + code: null, + funder: null, + fundingProgram: null, + jurisdiction: null, + title: null + }, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174007' + }, + target: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174007/target' + }, + related: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174007/related' + } + }, + target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid5)), + related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) +}; + +export const openaireBrokerEventObjectMissingPid6: OpenaireBrokerEventObject = { + id: '123e4567-e89b-12d3-a456-426614174008', + uuid: '123e4567-e89b-12d3-a456-426614174008', + type: new ResourceType('openaireBrokerEvent'), + originalId: 'oai:www.openstarts.units.it:10077/29860', + title: 'Donald Davidson', + trust: 0.375, + eventDate: '2020/10/09 10:11 UTC', + status: 'PENDING', + message: { + type: 'doi', + value: '10.1111/j.1475-4975.2004.00098.x', + abstract: null, + openaireId: null, + acronym: null, + code: null, + funder: null, + fundingProgram: null, + jurisdiction: null, + title: null + }, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174008' + }, + target: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174008/target' + }, + related: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174008/related' + } + }, + target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid6)), + related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) +}; + +export const openaireBrokerEventObjectMissingAbstract: OpenaireBrokerEventObject = { + id: '123e4567-e89b-12d3-a456-426614174009', + uuid: '123e4567-e89b-12d3-a456-426614174009', + type: new ResourceType('openaireBrokerEvent'), + originalId: 'oai:www.openstarts.units.it:10077/21110', + title: 'Missing abstract article', + trust: 0.751, + eventDate: '2020/10/09 10:11 UTC', + status: 'PENDING', + message: { + type: null, + value: null, + abstract: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla scelerisque vestibulum tellus sed lacinia. Aenean vitae sapien a quam congue ultrices. Sed vehicula sollicitudin ligula, vitae lacinia velit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla scelerisque vestibulum tellus sed lacinia. Aenean vitae sapien a quam congue ultrices. Sed vehicula sollicitudin ligula, vitae lacinia velit.', + openaireId: null, + acronym: null, + code: null, + funder: null, + fundingProgram: null, + jurisdiction: null, + title: null + }, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174009' + }, + target: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174009/target' + }, + related: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174009/related' + } + }, + target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid7)), + related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) +}; + +export const openaireBrokerEventObjectMissingProjectFound: OpenaireBrokerEventObject = { + id: '123e4567-e89b-12d3-a456-426614174002', + uuid: '123e4567-e89b-12d3-a456-426614174002', + type: new ResourceType('openaireBrokerEvent'), + originalId: 'oai:www.openstarts.units.it:10077/21838', + title: 'Egypt, crossroad of translations and literary interweavings (3rd-6th centuries). A reconsideration of earlier Coptic literature', + trust: 1.0, + eventDate: '2020/10/09 10:11 UTC', + status: 'PENDING', + message: { + type: null, + value: null, + abstract: null, + openaireId: null, + acronym: 'PAThs', + code: '687567', + funder: 'EC', + fundingProgram: 'H2020', + jurisdiction: 'EU', + title: 'Tracking Papyrus and Parchment Paths: An Archaeological Atlas of Coptic Literature.\nLiterary Texts in their Geographical Context: Production, Copying, Usage, Dissemination and Storage' + }, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174002' + }, + target: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174002/target' + }, + related: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174002/related' + } + }, + target: createSuccessfulRemoteDataObject$(ItemMockPid8), + related: createSuccessfulRemoteDataObject$(ItemMockPid10) +}; + +export const openaireBrokerEventObjectMissingProjectNotFound: OpenaireBrokerEventObject = { + id: '123e4567-e89b-12d3-a456-426614174003', + uuid: '123e4567-e89b-12d3-a456-426614174003', + type: new ResourceType('openaireBrokerEvent'), + originalId: 'oai:www.openstarts.units.it:10077/21838', + title: 'Morocco, crossroad of translations and literary interweavings (3rd-6th centuries). A reconsideration of earlier Coptic literature', + trust: 1.0, + eventDate: '2020/10/09 10:11 UTC', + status: 'PENDING', + message: { + type: null, + value: null, + abstract: null, + openaireId: null, + acronym: 'PAThs', + code: '687567B', + funder: 'EC', + fundingProgram: 'H2021', + jurisdiction: 'EU', + title: 'Tracking Unknown Papyrus and Parchment Paths: An Archaeological Atlas of Coptic Literature.\nLiterary Texts in their Geographical Context: Production, Copying, Usage, Dissemination and Storage' + }, + _links: { + self: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174003' + }, + target: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174003/target' + }, + related: { + href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174003/related' + } + }, + target: createSuccessfulRemoteDataObject$(ItemMockPid9), + related: createNoContentRemoteDataObject$() +}; + +// Classes +// ------------------------------------------------------------------------------- + +/** + * Mock for [[OpenaireStateService]] + */ +export function getMockOpenaireStateService(): any { + return jasmine.createSpyObj('OpenaireStateService', { + getOpenaireBrokerTopics: jasmine.createSpy('getOpenaireBrokerTopics'), + isOpenaireBrokerTopicsLoading: jasmine.createSpy('isOpenaireBrokerTopicsLoading'), + isOpenaireBrokerTopicsLoaded: jasmine.createSpy('isOpenaireBrokerTopicsLoaded'), + isOpenaireBrokerTopicsProcessing: jasmine.createSpy('isOpenaireBrokerTopicsProcessing'), + getOpenaireBrokerTopicsTotalPages: jasmine.createSpy('getOpenaireBrokerTopicsTotalPages'), + getOpenaireBrokerTopicsCurrentPage: jasmine.createSpy('getOpenaireBrokerTopicsCurrentPage'), + getOpenaireBrokerTopicsTotals: jasmine.createSpy('getOpenaireBrokerTopicsTotals'), + dispatchRetrieveOpenaireBrokerTopics: jasmine.createSpy('dispatchRetrieveOpenaireBrokerTopics'), + dispatchMarkUserSuggestionsAsVisitedAction: jasmine.createSpy('dispatchMarkUserSuggestionsAsVisitedAction') + }); +} + +/** + * Mock for [[OpenaireBrokerTopicRestService]] + */ +export function getMockOpenaireBrokerTopicRestService(): OpenaireBrokerTopicRestService { + return jasmine.createSpyObj('OpenaireBrokerTopicRestService', { + getTopics: jasmine.createSpy('getTopics'), + getTopic: jasmine.createSpy('getTopic'), + }); +} + +/** + * Mock for [[OpenaireBrokerEventRestService]] + */ +export function getMockOpenaireBrokerEventRestService(): OpenaireBrokerEventRestService { + return jasmine.createSpyObj('OpenaireBrokerEventRestService', { + getEventsByTopic: jasmine.createSpy('getEventsByTopic'), + getEvent: jasmine.createSpy('getEvent'), + patchEvent: jasmine.createSpy('patchEvent'), + boundProject: jasmine.createSpy('boundProject'), + removeProject: jasmine.createSpy('removeProject'), + clearFindByTopicRequests: jasmine.createSpy('.clearFindByTopicRequests') + }); +} + +/** + * Mock for [[OpenaireBrokerEventRestService]] + */ +export function getMockSuggestionsService(): any { + return jasmine.createSpyObj('SuggestionsService', { + getTargets: jasmine.createSpy('getTargets'), + getSuggestions: jasmine.createSpy('getSuggestions'), + clearSuggestionRequests: jasmine.createSpy('clearSuggestionRequests'), + deleteReviewedSuggestion: jasmine.createSpy('deleteReviewedSuggestion'), + retrieveCurrentUserSuggestions: jasmine.createSpy('retrieveCurrentUserSuggestions'), + getTargetUuid: jasmine.createSpy('getTargetUuid'), + }); +} diff --git a/src/app/shared/mocks/reciter-suggestion-targets.mock.ts b/src/app/shared/mocks/reciter-suggestion-targets.mock.ts new file mode 100644 index 0000000000..42e293e603 --- /dev/null +++ b/src/app/shared/mocks/reciter-suggestion-targets.mock.ts @@ -0,0 +1,42 @@ +import { ResourceType } from '../../core/shared/resource-type'; +import { OpenaireSuggestionTarget } from '../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; + +// REST Mock --------------------------------------------------------------------- +// ------------------------------------------------------------------------------- +export const mockSuggestionTargetsObjectOne: OpenaireSuggestionTarget = { + type: new ResourceType('suggestiontarget'), + id: 'reciter:gf3d657-9d6d-4a87-b905-fef0f8cae26', + display: 'Bollini, Andrea', + source: 'reciter', + total: 31, + _links: { + target: { + href: 'https://rest.api/rest/api/core/items/gf3d657-9d6d-4a87-b905-fef0f8cae26' + }, + suggestions: { + href: 'https://rest.api/rest/api/integration/suggestions/search/findByTargetAndSource?target=gf3d657-9d6d-4a87-b905-fef0f8cae26c&source=reciter' + }, + self: { + href: 'https://rest.api/rest/api/integration/suggestiontargets/reciter:gf3d657-9d6d-4a87-b905-fef0f8cae26' + } + } +}; + +export const mockSuggestionTargetsObjectTwo: OpenaireSuggestionTarget = { + type: new ResourceType('suggestiontarget'), + id: 'reciter:nhy567-9d6d-ty67-b905-fef0f8cae26', + display: 'Digilio, Andrea', + source: 'reciter', + total: 12, + _links: { + target: { + href: 'https://rest.api/rest/api/core/items/nhy567-9d6d-ty67-b905-fef0f8cae26' + }, + suggestions: { + href: 'https://rest.api/rest/api/integration/suggestions/search/findByTargetAndSource?target=nhy567-9d6d-ty67-b905-fef0f8cae26&source=reciter' + }, + self: { + href: 'https://rest.api/rest/api/integration/suggestiontargets/reciter:nhy567-9d6d-ty67-b905-fef0f8cae26' + } + } +}; diff --git a/src/app/shared/mocks/reciter-suggestion.mock.ts b/src/app/shared/mocks/reciter-suggestion.mock.ts new file mode 100644 index 0000000000..2ddb49868f --- /dev/null +++ b/src/app/shared/mocks/reciter-suggestion.mock.ts @@ -0,0 +1,210 @@ + +// REST Mock --------------------------------------------------------------------- +// ------------------------------------------------------------------------------- + +import { OpenaireSuggestion } from '../../core/openaire/reciter-suggestions/models/openaire-suggestion.model'; +import { SUGGESTION } from '../../core/openaire/reciter-suggestions/models/openaire-suggestion-objects.resource-type'; + +export const mockSuggestionPublicationOne: OpenaireSuggestion = { + id: '24694772', + display: 'publication one', + source: 'reciter', + externalSourceUri: 'https://dspace7.4science.cloud/server/api/integration/reciterSourcesEntry/pubmed/entryValues/24694772', + score: '48', + evidences: { + acceptedRejectedEvidence: { + score: '2.7', + notes: 'some notes, eventually empty or null' + }, + authorNameEvidence: { + score: '0', + notes: 'some notes, eventually empty or null' + }, + journalCategoryEvidence: { + score: '6', + notes: 'some notes, eventually empty or null' + }, + affiliationEvidence: { + score: 'xxx', + notes: 'some notes, eventually empty or null' + }, + relationshipEvidence: { + score: '9', + notes: 'some notes, eventually empty or null' + }, + educationYearEvidence: { + score: '3.6', + notes: 'some notes, eventually empty or null' + }, + personTypeEvidence: { + score: '4', + notes: 'some notes, eventually empty or null' + }, + articleCountEvidence: { + score: '6.7', + notes: 'some notes, eventually empty or null' + }, + averageClusteringEvidence: { + score: '7', + notes: 'some notes, eventually empty or null' + } + }, + metadata: { + 'dc.identifier.uri': [ + { + value: 'https://publication/0000-0003-3681-2038', + language: null, + authority: null, + confidence: -1, + place: -1 + } as any + ], + 'dc.title': [ + { + value: 'publication one', + language: null, + authority: null, + confidence: -1 + } as any + ], + 'dc.date.issued': [ + { + value: '2010-11-03', + language: null, + authority: null, + confidence: -1 + } as any + ], + 'dspace.entity.type': [ + { + uuid: '95f21fe6-ce38-43d6-96d4-60ae66385a06', + language: null, + value: 'OrgUnit', + place: 0, + authority: null, + confidence: -1 + } as any + ], + 'dc.description': [ + { + uuid: '95f21fe6-ce38-43d6-96d4-60ae66385a06', + language: null, + value: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).", + place: 0, + authority: null, + confidence: -1 + } as any + ] + }, + type: SUGGESTION, + _links: { + target: { + href: 'https://dspace7.4science.cloud/server/api/core/items/gf3d657-9d6d-4a87-b905-fef0f8cae26' + }, + self: { + href: 'https://dspace7.4science.cloud/server/api/integration/suggestions/reciter:gf3d657-9d6d-4a87-b905-fef0f8cae26c:24694772' + } + } +}; + +export const mockSuggestionPublicationTwo: OpenaireSuggestion = { + id: '24694772', + display: 'publication two', + source: 'reciter', + externalSourceUri: 'https://dspace7.4science.cloud/server/api/integration/reciterSourcesEntry/pubmed/entryValues/24694772', + score: '48', + evidences: { + acceptedRejectedEvidence: { + score: '2.7', + notes: 'some notes, eventually empty or null' + }, + authorNameEvidence: { + score: '0', + notes: 'some notes, eventually empty or null' + }, + journalCategoryEvidence: { + score: '6', + notes: 'some notes, eventually empty or null' + }, + affiliationEvidence: { + score: 'xxx', + notes: 'some notes, eventually empty or null' + }, + relationshipEvidence: { + score: '9', + notes: 'some notes, eventually empty or null' + }, + educationYearEvidence: { + score: '3.6', + notes: 'some notes, eventually empty or null' + }, + personTypeEvidence: { + score: '4', + notes: 'some notes, eventually empty or null' + }, + articleCountEvidence: { + score: '6.7', + notes: 'some notes, eventually empty or null' + }, + averageClusteringEvidence: { + score: '7', + notes: 'some notes, eventually empty or null' + } + }, + metadata: { + 'dc.identifier.uri': [ + { + value: 'https://publication/0000-0003-3681-2038', + language: null, + authority: null, + confidence: -1, + place: -1 + } as any + ], + 'dc.title': [ + { + value: 'publication one', + language: null, + authority: null, + confidence: -1 + } as any + ], + 'dc.date.issued': [ + { + value: '2010-11-03', + language: null, + authority: null, + confidence: -1 + } as any + ], + 'dspace.entity.type': [ + { + uuid: '95f21fe6-ce38-43d6-96d4-60ae66385a06', + language: null, + value: 'OrgUnit', + place: 0, + authority: null, + confidence: -1 + } as any + ], + 'dc.description': [ + { + uuid: '95f21fe6-ce38-43d6-96d4-60ae66385a06', + language: null, + value: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).", + place: 0, + authority: null, + confidence: -1 + } as any + ] + }, + type: SUGGESTION, + _links: { + target: { + href: 'https://dspace7.4science.cloud/server/api/core/items/gf3d657-9d6d-4a87-b905-fef0f8cae26' + }, + self: { + href: 'https://dspace7.4science.cloud/server/api/integration/suggestions/reciter:gf3d657-9d6d-4a87-b905-fef0f8cae26c:24694772' + } + } +}; diff --git a/src/app/suggestions-page/suggestions-page-routing-paths.ts b/src/app/suggestions-page/suggestions-page-routing-paths.ts new file mode 100644 index 0000000000..0f3aa782d2 --- /dev/null +++ b/src/app/suggestions-page/suggestions-page-routing-paths.ts @@ -0,0 +1,11 @@ +import { URLCombiner } from '../core/url-combiner/url-combiner'; + +export const SUGGESTION_MODULE_PATH = 'suggestions'; + +export function getSuggestionModuleRoute() { + return `/${SUGGESTION_MODULE_PATH}`; +} + +export function getSuggestionPageRoute(SuggestionId: string) { + return new URLCombiner(getSuggestionModuleRoute(), SuggestionId).toString(); +} diff --git a/src/app/suggestions-page/suggestions-page-routing.module.ts b/src/app/suggestions-page/suggestions-page-routing.module.ts new file mode 100644 index 0000000000..20ed658707 --- /dev/null +++ b/src/app/suggestions-page/suggestions-page-routing.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { SuggestionsPageResolver } from './suggestions-page.resolver'; +import { SuggestionsPageComponent } from './suggestions-page.component'; +import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; +import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { + path: ':targetId', + resolve: { + suggestionTargets: SuggestionsPageResolver, + breadcrumb: I18nBreadcrumbResolver + }, + data: { + title: 'admin.notifications.recitersuggestion.page.title', + breadcrumbKey: 'admin.notifications.recitersuggestion', + showBreadcrumbsFluid: false + }, + canActivate: [AuthenticatedGuard], + runGuardsAndResolvers: 'always', + component: SuggestionsPageComponent, + }, + ]) + ], + providers: [ + SuggestionsPageResolver + ] +}) +export class SuggestionsPageRoutingModule { + +} diff --git a/src/app/suggestions-page/suggestions-page.component.html b/src/app/suggestions-page/suggestions-page.component.html new file mode 100644 index 0000000000..fb5f08b6a4 --- /dev/null +++ b/src/app/suggestions-page/suggestions-page.component.html @@ -0,0 +1,48 @@ +
+
+
+ +
+ +

+ {{ translateSuggestionType() | translate }} + {{'reciter.suggestion.suggestionFor' | translate}} + {{researcherName}} + {{'reciter.suggestion.from.source' | translate}} {{ translateSuggestionSource() | translate }} +

+ +
+ + ({{ getSelectedSuggestionsCount() }}) + + +
+ + +
    +
  • + +
  • +
+
+
+
+
+
+
diff --git a/src/app/suggestions-page/suggestions-page.component.scss b/src/app/suggestions-page/suggestions-page.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/suggestions-page/suggestions-page.component.spec.ts b/src/app/suggestions-page/suggestions-page.component.spec.ts new file mode 100644 index 0000000000..ad518930d9 --- /dev/null +++ b/src/app/suggestions-page/suggestions-page.component.spec.ts @@ -0,0 +1,107 @@ +import { CommonModule } from '@angular/common'; +import { BrowserModule } from '@angular/platform-browser'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { of as observableOf } from 'rxjs'; + +import { SuggestionsPageComponent } from './suggestions-page.component'; +import { SuggestionListElementComponent } from '../openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; +import { SuggestionsService } from '../openaire/reciter-suggestions/suggestions.service'; +import { getMockOpenaireStateService, getMockSuggestionsService } from '../shared/mocks/openaire.mock'; +import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model'; +import { OpenaireSuggestion } from '../core/openaire/reciter-suggestions/models/openaire-suggestion.model'; +import { mockSuggestionPublicationOne, mockSuggestionPublicationTwo } from '../shared/mocks/reciter-suggestion.mock'; +import { SuggestionEvidencesComponent } from '../openaire/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component'; +import { ObjectKeysPipe } from '../shared/utils/object-keys-pipe'; +import { VarDirective } from '../shared/utils/var.directive'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RouterStub } from '../shared/testing/router.stub'; +import { mockSuggestionTargetsObjectOne } from '../shared/mocks/reciter-suggestion-targets.mock'; +import { AuthService } from '../core/auth/auth.service'; +import { NotificationsService } from '../shared/notifications/notifications.service'; +import { NotificationsServiceStub } from '../shared/testing/notifications-service.stub'; +import { getMockTranslateService } from '../shared/mocks/translate.service.mock'; +import { SuggestionTargetsStateService } from '../openaire/reciter-suggestions/suggestion-targets/suggestion-targets.state.service'; +import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; +import { createSuccessfulRemoteDataObject } from '../shared/remote-data.utils'; +import { PageInfo } from '../core/shared/page-info.model'; +import { TestScheduler } from 'rxjs/testing'; +import { getTestScheduler } from 'jasmine-marbles'; +import { PaginationServiceStub } from '../shared/testing/pagination-service.stub'; +import { PaginationService } from '../core/pagination/pagination.service'; + +describe('SuggestionPageComponent', () => { + let component: SuggestionsPageComponent; + let fixture: ComponentFixture; + let scheduler: TestScheduler; + const mockSuggestionsService = getMockSuggestionsService(); + const mockSuggestionsTargetStateService = getMockOpenaireStateService(); + const suggestionTargetsList: PaginatedList = buildPaginatedList(new PageInfo(), [mockSuggestionPublicationOne, mockSuggestionPublicationTwo]); + const router = new RouterStub(); + const routeStub = { + data: observableOf({ + suggestionTargets: createSuccessfulRemoteDataObject(mockSuggestionTargetsObjectOne) + }), + queryParams: observableOf({}) + }; + const workspaceitemServiceMock = jasmine.createSpyObj('WorkspaceitemDataService', { + importExternalSourceEntry: jasmine.createSpy('importExternalSourceEntry') + }); + + const authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true), + setRedirectUrl: {} + }); + const paginationService = new PaginationServiceStub(); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + BrowserModule, + CommonModule, + TranslateModule.forRoot() + ], + declarations: [ + SuggestionEvidencesComponent, + SuggestionListElementComponent, + SuggestionsPageComponent, + ObjectKeysPipe, + VarDirective + ], + providers: [ + { provide: AuthService, useValue: authService }, + { provide: ActivatedRoute, useValue: routeStub }, + { provide: WorkspaceitemDataService, useValue: workspaceitemServiceMock }, + { provide: Router, useValue: router }, + { provide: SuggestionsService, useValue: mockSuggestionsService }, + { provide: SuggestionTargetsStateService, useValue: mockSuggestionsTargetStateService }, + { provide: NotificationsService, useValue: new NotificationsServiceStub() }, + { provide: TranslateService, useValue: getMockTranslateService() }, + { provide: PaginationService, useValue: paginationService }, + SuggestionsPageComponent + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents().then(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SuggestionsPageComponent); + component = fixture.componentInstance; + scheduler = getTestScheduler(); + }); + + it('should create', () => { + spyOn(component, 'updatePage').and.stub(); + + scheduler.schedule(() => fixture.detectChanges()); + scheduler.flush(); + + expect(component).toBeTruthy(); + expect(component.suggestionId).toBe(mockSuggestionTargetsObjectOne.id); + expect(component.researcherName).toBe(mockSuggestionTargetsObjectOne.display); + expect(component.updatePage).toHaveBeenCalled(); + }); +}); diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts new file mode 100644 index 0000000000..911f6167de --- /dev/null +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -0,0 +1,285 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, Data, Router } from '@angular/router'; + +import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; +import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators'; +import { TranslateService } from '@ngx-translate/core'; + +import { SortDirection, SortOptions, } from '../core/cache/models/sort-options.model'; +import { PaginatedList } from '../core/data/paginated-list.model'; +import { RemoteData } from '../core/data/remote-data'; +import { getFirstSucceededRemoteDataPayload, redirectOn4xx } from '../core/shared/operators'; +import { SuggestionBulkResult, SuggestionsService } from '../openaire/reciter-suggestions/suggestions.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { OpenaireSuggestion } from '../core/openaire/reciter-suggestions/models/openaire-suggestion.model'; +import { OpenaireSuggestionTarget } from '../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; +import { AuthService } from '../core/auth/auth.service'; +import { SuggestionApproveAndImport } from '../openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; +import { NotificationsService } from '../shared/notifications/notifications.service'; +import { SuggestionTargetsStateService } from '../openaire/reciter-suggestions/suggestion-targets/suggestion-targets.state.service'; +import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; +import { PaginationService } from '../core/pagination/pagination.service'; +import { FindListOptions } from '../core/data/request.models'; +import { WorkspaceItem } from '../core/submission/models/workspaceitem.model'; + +@Component({ + selector: 'ds-suggestion-page', + templateUrl: './suggestions-page.component.html', + styleUrls: ['./suggestions-page.component.scss'], +}) +export class SuggestionsPageComponent implements OnInit { + + /** + * The pagination configuration + */ + paginationOptions: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), { + id: 'sp', + pageSizeOptions: [5, 10, 20, 40, 60] + }); + + /** + * The sorting configuration + */ + paginationSortConfig: SortOptions = new SortOptions('trust', SortDirection.DESC); + + /** + * The FindListOptions object + */ + defaultConfig: FindListOptions = Object.assign(new FindListOptions(), {sort: this.paginationSortConfig}); + + /** + * A boolean representing if results are loading + */ + public processing$ = new BehaviorSubject(false); + + /** + * A list of remote data objects of suggestions + */ + suggestionsRD$: BehaviorSubject> = new BehaviorSubject>({} as any); + + targetRD$: Observable>; + targetId$: Observable; + + suggestionTarget: OpenaireSuggestionTarget; + suggestionId: any; + suggestionSource: any; + researcherName: any; + researcherUuid: any; + + selectedSuggestions: { [id: string]: OpenaireSuggestion } = {}; + isBulkOperationPending = false; + + constructor( + private authService: AuthService, + private notificationService: NotificationsService, + private paginationService: PaginationService, + private route: ActivatedRoute, + private router: Router, + private suggestionService: SuggestionsService, + private suggestionTargetsStateService: SuggestionTargetsStateService, + private translateService: TranslateService, + private workspaceItemService: WorkspaceitemDataService + ) { + } + + ngOnInit(): void { + this.targetRD$ = this.route.data.pipe( + map((data: Data) => data.suggestionTargets as RemoteData), + redirectOn4xx(this.router, this.authService) + ); + + this.targetId$ = this.targetRD$.pipe( + getFirstSucceededRemoteDataPayload(), + map((target: OpenaireSuggestionTarget) => target.id) + ); + this.targetRD$.pipe( + getFirstSucceededRemoteDataPayload() + ).subscribe((suggestionTarget: OpenaireSuggestionTarget) => { + this.suggestionTarget = suggestionTarget; + this.suggestionId = suggestionTarget.id; + this.researcherName = suggestionTarget.display; + this.suggestionSource = suggestionTarget.source; + this.researcherUuid = this.suggestionService.getTargetUuid(suggestionTarget); + this.updatePage(); + }); + + this.suggestionTargetsStateService.dispatchMarkUserSuggestionsAsVisitedAction(); + } + + /** + * Called when one of the pagination settings is changed + */ + onPaginationChange() { + this.updatePage(); + } + + /** + * Update the list of suggestions + */ + updatePage() { + this.processing$.next(true); + const pageConfig$: Observable = this.paginationService.getFindListOptions( + this.paginationOptions.id, + this.defaultConfig, + ).pipe( + distinctUntilChanged() + ); + combineLatest([this.targetId$, pageConfig$]).pipe( + switchMap(([targetId, config]: [string, FindListOptions]) => { + return this.suggestionService.getSuggestions( + targetId, + config.elementsPerPage, + config.currentPage, + config.sort + ); + }), + take(1) + ).subscribe((results: PaginatedList) => { + this.processing$.next(false); + this.suggestionsRD$.next(results); + 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']); + // } + }); + } + + /** + * Used to delete a suggestion. + * @suggestionId + */ + notMine(suggestionId) { + this.suggestionService.notMine(suggestionId).subscribe((res) => { + this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); + this.updatePage(); + }); + } + + /** + * Used to delete all selected suggestions. + */ + notMineAllSelected() { + this.isBulkOperationPending = true; + this.suggestionService + .notMineMultiple(Object.values(this.selectedSuggestions)) + .subscribe((results: SuggestionBulkResult) => { + this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); + this.updatePage(); + this.isBulkOperationPending = false; + this.selectedSuggestions = {}; + if (results.success > 0) { + this.notificationService.success( + this.translateService.get('reciter.suggestion.notMine.bulk.success', + {count: results.success})); + } + if (results.fails > 0) { + this.notificationService.error( + this.translateService.get('reciter.suggestion.notMine.bulk.error', + {count: results.fails})); + } + }); + } + + /** + * Used to approve & import. + * @param event contains the suggestion and the target collection + */ + approveAndImport(event: SuggestionApproveAndImport) { + this.suggestionService.approveAndImport(this.workspaceItemService, event.suggestion, event.collectionId) + .subscribe((workspaceitem: WorkspaceItem) => { + const content = this.translateService.instant('reciter.suggestion.approveAndImport.success', { workspaceItemId: workspaceitem.id }); + this.notificationService.success('', content, {timeOut:0}, true); + this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); + this.updatePage(); + }); + } + + /** + * Used to approve & import all selected suggestions. + * @param event contains the target collection + */ + approveAndImportAllSelected(event: SuggestionApproveAndImport) { + this.isBulkOperationPending = true; + this.suggestionService + .approveAndImportMultiple(this.workspaceItemService, Object.values(this.selectedSuggestions), event.collectionId) + .subscribe((results: SuggestionBulkResult) => { + this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); + this.updatePage(); + this.isBulkOperationPending = false; + this.selectedSuggestions = {}; + if (results.success > 0) { + this.notificationService.success( + this.translateService.get('reciter.suggestion.approveAndImport.bulk.success', + {count: results.success})); + } + if (results.fails > 0) { + this.notificationService.error( + this.translateService.get('reciter.suggestion.approveAndImport.bulk.error', + {count: results.fails})); + } + }); + } + + /** + * When a specific suggestion is selected. + * @param object the suggestions + * @param selected the new selected value for the suggestion + */ + onSelected(object: OpenaireSuggestion, selected: boolean) { + if (selected) { + this.selectedSuggestions[object.id] = object; + } else { + delete this.selectedSuggestions[object.id]; + } + } + + /** + * When Toggle Select All occurs. + * @param suggestions all the visible suggestions inside the page + */ + onToggleSelectAll(suggestions: OpenaireSuggestion[]) { + if ( this.getSelectedSuggestionsCount() > 0) { + this.selectedSuggestions = {}; + } else { + suggestions.forEach((suggestion) => { + this.selectedSuggestions[suggestion.id] = suggestion; + }); + } + } + + /** + * The current number of selected suggestions. + */ + getSelectedSuggestionsCount(): number { + return Object.keys(this.selectedSuggestions).length; + } + + /** + * Return true if all the suggestion are configured with the same fixed collection in the configuration. + * @param suggestions + */ + isCollectionFixed(suggestions: OpenaireSuggestion[]): boolean { + return this.suggestionService.isCollectionFixed(suggestions); + } + + /** + * Label to be used to translate the suggestion source. + */ + translateSuggestionSource() { + return this.suggestionService.translateSuggestionSource(this.suggestionSource); + } + + /** + * Label to be used to translate the suggestion type. + */ + translateSuggestionType() { + return this.suggestionService.translateSuggestionType(this.suggestionSource); + } + +} diff --git a/src/app/suggestions-page/suggestions-page.module.ts b/src/app/suggestions-page/suggestions-page.module.ts new file mode 100644 index 0000000000..3fad2d0c19 --- /dev/null +++ b/src/app/suggestions-page/suggestions-page.module.ts @@ -0,0 +1,24 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { SuggestionsPageComponent } from './suggestions-page.component'; +import { SharedModule } from '../shared/shared.module'; +import { SuggestionsPageRoutingModule } from './suggestions-page-routing.module'; +import { SuggestionsService } from '../openaire/reciter-suggestions/suggestions.service'; +import { OpenaireSuggestionsDataService } from '../core/openaire/reciter-suggestions/openaire-suggestions-data.service'; +import { OpenaireModule } from '../openaire/openaire.module'; + +@NgModule({ + declarations: [SuggestionsPageComponent], + imports: [ + CommonModule, + SharedModule, + OpenaireModule, + SuggestionsPageRoutingModule + ], + providers: [ + OpenaireSuggestionsDataService, + SuggestionsService + ] +}) +export class SuggestionsPageModule { } diff --git a/src/app/suggestions-page/suggestions-page.resolver.ts b/src/app/suggestions-page/suggestions-page.resolver.ts new file mode 100644 index 0000000000..0027ae2e77 --- /dev/null +++ b/src/app/suggestions-page/suggestions-page.resolver.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; + +import { Observable } from 'rxjs'; +import { find } from 'rxjs/operators'; + +import { RemoteData } from '../core/data/remote-data'; +import { hasValue } from '../shared/empty.util'; +import { OpenaireSuggestionsDataService } from '../core/openaire/reciter-suggestions/openaire-suggestions-data.service'; +import { OpenaireSuggestionTarget } from '../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; + +/** + * This class represents a resolver that requests a specific collection before the route is activated + */ +@Injectable() +export class SuggestionsPageResolver implements Resolve> { + constructor(private suggestionsDataService: OpenaireSuggestionsDataService) { + } + + /** + * Method for resolving a suggestion target based on the parameters in the current route + * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot + * @param {RouterStateSnapshot} state The current RouterStateSnapshot + * @returns Observable<> Emits the found collection based on the parameters in the current route, + * or an error if something went wrong + */ + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { + return this.suggestionsDataService.getTargetById(route.params.targetId).pipe( + find((RD) => hasValue(RD.hasFailed) || RD.hasSucceeded), + ); + } +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 2af9d16b0c..e2919097d6 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -27,6 +27,10 @@ "404.page-not-found": "page not found", + "admin.notifications.recitersuggestion.breadcrumbs": "Suggestions", + + "admin.notifications.recitersuggestion.page.title": "Suggestions", + "admin.curation-tasks.breadcrumbs": "System curation tasks", "admin.curation-tasks.title": "System curation tasks", @@ -2511,6 +2515,7 @@ "menu.section.icon.unpin": "Unpin sidebar", + "menu.section.icon.notifications": "Notifictions menu section", "menu.section.import": "Import", @@ -2533,6 +2538,10 @@ "menu.section.new_process": "Process", + "menu.section.notifications": "Notifications", + + "menu.section.notifications_reciter": "Publication Claim", + "menu.section.pin": "Pin sidebar", @@ -2960,6 +2969,62 @@ "media-viewer.playlist": "Playlist", + "reciter.suggestion.loading": "Loading ...", + + "reciter.suggestion.title": "Suggestions", + + "reciter.suggestion.targets.description": "Below you can see all the suggestions ", + + "reciter.suggestion.targets": "Current Suggestions", + + "reciter.suggestion.table.name": "Researcher Name", + + "reciter.suggestion.table.actions": "Actions", + + "reciter.suggestion.button.review": "Review {{ total }} suggestion(s)", + + "reciter.suggestion.noTargets": "No target found.", + + "reciter.suggestion.target.error.service.retrieve": "An error occurred while loading the Suggestion targets", + + "reciter.suggestion.evidence.type": "Type", + + "reciter.suggestion.evidence.score": "Score", + + "reciter.suggestion.evidence.notes": "Notes", + + "reciter.suggestion.approveAndImport": "Approve & import", + + "reciter.suggestion.approveAndImport.success": "The suggestion has been imported successfully", + + "reciter.suggestion.approveAndImport.bulk": "Approve & import Selected", + + "reciter.suggestion.approveAndImport.bulk.success": "{{ count }} suggestions have been imported successfully ", + + "reciter.suggestion.approveAndImport.bulk.error": "{{ count }} suggestions haven't been imported due to unexpected server errors", + + "reciter.suggestion.notMine": "Not mine", + + "reciter.suggestion.notMine.success": "The suggestion has been discarded", + + "reciter.suggestion.notMine.bulk": "Not mine Selected", + + "reciter.suggestion.notMine.bulk.success": "{{ count }} suggestions have been discarded ", + + "reciter.suggestion.notMine.bulk.error": "{{ count }} suggestions haven't been discarded due to unexpected server errors", + + "reciter.suggestion.seeEvidence": "See evidence", + + "reciter.suggestion.hideEvidence": "Hide evidence", + + "reciter.suggestion.suggestionFor": "Suggestion for", + + "reciter.suggestion.source.oaire": "OpenAIRE Graph", + + "reciter.suggestion.from.source": "from the ", + + "reciter.suggestion.totalScore": "Total Score", + "register-email.title": "New user registration", From b4d6fbc390282a381e1f7cc340615c40f39e936a Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Mon, 2 May 2022 17:40:51 +0200 Subject: [PATCH 02/94] [CST-5249] Fixed compilation issues --- .../admin-sidebar/admin-sidebar.component.ts | 1 - .../openaire-suggestion-source.model.ts | 2 +- .../openaire-suggestion-target.model.ts | 2 +- .../models/openaire-suggestion.model.ts | 2 +- .../openaire-suggestions-data.service.ts | 4 +- .../submission/workspaceitem-data.service.ts | 31 ++++++++++++- src/app/openaire/openaire.effects.ts | 5 ++ src/app/openaire/openaire.module.ts | 2 + .../openaire/reciter-suggestions/selectors.ts | 6 +-- .../suggestion-list-element.component.html | 1 - .../suggestion-targets.component.html | 1 - .../suggestion-targets.effects.ts | 2 +- .../suggestions.service.ts | 12 ++--- .../suggestions-page.component.ts | 5 +- src/config/app-config.interface.ts | 2 + src/config/default-app-config.ts | 9 ++++ src/config/layout-config.interfaces.ts | 46 +++++++++++++++++++ 17 files changed, 112 insertions(+), 21 deletions(-) create mode 100644 src/app/openaire/openaire.effects.ts create mode 100644 src/config/layout-config.interfaces.ts diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index a8c5ef800c..b770e3d675 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -42,7 +42,6 @@ import { Router, ActivatedRoute } from '@angular/router'; import {NOTIFICATIONS_RECITER_SUGGESTION_PATH} from "../admin-notifications/admin-notifications-routing-paths"; import { MenuID } from '../../shared/menu/menu-id.model'; import { MenuItemType } from '../../shared/menu/menu-item-type.model'; -import { ActivatedRoute } from '@angular/router'; /** * Component representing the admin sidebar diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts index 6da9fd47b9..d71b49b913 100644 --- a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts @@ -1,11 +1,11 @@ import { autoserialize, deserialize } from 'cerialize'; -import { CacheableObject } from '../../../cache/object-cache.reducer'; import { SUGGESTION_SOURCE } from './openaire-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 {CacheableObject} from "../../../cache/cacheable-object.model"; /** * The interface representing the Suggestion Source model diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts index e35972bc79..06f85ea5b3 100644 --- a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts @@ -1,11 +1,11 @@ import { autoserialize, deserialize } from 'cerialize'; -import { CacheableObject } from '../../../cache/object-cache.reducer'; import { SUGGESTION_TARGET } from './openaire-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 {CacheableObject} from "../../../cache/cacheable-object.model"; /** * The interface representing the Suggestion Target model diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts index 3ff5d7b630..2d28ccf9bc 100644 --- a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts @@ -1,12 +1,12 @@ import { autoserialize, autoserializeAs, deserialize } from 'cerialize'; -import { CacheableObject } from '../../../cache/object-cache.reducer'; import { SUGGESTION } from './openaire-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"; export interface SuggestionEvidences { [sectionId: string]: { diff --git a/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts b/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts index d961eaf9b5..7454caa1d9 100644 --- a/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts +++ b/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts @@ -4,14 +4,12 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { CoreState } from '../../core.reducers'; import { HALEndpointService } from '../../shared/hal-endpoint.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../../cache/object-cache.service'; import { dataService } from '../../cache/builders/build-decorators'; import { RequestService } from '../../data/request.service'; -import { FindListOptions } from '../../data/request.models'; import { DataService } from '../../data/data.service'; import { ChangeAnalyzer } from '../../data/change-analyzer'; import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service'; @@ -24,6 +22,8 @@ import { OpenaireSuggestionTarget } from './models/openaire-suggestion-target.mo import { OpenaireSuggestion } from './models/openaire-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"; /* tslint:disable:max-classes-per-file */ diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 1b9782834c..a4dbaab6ca 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; +import {HttpClient, HttpHeaders} from '@angular/common/http'; import { Store } from '@ngrx/store'; import { dataService } from '../cache/builders/build-decorators'; @@ -17,6 +17,10 @@ import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { RequestParam } from '../cache/models/request-param.model'; import { CoreState } from '../core-state.model'; import { FindListOptions } from '../data/find-list-options.model'; +import {HttpOptions} from "../dspace-rest/dspace-rest.service"; +import {find, map} from "rxjs/operators"; +import {PostRequest} from "../data/request.models"; +import {hasValue} from "../../shared/empty.util"; /** * A service that provides methods to make REST requests with workspaceitems endpoint. @@ -57,4 +61,29 @@ export class WorkspaceitemDataService extends DataService { return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } + /** + * Import an external source entry into a collection + * @param externalSourceEntryHref + * @param collectionId + */ + public importExternalSourceEntry(externalSourceEntryHref: string, collectionId: string): Observable> { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + + const requestId = this.requestService.generateRequestId(); + const href$ = this.halService.getEndpoint(this.linkPath).pipe(map((href) => `${href}?owningCollection=${collectionId}`)); + + href$.pipe( + find((href: string) => hasValue(href)), + map((href: string) => { + const request = new PostRequest(requestId, href, externalSourceEntryHref, options); + this.requestService.send(request); + }) + ).subscribe(); + + return this.rdbService.buildFromRequestUUID(requestId); + } + } diff --git a/src/app/openaire/openaire.effects.ts b/src/app/openaire/openaire.effects.ts new file mode 100644 index 0000000000..d771089f92 --- /dev/null +++ b/src/app/openaire/openaire.effects.ts @@ -0,0 +1,5 @@ +import { SuggestionTargetsEffects } from './reciter-suggestions/suggestion-targets/suggestion-targets.effects'; + +export const openaireEffects = [ + SuggestionTargetsEffects +]; diff --git a/src/app/openaire/openaire.module.ts b/src/app/openaire/openaire.module.ts index 22d04f3002..ace4622190 100644 --- a/src/app/openaire/openaire.module.ts +++ b/src/app/openaire/openaire.module.ts @@ -18,12 +18,14 @@ import { SuggestionsPopupComponent } from './reciter-suggestions/suggestions-pop import { SuggestionsNotificationComponent } from './reciter-suggestions/suggestions-notification/suggestions-notification.component'; import { TranslateModule } from '@ngx-translate/core'; import { SearchModule } from '../shared/search/search.module'; +import {openaireEffects} from "./openaire.effects"; const MODULES = [ CommonModule, SharedModule, CoreModule.forRoot(), StoreModule.forFeature('openaire', openaireReducers, storeModuleConfig as StoreConfig), + EffectsModule.forFeature(openaireEffects), TranslateModule ]; diff --git a/src/app/openaire/reciter-suggestions/selectors.ts b/src/app/openaire/reciter-suggestions/selectors.ts index e699b27dba..292e63472a 100644 --- a/src/app/openaire/reciter-suggestions/selectors.ts +++ b/src/app/openaire/reciter-suggestions/selectors.ts @@ -1,8 +1,8 @@ -import { createSelector, MemoizedSelector } from '@ngrx/store'; -import { subStateSelector } from '../../shared/selector.util'; +import {createFeatureSelector, createSelector, MemoizedSelector} from '@ngrx/store'; import { openaireSelector, OpenaireState } from '../openaire.reducer'; import { OpenaireSuggestionTarget } from '../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; import { SuggestionTargetState } from './suggestion-targets/suggestion-targets.reducer'; +import {subStateSelector} from "../../submission/selectors"; /** * Returns the Reciter Suggestion Target state. @@ -10,7 +10,7 @@ import { SuggestionTargetState } from './suggestion-targets/suggestion-targets.r * @param {AppState} state Top level state. * @return {OpenaireState} */ -const _getReciterSuggestionTargetState = (state: any) => state.openaire; +const _getReciterSuggestionTargetState = createFeatureSelector('openaire'); // Reciter Suggestion Targets // ---------------------------------------------------------------------------- diff --git a/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html index 05f9c0ac77..f37d595c45 100644 --- a/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html +++ b/src/app/openaire/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html @@ -21,7 +21,6 @@ [showLabel]="false" [object]="listableObject" [linkType]="0" - [hideMetrics]="true" > diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts index 85e871403c..9a007fab21 100644 --- a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts +++ b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts @@ -81,7 +81,7 @@ export class SuggestionTargetsEffects { return this.store$.select((state: any) => state.core.auth.user) .pipe( switchMap((user: EPerson) => { - return this.suggestionsService.retrieveCurrentUserSuggestions(user) + return this.suggestionsService.retrieveCurrentUserSuggestions(user.uuid) .pipe( map((suggestionTargets: OpenaireSuggestionTarget[]) => new AddUserSuggestionsAction(suggestionTargets)), catchError((errors) => of(errors)) diff --git a/src/app/openaire/reciter-suggestions/suggestions.service.ts b/src/app/openaire/reciter-suggestions/suggestions.service.ts index de743d95e3..5908c8c6bf 100644 --- a/src/app/openaire/reciter-suggestions/suggestions.service.ts +++ b/src/app/openaire/reciter-suggestions/suggestions.service.ts @@ -5,7 +5,6 @@ import { catchError, map, mergeMap, take } from 'rxjs/operators'; import { OpenaireSuggestionsDataService } from '../../core/openaire/reciter-suggestions/openaire-suggestions-data.service'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { FindListOptions } from '../../core/data/request.models'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { OpenaireSuggestionTarget } from '../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; @@ -25,8 +24,9 @@ import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-da import { TranslateService } from '@ngx-translate/core'; import { NoContent } from '../../core/shared/NoContent.model'; import { environment } from '../../../environments/environment'; -import { SuggestionConfig } from '../../../config/layout-config.interfaces'; import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model'; +import {FindListOptions} from "../../core/data/find-list-options.model"; +import {SuggestionConfig} from "../../../config/layout-config.interfaces"; export interface SuggestionBulkResult { success: number; @@ -142,11 +142,11 @@ export class SuggestionsService { /** * Retrieve suggestion targets for the given user * - * @param user - * The EPerson object for which to retrieve suggestion targets + * @param userUuid + * The EPerson id for which to retrieve suggestion targets */ - public retrieveCurrentUserSuggestions(user: EPerson): Observable { - return this.researcherProfileService.findById(user.uuid).pipe( + public retrieveCurrentUserSuggestions(userUuid: string): Observable { + return this.researcherProfileService.findById(userUuid).pipe( mergeMap((profile: ResearcherProfile) => { if (isNotEmpty(profile)) { return this.researcherProfileService.findRelatedItemId(profile).pipe( diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts index 911f6167de..850a30da22 100644 --- a/src/app/suggestions-page/suggestions-page.component.ts +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -8,7 +8,7 @@ import { TranslateService } from '@ngx-translate/core'; import { SortDirection, SortOptions, } from '../core/cache/models/sort-options.model'; import { PaginatedList } from '../core/data/paginated-list.model'; import { RemoteData } from '../core/data/remote-data'; -import { getFirstSucceededRemoteDataPayload, redirectOn4xx } from '../core/shared/operators'; +import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { SuggestionBulkResult, SuggestionsService } from '../openaire/reciter-suggestions/suggestions.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { OpenaireSuggestion } from '../core/openaire/reciter-suggestions/models/openaire-suggestion.model'; @@ -19,8 +19,9 @@ import { NotificationsService } from '../shared/notifications/notifications.serv import { SuggestionTargetsStateService } from '../openaire/reciter-suggestions/suggestion-targets/suggestion-targets.state.service'; import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; import { PaginationService } from '../core/pagination/pagination.service'; -import { FindListOptions } from '../core/data/request.models'; import { WorkspaceItem } from '../core/submission/models/workspaceitem.model'; +import {FindListOptions} from "../core/data/find-list-options.model"; +import {redirectOn4xx} from "../core/shared/authorized.operators"; @Component({ selector: 'ds-suggestion-page', diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index 121e80cd74..6446e7f127 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -14,6 +14,7 @@ import { AuthConfig } from './auth-config.interfaces'; import { UIServerConfig } from './ui-server-config.interface'; import { MediaViewerConfig } from './media-viewer-config.interface'; import { BrowseByConfig } from './browse-by-config.interface'; +import {SuggestionConfig} from "./layout-config.interfaces"; interface AppConfig extends Config { ui: UIServerConfig; @@ -32,6 +33,7 @@ interface AppConfig extends Config { collection: CollectionPageConfig; themes: ThemeConfig[]; mediaViewer: MediaViewerConfig; + suggestion: SuggestionConfig[]; } const APP_CONFIG = new InjectionToken('APP_CONFIG'); diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index dc54c2fcb0..aea29fc819 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -14,6 +14,7 @@ import { ServerConfig } from './server-config.interface'; import { SubmissionConfig } from './submission-config.interface'; import { ThemeConfig } from './theme.model'; import { UIServerConfig } from './ui-server-config.interface'; +import {SuggestionConfig} from "./layout-config.interfaces"; export class DefaultAppConfig implements AppConfig { production = false; @@ -210,6 +211,14 @@ export class DefaultAppConfig implements AppConfig { } }; + suggestion: SuggestionConfig[] = [ + // { + // // Use this configuration to map a suggestion import to a specific collection based on the suggestion type. + // source: 'suggestionSource', + // collectionId: 'collectionUUID' + // } + ]; + // Theme Config themes: ThemeConfig[] = [ // Add additional themes here. In the case where multiple themes match a route, the first one diff --git a/src/config/layout-config.interfaces.ts b/src/config/layout-config.interfaces.ts new file mode 100644 index 0000000000..0b15a06aa9 --- /dev/null +++ b/src/config/layout-config.interfaces.ts @@ -0,0 +1,46 @@ +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; +} From 11d25e66997d99bd1cb932c057feff3f8dc30e70 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Wed, 4 May 2022 13:16:13 +0200 Subject: [PATCH 03/94] [CST-5249] Fixed suggestion page --- .../admin-sidebar/admin-sidebar.component.ts | 2 +- src/app/core/core.module.ts | 2 +- .../openaire-suggestion-source.model.ts | 2 +- .../openaire-suggestion-target.model.ts | 2 +- .../models/openaire-suggestion.model.ts | 2 +- .../openaire-suggestions-data.service.ts | 5 +- .../submission/workspaceitem-data.service.ts | 8 +- src/app/openaire/openaire.module.ts | 2 +- .../openaire/reciter-suggestions/selectors.ts | 2 +- .../suggestion-targets.actions.ts | 1 + .../suggestion-targets.effects.ts | 6 +- .../suggestions.service.ts | 4 +- .../dso-selector-modal-wrapper.component.ts | 24 +- src/app/shared/mocks/openaire.mock.ts | 445 ------------------ .../suggestions-page.component.ts | 4 +- src/assets/i18n/en.json5 | 1 + src/config/app-config.interface.ts | 2 +- src/config/default-app-config.ts | 2 +- src/environments/environment.test.ts | 8 + 19 files changed, 55 insertions(+), 469 deletions(-) diff --git a/src/app/admin/admin-sidebar/admin-sidebar.component.ts b/src/app/admin/admin-sidebar/admin-sidebar.component.ts index b770e3d675..c13599be9d 100644 --- a/src/app/admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/admin/admin-sidebar/admin-sidebar.component.ts @@ -39,7 +39,7 @@ import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { Router, ActivatedRoute } from '@angular/router'; -import {NOTIFICATIONS_RECITER_SUGGESTION_PATH} from "../admin-notifications/admin-notifications-routing-paths"; +import {NOTIFICATIONS_RECITER_SUGGESTION_PATH} from '../admin-notifications/admin-notifications-routing-paths'; import { MenuID } from '../../shared/menu/menu-id.model'; import { MenuItemType } from '../../shared/menu/menu-item-type.model'; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 7273996401..879ba76c7d 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -168,7 +168,7 @@ import { OpenaireSuggestionSource } from './openaire/reciter-suggestions/models/ import { ResearcherProfileService } from './profile/researcher-profile.service'; import { ProfileClaimService } from '../profile-page/profile-claim/profile-claim.service'; import { ResearcherProfile } from './profile/model/researcher-profile.model'; -import {SubmissionAccessesModel} from "./config/models/config-submission-accesses.model"; +import {SubmissionAccessesModel} from './config/models/config-submission-accesses.model'; /** * When not in production, endpoint responses can be mocked for testing purposes diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts index d71b49b913..00f5f11936 100644 --- a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-source.model.ts @@ -5,7 +5,7 @@ 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 {CacheableObject} from '../../../cache/cacheable-object.model'; /** * The interface representing the Suggestion Source model diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts index 06f85ea5b3..96a43c9654 100644 --- a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion-target.model.ts @@ -5,7 +5,7 @@ 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 {CacheableObject} from '../../../cache/cacheable-object.model'; /** * The interface representing the Suggestion Target model diff --git a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts index 2d28ccf9bc..0f84072c6b 100644 --- a/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts +++ b/src/app/core/openaire/reciter-suggestions/models/openaire-suggestion.model.ts @@ -6,7 +6,7 @@ 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 {CacheableObject} from '../../../cache/cacheable-object.model'; export interface SuggestionEvidences { [sectionId: string]: { diff --git a/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts b/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts index 7454caa1d9..3c1d04c040 100644 --- a/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts +++ b/src/app/core/openaire/reciter-suggestions/openaire-suggestions-data.service.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Store } from '@ngrx/store'; @@ -22,8 +23,8 @@ import { OpenaireSuggestionTarget } from './models/openaire-suggestion-target.mo import { OpenaireSuggestion } from './models/openaire-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 {CoreState} from '../../core-state.model'; +import {FindListOptions} from '../../data/find-list-options.model'; /* tslint:disable:max-classes-per-file */ diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index a4dbaab6ca..6c0e909bb4 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -17,10 +17,10 @@ import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { RequestParam } from '../cache/models/request-param.model'; import { CoreState } from '../core-state.model'; import { FindListOptions } from '../data/find-list-options.model'; -import {HttpOptions} from "../dspace-rest/dspace-rest.service"; -import {find, map} from "rxjs/operators"; -import {PostRequest} from "../data/request.models"; -import {hasValue} from "../../shared/empty.util"; +import {HttpOptions} from '../dspace-rest/dspace-rest.service'; +import {find, map} from 'rxjs/operators'; +import {PostRequest} from '../data/request.models'; +import {hasValue} from '../../shared/empty.util'; /** * A service that provides methods to make REST requests with workspaceitems endpoint. diff --git a/src/app/openaire/openaire.module.ts b/src/app/openaire/openaire.module.ts index ace4622190..8ae52b27f4 100644 --- a/src/app/openaire/openaire.module.ts +++ b/src/app/openaire/openaire.module.ts @@ -18,7 +18,7 @@ import { SuggestionsPopupComponent } from './reciter-suggestions/suggestions-pop import { SuggestionsNotificationComponent } from './reciter-suggestions/suggestions-notification/suggestions-notification.component'; import { TranslateModule } from '@ngx-translate/core'; import { SearchModule } from '../shared/search/search.module'; -import {openaireEffects} from "./openaire.effects"; +import {openaireEffects} from './openaire.effects'; const MODULES = [ CommonModule, diff --git a/src/app/openaire/reciter-suggestions/selectors.ts b/src/app/openaire/reciter-suggestions/selectors.ts index 292e63472a..cfbb36d4c2 100644 --- a/src/app/openaire/reciter-suggestions/selectors.ts +++ b/src/app/openaire/reciter-suggestions/selectors.ts @@ -2,7 +2,7 @@ import {createFeatureSelector, createSelector, MemoizedSelector} from '@ngrx/sto import { openaireSelector, OpenaireState } from '../openaire.reducer'; import { OpenaireSuggestionTarget } from '../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; import { SuggestionTargetState } from './suggestion-targets/suggestion-targets.reducer'; -import {subStateSelector} from "../../submission/selectors"; +import {subStateSelector} from '../../submission/selectors'; /** * Returns the Reciter Suggestion Target state. diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts index 6c44d40b91..33dfb1474b 100644 --- a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts +++ b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ import { Action } from '@ngrx/store'; import { type } from '../../../shared/ngrx/type'; import { OpenaireSuggestionTarget } from '../../../core/openaire/reciter-suggestions/models/openaire-suggestion-target.model'; diff --git a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts index 9a007fab21..29672ad49b 100644 --- a/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts +++ b/src/app/openaire/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts @@ -78,10 +78,10 @@ export class SuggestionTargetsEffects { @Effect() refreshUserTargets$ = this.actions$.pipe( ofType(SuggestionTargetActionTypes.REFRESH_USER_SUGGESTIONS), switchMap((action: RefreshUserSuggestionsAction) => { - return this.store$.select((state: any) => state.core.auth.user) + return this.store$.select((state: any) => state.core.auth.userId) .pipe( - switchMap((user: EPerson) => { - return this.suggestionsService.retrieveCurrentUserSuggestions(user.uuid) + switchMap((userId: string) => { + return this.suggestionsService.retrieveCurrentUserSuggestions(userId) .pipe( map((suggestionTargets: OpenaireSuggestionTarget[]) => new AddUserSuggestionsAction(suggestionTargets)), catchError((errors) => of(errors)) diff --git a/src/app/openaire/reciter-suggestions/suggestions.service.ts b/src/app/openaire/reciter-suggestions/suggestions.service.ts index 5908c8c6bf..67b496b903 100644 --- a/src/app/openaire/reciter-suggestions/suggestions.service.ts +++ b/src/app/openaire/reciter-suggestions/suggestions.service.ts @@ -25,8 +25,8 @@ 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/layout-config.interfaces"; +import {FindListOptions} from '../../core/data/find-list-options.model'; +import {SuggestionConfig} from '../../../config/layout-config.interfaces'; export interface SuggestionBulkResult { success: number; diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index ca8343cfad..3b3ef59ae5 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { RemoteData } from '../../../core/data/remote-data'; @@ -21,11 +21,22 @@ export enum SelectorActionType { template: '' }) export abstract class DSOSelectorModalWrapperComponent implements OnInit { + + /** + * The discovery configuration. + */ + @Input() configuration = 'default'; + /** * The current page's DSO */ @Input() dsoRD: RemoteData; + /** + * Representing if component should emit value of selected entries or navigate + */ + @Input() emitOnly = false; + /** * Optional header to display above the selection list * Supports i18n keys @@ -47,6 +58,11 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit { */ action: SelectorActionType; + /** + * Event emitted when a DSO entry is selected if emitOnly is set to true + */ + @Output() select: EventEmitter = new EventEmitter(); + constructor(protected activeModal: NgbActiveModal, protected route: ActivatedRoute) { } @@ -85,7 +101,11 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit { */ selectObject(dso: DSpaceObject) { this.close(); - this.navigate(dso); + if (this.emitOnly) { + this.select.emit(dso); + } else { + this.navigate(dso); + } } /** diff --git a/src/app/shared/mocks/openaire.mock.ts b/src/app/shared/mocks/openaire.mock.ts index 95ea871727..d6c50510cd 100644 --- a/src/app/shared/mocks/openaire.mock.ts +++ b/src/app/shared/mocks/openaire.mock.ts @@ -1,17 +1,5 @@ -import { of as observableOf } from 'rxjs'; -import { ResourceType } from '../../core/shared/resource-type'; -import { OpenaireBrokerTopicObject } from '../../core/openaire/broker/models/openaire-broker-topic.model'; -import { OpenaireBrokerEventObject } from '../../core/openaire/broker/models/openaire-broker-event.model'; -import { OpenaireBrokerTopicRestService } from '../../core/openaire/broker/topics/openaire-broker-topic-rest.service'; -import { OpenaireBrokerEventRestService } from '../../core/openaire/broker/events/openaire-broker-event-rest.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; -import { OpenaireStateService } from '../../openaire/openaire-state.service'; import { Item } from '../../core/shared/item.model'; -import { - createNoContentRemoteDataObject$, - createSuccessfulRemoteDataObject, - createSuccessfulRemoteDataObject$ -} from '../remote-data.utils'; import { SearchResult } from '../search/models/search-result.model'; import { SuggestionsService } from '../../openaire/reciter-suggestions/suggestions.service'; @@ -1330,414 +1318,6 @@ export const OpenaireMockDspaceObject: SearchResult = Object.assig } ); -// Topics -// ------------------------------------------------------------------------------- - -export const openaireBrokerTopicObjectMorePid: OpenaireBrokerTopicObject = { - type: new ResourceType('nbtopic'), - id: 'ENRICH!MORE!PID', - name: 'ENRICH/MORE/PID', - lastEvent: '2020/10/09 10:11 UTC', - totalEvents: 33, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MORE!PID' - } - } -}; - -export const openaireBrokerTopicObjectMoreAbstract: OpenaireBrokerTopicObject = { - type: new ResourceType('nbtopic'), - id: 'ENRICH!MORE!ABSTRACT', - name: 'ENRICH/MORE/ABSTRACT', - lastEvent: '2020/09/08 21:14 UTC', - totalEvents: 5, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MORE!ABSTRACT' - } - } -}; - -export const openaireBrokerTopicObjectMissingPid: OpenaireBrokerTopicObject = { - type: new ResourceType('nbtopic'), - id: 'ENRICH!MISSING!PID', - name: 'ENRICH/MISSING/PID', - lastEvent: '2020/10/01 07:36 UTC', - totalEvents: 4, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MISSING!PID' - } - } -}; - -export const openaireBrokerTopicObjectMissingAbstract: OpenaireBrokerTopicObject = { - type: new ResourceType('nbtopic'), - id: 'ENRICH!MISSING!ABSTRACT', - name: 'ENRICH/MISSING/ABSTRACT', - lastEvent: '2020/10/08 16:14 UTC', - totalEvents: 71, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MISSING!ABSTRACT' - } - } -}; - -export const openaireBrokerTopicObjectMissingAcm: OpenaireBrokerTopicObject = { - type: new ResourceType('nbtopic'), - id: 'ENRICH!MISSING!SUBJECT!ACM', - name: 'ENRICH/MISSING/SUBJECT/ACM', - lastEvent: '2020/09/21 17:51 UTC', - totalEvents: 18, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MISSING!SUBJECT!ACM' - } - } -}; - -export const openaireBrokerTopicObjectMissingProject: OpenaireBrokerTopicObject = { - type: new ResourceType('nbtopic'), - id: 'ENRICH!MISSING!PROJECT', - name: 'ENRICH/MISSING/PROJECT', - lastEvent: '2020/09/17 10:28 UTC', - totalEvents: 6, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbtopics/ENRICH!MISSING!PROJECT' - } - } -}; - -// Events -// ------------------------------------------------------------------------------- - -export const openaireBrokerEventObjectMissingPid: OpenaireBrokerEventObject = { - id: '123e4567-e89b-12d3-a456-426614174001', - uuid: '123e4567-e89b-12d3-a456-426614174001', - type: new ResourceType('nbevent'), - originalId: 'oai:www.openstarts.units.it:10077/21486', - title: 'Index nominum et rerum', - trust: 0.375, - eventDate: '2020/10/09 10:11 UTC', - status: 'PENDING', - message: { - type: 'doi', - value: '10.18848/1447-9494/cgp/v15i09/45934', - abstract: null, - openaireId: null, - acronym: null, - code: null, - funder: null, - fundingProgram: null, - jurisdiction: null, - title: null - }, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174001', - }, - target: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174001/target' - }, - related: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174001/related' - } - }, - target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid1)), - related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) -}; - -export const openaireBrokerEventObjectMissingPid2: OpenaireBrokerEventObject = { - id: '123e4567-e89b-12d3-a456-426614174004', - uuid: '123e4567-e89b-12d3-a456-426614174004', - type: new ResourceType('openaireBrokerEvent'), - originalId: 'oai:www.openstarts.units.it:10077/21486', - title: 'UNA NUOVA RILETTURA DELL\u0027 ARISTOTELE DI FRANZ BRENTANO ALLA LUCE DI ALCUNI INEDITI', - trust: 1.0, - eventDate: '2020/10/09 10:11 UTC', - status: 'PENDING', - message: { - type: 'urn', - value: 'http://thesis2.sba.units.it/store/handle/item/12238', - abstract: null, - openaireId: null, - acronym: null, - code: null, - funder: null, - fundingProgram: null, - jurisdiction: null, - title: null - }, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174004' - }, - target: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174004/target' - }, - related: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174004/related' - } - }, - target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid2)), - related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) -}; - -export const openaireBrokerEventObjectMissingPid3: OpenaireBrokerEventObject = { - id: '123e4567-e89b-12d3-a456-426614174005', - uuid: '123e4567-e89b-12d3-a456-426614174005', - type: new ResourceType('openaireBrokerEvent'), - originalId: 'oai:www.openstarts.units.it:10077/554', - title: 'Sustainable development', - trust: 0.375, - eventDate: '2020/10/09 10:11 UTC', - status: 'PENDING', - message: { - type: 'doi', - value: '10.4324/9780203408889', - abstract: null, - openaireId: null, - acronym: null, - code: null, - funder: null, - fundingProgram: null, - jurisdiction: null, - title: null - }, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174005' - }, - target: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174005/target' - }, - related: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174005/related' - } - }, - target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid3)), - related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) -}; - -export const openaireBrokerEventObjectMissingPid4: OpenaireBrokerEventObject = { - id: '123e4567-e89b-12d3-a456-426614174006', - uuid: '123e4567-e89b-12d3-a456-426614174006', - type: new ResourceType('openaireBrokerEvent'), - originalId: 'oai:www.openstarts.units.it:10077/10787', - title: 'Reply to Critics', - trust: 1.0, - eventDate: '2020/10/09 10:11 UTC', - status: 'PENDING', - message: { - type: 'doi', - value: '10.1080/13698230.2018.1430104', - abstract: null, - openaireId: null, - acronym: null, - code: null, - funder: null, - fundingProgram: null, - jurisdiction: null, - title: null - }, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174006' - }, - target: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174006/target' - }, - related: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174006/related' - } - }, - target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid4)), - related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) -}; - -export const openaireBrokerEventObjectMissingPid5: OpenaireBrokerEventObject = { - id: '123e4567-e89b-12d3-a456-426614174007', - uuid: '123e4567-e89b-12d3-a456-426614174007', - type: new ResourceType('openaireBrokerEvent'), - originalId: 'oai:www.openstarts.units.it:10077/11339', - title: 'PROGETTAZIONE, SINTESI E VALUTAZIONE DELL\u0027ATTIVITA\u0027 ANTIMICOBATTERICA ED ANTIFUNGINA DI NUOVI DERIVATI ETEROCICLICI', - trust: 0.375, - eventDate: '2020/10/09 10:11 UTC', - status: 'PENDING', - message: { - type: 'urn', - value: 'http://thesis2.sba.units.it/store/handle/item/12477', - abstract: null, - openaireId: null, - acronym: null, - code: null, - funder: null, - fundingProgram: null, - jurisdiction: null, - title: null - }, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174007' - }, - target: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174007/target' - }, - related: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174007/related' - } - }, - target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid5)), - related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) -}; - -export const openaireBrokerEventObjectMissingPid6: OpenaireBrokerEventObject = { - id: '123e4567-e89b-12d3-a456-426614174008', - uuid: '123e4567-e89b-12d3-a456-426614174008', - type: new ResourceType('openaireBrokerEvent'), - originalId: 'oai:www.openstarts.units.it:10077/29860', - title: 'Donald Davidson', - trust: 0.375, - eventDate: '2020/10/09 10:11 UTC', - status: 'PENDING', - message: { - type: 'doi', - value: '10.1111/j.1475-4975.2004.00098.x', - abstract: null, - openaireId: null, - acronym: null, - code: null, - funder: null, - fundingProgram: null, - jurisdiction: null, - title: null - }, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174008' - }, - target: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174008/target' - }, - related: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174008/related' - } - }, - target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid6)), - related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) -}; - -export const openaireBrokerEventObjectMissingAbstract: OpenaireBrokerEventObject = { - id: '123e4567-e89b-12d3-a456-426614174009', - uuid: '123e4567-e89b-12d3-a456-426614174009', - type: new ResourceType('openaireBrokerEvent'), - originalId: 'oai:www.openstarts.units.it:10077/21110', - title: 'Missing abstract article', - trust: 0.751, - eventDate: '2020/10/09 10:11 UTC', - status: 'PENDING', - message: { - type: null, - value: null, - abstract: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla scelerisque vestibulum tellus sed lacinia. Aenean vitae sapien a quam congue ultrices. Sed vehicula sollicitudin ligula, vitae lacinia velit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla scelerisque vestibulum tellus sed lacinia. Aenean vitae sapien a quam congue ultrices. Sed vehicula sollicitudin ligula, vitae lacinia velit.', - openaireId: null, - acronym: null, - code: null, - funder: null, - fundingProgram: null, - jurisdiction: null, - title: null - }, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174009' - }, - target: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174009/target' - }, - related: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174009/related' - } - }, - target: observableOf(createSuccessfulRemoteDataObject(ItemMockPid7)), - related: observableOf(createSuccessfulRemoteDataObject(ItemMockPid10)) -}; - -export const openaireBrokerEventObjectMissingProjectFound: OpenaireBrokerEventObject = { - id: '123e4567-e89b-12d3-a456-426614174002', - uuid: '123e4567-e89b-12d3-a456-426614174002', - type: new ResourceType('openaireBrokerEvent'), - originalId: 'oai:www.openstarts.units.it:10077/21838', - title: 'Egypt, crossroad of translations and literary interweavings (3rd-6th centuries). A reconsideration of earlier Coptic literature', - trust: 1.0, - eventDate: '2020/10/09 10:11 UTC', - status: 'PENDING', - message: { - type: null, - value: null, - abstract: null, - openaireId: null, - acronym: 'PAThs', - code: '687567', - funder: 'EC', - fundingProgram: 'H2020', - jurisdiction: 'EU', - title: 'Tracking Papyrus and Parchment Paths: An Archaeological Atlas of Coptic Literature.\nLiterary Texts in their Geographical Context: Production, Copying, Usage, Dissemination and Storage' - }, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174002' - }, - target: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174002/target' - }, - related: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174002/related' - } - }, - target: createSuccessfulRemoteDataObject$(ItemMockPid8), - related: createSuccessfulRemoteDataObject$(ItemMockPid10) -}; - -export const openaireBrokerEventObjectMissingProjectNotFound: OpenaireBrokerEventObject = { - id: '123e4567-e89b-12d3-a456-426614174003', - uuid: '123e4567-e89b-12d3-a456-426614174003', - type: new ResourceType('openaireBrokerEvent'), - originalId: 'oai:www.openstarts.units.it:10077/21838', - title: 'Morocco, crossroad of translations and literary interweavings (3rd-6th centuries). A reconsideration of earlier Coptic literature', - trust: 1.0, - eventDate: '2020/10/09 10:11 UTC', - status: 'PENDING', - message: { - type: null, - value: null, - abstract: null, - openaireId: null, - acronym: 'PAThs', - code: '687567B', - funder: 'EC', - fundingProgram: 'H2021', - jurisdiction: 'EU', - title: 'Tracking Unknown Papyrus and Parchment Paths: An Archaeological Atlas of Coptic Literature.\nLiterary Texts in their Geographical Context: Production, Copying, Usage, Dissemination and Storage' - }, - _links: { - self: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174003' - }, - target: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174003/target' - }, - related: { - href: 'https://rest.api/rest/api/integration/nbevents/123e4567-e89b-12d3-a456-426614174003/related' - } - }, - target: createSuccessfulRemoteDataObject$(ItemMockPid9), - related: createNoContentRemoteDataObject$() -}; - // Classes // ------------------------------------------------------------------------------- @@ -1757,31 +1337,6 @@ export function getMockOpenaireStateService(): any { dispatchMarkUserSuggestionsAsVisitedAction: jasmine.createSpy('dispatchMarkUserSuggestionsAsVisitedAction') }); } - -/** - * Mock for [[OpenaireBrokerTopicRestService]] - */ -export function getMockOpenaireBrokerTopicRestService(): OpenaireBrokerTopicRestService { - return jasmine.createSpyObj('OpenaireBrokerTopicRestService', { - getTopics: jasmine.createSpy('getTopics'), - getTopic: jasmine.createSpy('getTopic'), - }); -} - -/** - * Mock for [[OpenaireBrokerEventRestService]] - */ -export function getMockOpenaireBrokerEventRestService(): OpenaireBrokerEventRestService { - return jasmine.createSpyObj('OpenaireBrokerEventRestService', { - getEventsByTopic: jasmine.createSpy('getEventsByTopic'), - getEvent: jasmine.createSpy('getEvent'), - patchEvent: jasmine.createSpy('patchEvent'), - boundProject: jasmine.createSpy('boundProject'), - removeProject: jasmine.createSpy('removeProject'), - clearFindByTopicRequests: jasmine.createSpy('.clearFindByTopicRequests') - }); -} - /** * Mock for [[OpenaireBrokerEventRestService]] */ diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts index 850a30da22..3d6ced5ef5 100644 --- a/src/app/suggestions-page/suggestions-page.component.ts +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -20,8 +20,8 @@ import { SuggestionTargetsStateService } from '../openaire/reciter-suggestions/s import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; import { PaginationService } from '../core/pagination/pagination.service'; import { WorkspaceItem } from '../core/submission/models/workspaceitem.model'; -import {FindListOptions} from "../core/data/find-list-options.model"; -import {redirectOn4xx} from "../core/shared/authorized.operators"; +import {FindListOptions} from '../core/data/find-list-options.model'; +import {redirectOn4xx} from '../core/shared/authorized.operators'; @Component({ selector: 'ds-suggestion-page', diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index fbc04033d6..f79cf799e5 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3033,6 +3033,7 @@ "reciter.suggestion.totalScore": "Total Score", + "reciter.suggestion.type.oaire": "OpenAIRE", "register-email.title": "New user registration", diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index 6446e7f127..fbd84fae84 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -14,7 +14,7 @@ import { AuthConfig } from './auth-config.interfaces'; import { UIServerConfig } from './ui-server-config.interface'; import { MediaViewerConfig } from './media-viewer-config.interface'; import { BrowseByConfig } from './browse-by-config.interface'; -import {SuggestionConfig} from "./layout-config.interfaces"; +import {SuggestionConfig} from './layout-config.interfaces'; interface AppConfig extends Config { ui: UIServerConfig; diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index aea29fc819..9def4849f5 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -14,7 +14,7 @@ import { ServerConfig } from './server-config.interface'; import { SubmissionConfig } from './submission-config.interface'; import { ThemeConfig } from './theme.model'; import { UIServerConfig } from './ui-server-config.interface'; -import {SuggestionConfig} from "./layout-config.interfaces"; +import {SuggestionConfig} from './layout-config.interfaces'; export class DefaultAppConfig implements AppConfig { production = false; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 7c24ef8f05..6bf3a9ab8f 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -2,6 +2,7 @@ import { BuildConfig } from 'src/config/build-config.interface'; import { RestRequestMethod } from '../app/core/data/rest-request-method'; import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; +import {SuggestionConfig} from '../config/layout-config.interfaces'; export const environment: BuildConfig = { production: false, @@ -203,6 +204,13 @@ export const environment: BuildConfig = { undoTimeout: 10000 // 10 seconds } }, + suggestion: [ + // { + // // Use this configuration to map a suggestion import to a specific collection based on the suggestion type. + // source: 'suggestionSource', + // collectionId: 'collectionUUID' + // } + ], themes: [ { name: 'full-item-page-theme', From 63bba677bbcffda2554b263ef38f06c5e31aae00 Mon Sep 17 00:00:00 2001 From: Luca Giamminonni Date: Thu, 7 Jul 2022 17:09:48 +0200 Subject: [PATCH 04/94] [CST-5249] Removed Openaire prefix on Suggestion classes --- .../admin-notifications-routing.module.ts | 1 + src/app/core/core.module.ts | 12 ++-- ...ts => suggestion-objects.resource-type.ts} | 0 ...ce.model.ts => suggestion-source.model.ts} | 4 +- ...et.model.ts => suggestion-target.model.ts} | 4 +- ...uggestion.model.ts => suggestion.model.ts} | 4 +- ...service.ts => suggestions-data.service.ts} | 68 +++++++++---------- .../mocks/reciter-suggestion-targets.mock.ts | 6 +- .../shared/mocks/reciter-suggestion.mock.ts | 8 +-- .../{openaire.mock.ts => suggestion.mock.ts} | 0 .../reciter-suggestions/selectors.ts | 10 +-- .../suggestion-actions.component.ts | 4 +- .../suggestion-evidences.component.ts | 2 +- .../suggestion-list-element.component.ts | 6 +- .../suggestion-targets.actions.ts | 10 +-- .../suggestion-targets.component.ts | 6 +- .../suggestion-targets.effects.ts | 8 +-- .../suggestion-targets.reducer.ts | 6 +- .../suggestion-targets.state.service.ts | 6 +- .../suggestions-notification.component.ts | 6 +- .../suggestions-popup.component.ts | 6 +- .../suggestions.service.ts | 42 ++++++------ src/app/suggestion-notifications/selectors.ts | 2 +- .../suggestion-notifications.module.ts | 6 +- .../suggestion-notifications.reducer.ts | 2 +- .../suggestions-page.component.spec.ts | 6 +- .../suggestions-page.component.ts | 26 +++---- .../suggestions-page.module.ts | 4 +- .../suggestions-page.resolver.ts | 10 +-- 29 files changed, 138 insertions(+), 137 deletions(-) rename src/app/core/suggestion-notifications/reciter-suggestions/models/{openaire-suggestion-objects.resource-type.ts => suggestion-objects.resource-type.ts} (100%) rename src/app/core/suggestion-notifications/reciter-suggestions/models/{openaire-suggestion-source.model.ts => suggestion-source.model.ts} (87%) rename src/app/core/suggestion-notifications/reciter-suggestions/models/{openaire-suggestion-target.model.ts => suggestion-target.model.ts} (89%) rename src/app/core/suggestion-notifications/reciter-suggestions/models/{openaire-suggestion.model.ts => suggestion.model.ts} (92%) rename src/app/core/suggestion-notifications/reciter-suggestions/{openaire-suggestions-data.service.ts => suggestions-data.service.ts} (79%) rename src/app/shared/mocks/{openaire.mock.ts => suggestion.mock.ts} (100%) diff --git a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts index ca6a8cf572..60fa679777 100644 --- a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts +++ b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts @@ -87,6 +87,7 @@ import { SourceDataResolver } from './admin-quality-assurance-source-page-compon I18nBreadcrumbsService, AdminNotificationsSuggestionTargetsPageResolver, SourceDataResolver, + AdminQualityAssuranceSourcePageResolver, AdminQualityAssuranceTopicsPageResolver, AdminQualityAssuranceEventsPageResolver, ] diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index bd82e0adc0..8986fa76ff 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -166,9 +166,9 @@ import { SearchConfig } from './shared/search/search-filters/search-config.model import { SequenceService } from './shared/sequence.service'; import { CoreState } from './core-state.model'; import { GroupDataService } from './eperson/group-data.service'; -import { OpenaireSuggestionTarget } from './suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; -import { OpenaireSuggestion } from './suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model'; -import { OpenaireSuggestionSource } from './suggestion-notifications/reciter-suggestions/models/openaire-suggestion-source.model'; +import { SuggestionTarget } from './suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { Suggestion } from './suggestion-notifications/reciter-suggestions/models/suggestion.model'; +import { SuggestionSource } from './suggestion-notifications/reciter-suggestions/models/suggestion-source.model'; import { ResearcherProfileService } from './profile/researcher-profile.service'; import { ProfileClaimService } from '../profile-page/profile-claim/profile-claim.service'; import { ResearcherProfile } from './profile/model/researcher-profile.model'; @@ -372,9 +372,9 @@ export const models = ShortLivedToken, Registration, UsageReport, - OpenaireSuggestion, - OpenaireSuggestionTarget, - OpenaireSuggestionSource, + Suggestion, + SuggestionTarget, + SuggestionSource, QualityAssuranceTopicObject, QualityAssuranceEventObject, Root, diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-objects.resource-type.ts b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type.ts similarity index 100% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-objects.resource-type.ts rename to src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type.ts diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-source.model.ts b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts similarity index 87% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-source.model.ts rename to src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts index 00f5f11936..64ddf9863c 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-source.model.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts @@ -1,6 +1,6 @@ import { autoserialize, deserialize } from 'cerialize'; -import { SUGGESTION_SOURCE } from './openaire-suggestion-objects.resource-type'; +import { SUGGESTION_SOURCE } from './suggestion-objects.resource-type'; import { excludeFromEquals } from '../../../utilities/equals.decorators'; import { ResourceType } from '../../../shared/resource-type'; import { HALLink } from '../../../shared/hal-link.model'; @@ -11,7 +11,7 @@ import {CacheableObject} from '../../../cache/cacheable-object.model'; * The interface representing the Suggestion Source model */ @typedObject -export class OpenaireSuggestionSource implements CacheableObject { +export class SuggestionSource implements CacheableObject { /** * A string representing the kind of object, e.g. community, item, … */ diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model.ts b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts similarity index 89% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model.ts rename to src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts index 96a43c9654..fa23dd0ffa 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts @@ -1,6 +1,6 @@ import { autoserialize, deserialize } from 'cerialize'; -import { SUGGESTION_TARGET } from './openaire-suggestion-objects.resource-type'; +import { SUGGESTION_TARGET } from './suggestion-objects.resource-type'; import { excludeFromEquals } from '../../../utilities/equals.decorators'; import { ResourceType } from '../../../shared/resource-type'; import { HALLink } from '../../../shared/hal-link.model'; @@ -11,7 +11,7 @@ import {CacheableObject} from '../../../cache/cacheable-object.model'; * The interface representing the Suggestion Target model */ @typedObject -export class OpenaireSuggestionTarget implements CacheableObject { +export class SuggestionTarget implements CacheableObject { /** * A string representing the kind of object, e.g. community, item, … */ diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model.ts b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion.model.ts similarity index 92% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model.ts rename to src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion.model.ts index 0f84072c6b..c36d36794b 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion.model.ts @@ -1,6 +1,6 @@ import { autoserialize, autoserializeAs, deserialize } from 'cerialize'; -import { SUGGESTION } from './openaire-suggestion-objects.resource-type'; +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'; @@ -18,7 +18,7 @@ export interface SuggestionEvidences { * The interface representing the Suggestion Source model */ @typedObject -export class OpenaireSuggestion implements CacheableObject { +export class Suggestion implements CacheableObject { /** * A string representing the kind of object, e.g. community, item, … */ diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/openaire-suggestions-data.service.ts b/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts similarity index 79% rename from src/app/core/suggestion-notifications/reciter-suggestions/openaire-suggestions-data.service.ts rename to src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts index 3c1d04c040..880c7b2f33 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/openaire-suggestions-data.service.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts @@ -15,12 +15,12 @@ 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 { SUGGESTION_TARGET } from './models/openaire-suggestion-objects.resource-type'; +import { SUGGESTION_TARGET } from './models/suggestion-objects.resource-type'; import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model'; import { PaginatedList } from '../../data/paginated-list.model'; -import { OpenaireSuggestionSource } from './models/openaire-suggestion-source.model'; -import { OpenaireSuggestionTarget } from './models/openaire-suggestion-target.model'; -import { OpenaireSuggestion } from './models/openaire-suggestion.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'; @@ -31,7 +31,7 @@ import {FindListOptions} from '../../data/find-list-options.model'; /** * A private DataService implementation to delegate specific methods to. */ -class SuggestionDataServiceImpl extends DataService { +class SuggestionDataServiceImpl extends DataService { /** * The REST endpoint. */ @@ -46,7 +46,7 @@ class SuggestionDataServiceImpl extends DataService { * @param {HALEndpointService} halService * @param {NotificationsService} notificationsService * @param {HttpClient} http - * @param {ChangeAnalyzer} comparator + * @param {ChangeAnalyzer} comparator */ constructor( protected requestService: RequestService, @@ -56,7 +56,7 @@ class SuggestionDataServiceImpl extends DataService { protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: ChangeAnalyzer) { + protected comparator: ChangeAnalyzer) { super(); } } @@ -64,7 +64,7 @@ class SuggestionDataServiceImpl extends DataService { /** * A private DataService implementation to delegate specific methods to. */ -class SuggestionTargetsDataServiceImpl extends DataService { +class SuggestionTargetsDataServiceImpl extends DataService { /** * The REST endpoint. */ @@ -79,7 +79,7 @@ class SuggestionTargetsDataServiceImpl extends DataService} comparator + * @param {ChangeAnalyzer} comparator */ constructor( protected requestService: RequestService, @@ -89,7 +89,7 @@ class SuggestionTargetsDataServiceImpl extends DataService) { + protected comparator: ChangeAnalyzer) { super(); } } @@ -97,7 +97,7 @@ class SuggestionTargetsDataServiceImpl extends DataService { +class SuggestionSourcesDataServiceImpl extends DataService { /** * The REST endpoint. */ @@ -112,7 +112,7 @@ class SuggestionSourcesDataServiceImpl extends DataService} comparator + * @param {ChangeAnalyzer} comparator */ constructor( protected requestService: RequestService, @@ -122,7 +122,7 @@ class SuggestionSourcesDataServiceImpl extends DataService) { + protected comparator: ChangeAnalyzer) { super(); } } @@ -132,7 +132,7 @@ class SuggestionSourcesDataServiceImpl extends DataService} comparatorSuggestions - * @param {DefaultChangeAnalyzer} comparatorSources - * @param {DefaultChangeAnalyzer} comparatorTargets + * @param {DefaultChangeAnalyzer} comparatorSuggestions + * @param {DefaultChangeAnalyzer} comparatorSources + * @param {DefaultChangeAnalyzer} comparatorTargets */ constructor( protected requestService: RequestService, @@ -171,9 +171,9 @@ export class OpenaireSuggestionsDataService { protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparatorSuggestions: DefaultChangeAnalyzer, - protected comparatorSources: DefaultChangeAnalyzer, - protected comparatorTargets: DefaultChangeAnalyzer, + protected comparatorSuggestions: DefaultChangeAnalyzer, + protected comparatorSources: DefaultChangeAnalyzer, + protected comparatorTargets: DefaultChangeAnalyzer, ) { this.suggestionsDataService = new SuggestionDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSuggestions); this.suggestionSourcesDataService = new SuggestionSourcesDataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparatorSources); @@ -185,10 +185,10 @@ export class OpenaireSuggestionsDataService { * * @param options * Find list options object. - * @return Observable>> + * @return Observable>> * The list of Suggestion Sources. */ - public getSources(options: FindListOptions = {}): Observable>> { + public getSources(options: FindListOptions = {}): Observable>> { return this.suggestionSourcesDataService.findAll(options); } @@ -201,14 +201,14 @@ export class OpenaireSuggestionsDataService { * Find list options object. * @param linksToFollow * List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved. - * @return Observable>> + * @return Observable>> * The list of Suggestion Target. */ public getTargets( source: string, options: FindListOptions = {}, - ...linksToFollow: FollowLinkConfig[] - ): Observable>> { + ...linksToFollow: FollowLinkConfig[] + ): Observable>> { options.searchParams = [new RequestParam('source', source)]; return this.suggestionTargetsDataService.searchBy(this.searchFindBySourceMethod, options, true, true, ...linksToFollow); @@ -223,14 +223,14 @@ export class OpenaireSuggestionsDataService { * Find list options object. * @param linksToFollow * List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved. - * @return Observable>> + * @return Observable>> * The list of Suggestion Target. */ public getTargetsByUser( userId: string, options: FindListOptions = {}, - ...linksToFollow: FollowLinkConfig[] - ): Observable>> { + ...linksToFollow: FollowLinkConfig[] + ): Observable>> { options.searchParams = [new RequestParam('target', userId)]; return this.suggestionTargetsDataService.searchBy(this.searchFindByTargetMethod, options, true, true, ...linksToFollow); @@ -242,10 +242,10 @@ export class OpenaireSuggestionsDataService { * @param targetId * The target id to retrieve. * - * @return Observable> + * @return Observable> * The list of Suggestion Target. */ - public getTargetById(targetId: string): Observable> { + public getTargetById(targetId: string): Observable> { return this.suggestionTargetsDataService.findById(targetId); } @@ -261,7 +261,7 @@ export class OpenaireSuggestionsDataService { * Used to fetch Suggestion notification for user * @suggestionId */ - public getSuggestion(suggestionId: string, ...linksToFollow: FollowLinkConfig[]): Observable> { + public getSuggestion(suggestionId: string, ...linksToFollow: FollowLinkConfig[]): Observable> { return this.suggestionsDataService.findById(suggestionId, true, true, ...linksToFollow); } @@ -276,15 +276,15 @@ export class OpenaireSuggestionsDataService { * Find list options object. * @param linksToFollow * List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved. - * @return Observable>> + * @return Observable>> * The list of Suggestion. */ public getSuggestionsByTargetAndSource( target: string, source: string, options: FindListOptions = {}, - ...linksToFollow: FollowLinkConfig[] - ): Observable>> { + ...linksToFollow: FollowLinkConfig[] + ): Observable>> { options.searchParams = [ new RequestParam('target', target), new RequestParam('source', source) diff --git a/src/app/shared/mocks/reciter-suggestion-targets.mock.ts b/src/app/shared/mocks/reciter-suggestion-targets.mock.ts index ae8cacb273..489bb7733d 100644 --- a/src/app/shared/mocks/reciter-suggestion-targets.mock.ts +++ b/src/app/shared/mocks/reciter-suggestion-targets.mock.ts @@ -1,9 +1,9 @@ import { ResourceType } from '../../core/shared/resource-type'; -import { OpenaireSuggestionTarget } from '../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; // REST Mock --------------------------------------------------------------------- // ------------------------------------------------------------------------------- -export const mockSuggestionTargetsObjectOne: OpenaireSuggestionTarget = { +export const mockSuggestionTargetsObjectOne: SuggestionTarget = { type: new ResourceType('suggestiontarget'), id: 'reciter:gf3d657-9d6d-4a87-b905-fef0f8cae26', display: 'Bollini, Andrea', @@ -22,7 +22,7 @@ export const mockSuggestionTargetsObjectOne: OpenaireSuggestionTarget = { } }; -export const mockSuggestionTargetsObjectTwo: OpenaireSuggestionTarget = { +export const mockSuggestionTargetsObjectTwo: SuggestionTarget = { type: new ResourceType('suggestiontarget'), id: 'reciter:nhy567-9d6d-ty67-b905-fef0f8cae26', display: 'Digilio, Andrea', diff --git a/src/app/shared/mocks/reciter-suggestion.mock.ts b/src/app/shared/mocks/reciter-suggestion.mock.ts index ae1732808e..aceca72fc8 100644 --- a/src/app/shared/mocks/reciter-suggestion.mock.ts +++ b/src/app/shared/mocks/reciter-suggestion.mock.ts @@ -2,10 +2,10 @@ // REST Mock --------------------------------------------------------------------- // ------------------------------------------------------------------------------- -import { OpenaireSuggestion } from '../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model'; -import { SUGGESTION } from '../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-objects.resource-type'; +import { Suggestion } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; +import { SUGGESTION } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type'; -export const mockSuggestionPublicationOne: OpenaireSuggestion = { +export const mockSuggestionPublicationOne: Suggestion = { id: '24694772', display: 'publication one', source: 'reciter', @@ -107,7 +107,7 @@ export const mockSuggestionPublicationOne: OpenaireSuggestion = { } }; -export const mockSuggestionPublicationTwo: OpenaireSuggestion = { +export const mockSuggestionPublicationTwo: Suggestion = { id: '24694772', display: 'publication two', source: 'reciter', diff --git a/src/app/shared/mocks/openaire.mock.ts b/src/app/shared/mocks/suggestion.mock.ts similarity index 100% rename from src/app/shared/mocks/openaire.mock.ts rename to src/app/shared/mocks/suggestion.mock.ts diff --git a/src/app/suggestion-notifications/reciter-suggestions/selectors.ts b/src/app/suggestion-notifications/reciter-suggestions/selectors.ts index 6ba474bfd8..aa81ab4bc8 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/selectors.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/selectors.ts @@ -1,6 +1,6 @@ import {createFeatureSelector, createSelector, MemoizedSelector} from '@ngrx/store'; import { suggestionNotificationsSelector, SuggestionNotificationsState } from '../suggestion-notifications.reducer'; -import { OpenaireSuggestionTarget } from '../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; import { SuggestionTargetState } from './suggestion-targets/suggestion-targets.reducer'; import {subStateSelector} from '../../submission/selectors'; @@ -10,7 +10,7 @@ import {subStateSelector} from '../../submission/selectors'; * @param {AppState} state Top level state. * @return {SuggestionNotificationsState} */ -const _getReciterSuggestionTargetState = createFeatureSelector('openaire'); +const _getReciterSuggestionTargetState = createFeatureSelector('suggestion-notifications'); // Reciter Suggestion Targets // ---------------------------------------------------------------------------- @@ -29,8 +29,8 @@ export function reciterSuggestionTargetStateSelector(): MemoizedSelector { - return subStateSelector(reciterSuggestionTargetStateSelector(), 'targets'); +export function reciterSuggestionTargetObjectSelector(): MemoizedSelector { + return subStateSelector(reciterSuggestionTargetStateSelector(), 'targets'); } /** @@ -81,7 +81,7 @@ export const getreciterSuggestionTargetTotalsSelector = createSelector(_getRecit /** * Returns Suggestion Targets for the current user. * @function getCurrentUserReciterSuggestionTargetSelector - * @return {OpenaireSuggestionTarget[]} + * @return {SuggestionTarget[]} */ export const getCurrentUserSuggestionTargetsSelector = createSelector(_getReciterSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.currentUserTargets diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.ts index 7a3312af0c..35599669ef 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { ItemType } from '../../../core/shared/item-relationships/item-type.model'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { OpenaireSuggestion } from '../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model'; +import { Suggestion } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; import { SuggestionApproveAndImport } from '../suggestion-list-element/suggestion-list-element.component'; import { Collection } from '../../../core/shared/collection.model'; import { take } from 'rxjs/operators'; @@ -14,7 +14,7 @@ import { CreateItemParentSelectorComponent } from '../../../shared/dso-selector/ }) export class SuggestionActionsComponent { - @Input() object: OpenaireSuggestion; + @Input() object: Suggestion; @Input() isBulk = false; diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts index 7bd5fc6e1b..431749eaa5 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts @@ -1,6 +1,6 @@ import { Component, Input } from '@angular/core'; import { fadeIn } from '../../../../shared/animations/fade'; -import { SuggestionEvidences } from '../../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model'; +import { SuggestionEvidences } from '../../../../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; @Component({ selector: 'ds-suggestion-evidences', diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts index ca531655fc..a1ef454037 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts @@ -3,12 +3,12 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { fadeIn } from '../../../shared/animations/fade'; -import { OpenaireSuggestion } from '../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model'; +import { Suggestion } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; import { Item } from '../../../core/shared/item.model'; import { isNotEmpty } from '../../../shared/empty.util'; export interface SuggestionApproveAndImport { - suggestion: OpenaireSuggestion; + suggestion: Suggestion; collectionId: string; } @@ -20,7 +20,7 @@ export interface SuggestionApproveAndImport { }) export class SuggestionListElementComponent implements OnInit { - @Input() object: OpenaireSuggestion; + @Input() object: Suggestion; @Input() isSelected = false; diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts index 627b815554..09e4f6e20c 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts @@ -1,7 +1,7 @@ /* eslint-disable max-classes-per-file */ import { Action } from '@ngrx/store'; import { type } from '../../../shared/ngrx/type'; -import { OpenaireSuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; /** * For each action type in an action group, make a simple @@ -66,7 +66,7 @@ export class RetrieveAllTargetsErrorAction implements Action { export class AddTargetAction implements Action { type = SuggestionTargetActionTypes.ADD_TARGETS; payload: { - targets: OpenaireSuggestionTarget[]; + targets: SuggestionTarget[]; totalPages: number; currentPage: number; totalElements: number; @@ -84,7 +84,7 @@ export class AddTargetAction implements Action { * @param totalElements * the total available Suggestion Targets */ - constructor(targets: OpenaireSuggestionTarget[], totalPages: number, currentPage: number, totalElements: number) { + constructor(targets: SuggestionTarget[], totalPages: number, currentPage: number, totalElements: number) { this.payload = { targets, totalPages, @@ -102,7 +102,7 @@ export class AddTargetAction implements Action { export class AddUserSuggestionsAction implements Action { type = SuggestionTargetActionTypes.ADD_USER_SUGGESTIONS; payload: { - suggestionTargets: OpenaireSuggestionTarget[]; + suggestionTargets: SuggestionTarget[]; }; /** @@ -111,7 +111,7 @@ export class AddUserSuggestionsAction implements Action { * @param suggestionTargets * the user suggestions target */ - constructor(suggestionTargets: OpenaireSuggestionTarget[]) { + constructor(suggestionTargets: SuggestionTarget[]) { this.payload = { suggestionTargets }; } diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts index 7cd882d01e..cfa168e2b8 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts @@ -4,7 +4,7 @@ import { Router } from '@angular/router'; import { Observable, Subscription } from 'rxjs'; import { distinctUntilChanged, take } from 'rxjs/operators'; -import { OpenaireSuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; import { hasValue } from '../../../shared/empty.util'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { SuggestionTargetsStateService } from './suggestion-targets.state.service'; @@ -39,7 +39,7 @@ export class SuggestionTargetsComponent implements OnInit { /** * The Suggestion Target list. */ - public targets$: Observable; + public targets$: Observable; /** * The total number of Suggestion Targets. */ @@ -144,7 +144,7 @@ export class SuggestionTargetsComponent implements OnInit { }); } - public getTargetUuid(target: OpenaireSuggestionTarget) { + public getTargetUuid(target: SuggestionTarget) { return this.suggestionService.getTargetUuid(target); } } diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts index 01b305bbd4..9c595a1e0d 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts @@ -18,7 +18,7 @@ import { PaginatedList } from '../../../core/data/paginated-list.model'; import { SuggestionsService } from '../suggestions.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { AuthActionTypes, RetrieveAuthenticatedEpersonSuccessAction } from '../../../core/auth/auth.actions'; -import { OpenaireSuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; import { EPerson } from '../../../core/eperson/models/eperson.model'; /** @@ -38,7 +38,7 @@ export class SuggestionTargetsEffects { action.payload.elementsPerPage, action.payload.currentPage ).pipe( - map((targets: PaginatedList) => + map((targets: PaginatedList) => new AddTargetAction(targets.page, targets.totalPages, targets.currentPage, targets.totalElements) ), catchError((error: Error) => { @@ -68,7 +68,7 @@ export class SuggestionTargetsEffects { ofType(AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS), switchMap((action: RetrieveAuthenticatedEpersonSuccessAction) => { return this.suggestionsService.retrieveCurrentUserSuggestions(action.payload).pipe( - map((suggestionTargets: OpenaireSuggestionTarget[]) => new AddUserSuggestionsAction(suggestionTargets)) + map((suggestionTargets: SuggestionTarget[]) => new AddUserSuggestionsAction(suggestionTargets)) ); })); @@ -83,7 +83,7 @@ export class SuggestionTargetsEffects { switchMap((userId: string) => { return this.suggestionsService.retrieveCurrentUserSuggestions(userId) .pipe( - map((suggestionTargets: OpenaireSuggestionTarget[]) => new AddUserSuggestionsAction(suggestionTargets)), + map((suggestionTargets: SuggestionTarget[]) => new AddUserSuggestionsAction(suggestionTargets)), catchError((errors) => of(errors)) ); }), diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts index b0b551dfa9..2d315e6995 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts @@ -1,17 +1,17 @@ import { SuggestionTargetActionTypes, SuggestionTargetsActions } from './suggestion-targets.actions'; -import { OpenaireSuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; /** * The interface representing the OpenAIRE suggestion targets state. */ export interface SuggestionTargetState { - targets: OpenaireSuggestionTarget[]; + targets: SuggestionTarget[]; processing: boolean; loaded: boolean; totalPages: number; currentPage: number; totalElements: number; - currentUserTargets: OpenaireSuggestionTarget[]; + currentUserTargets: SuggestionTarget[]; currentUserTargetsVisited: boolean; } diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts index 164750d221..97792df6af 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts @@ -13,7 +13,7 @@ import { isreciterSuggestionTargetProcessingSelector, reciterSuggestionTargetObjectSelector } from '../selectors'; -import { OpenaireSuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; import { ClearSuggestionTargetsAction, MarkUserSuggestionsAsVisitedAction, @@ -40,7 +40,7 @@ export class SuggestionTargetsStateService { * @return Observable * The list of Reciter Suggestion Targets. */ - public getReciterSuggestionTargets(): Observable { + public getReciterSuggestionTargets(): Observable { return this.store.pipe(select(reciterSuggestionTargetObjectSelector())); } @@ -127,7 +127,7 @@ export class SuggestionTargetsStateService { * @return Observable * The Reciter Suggestion Targets object. */ - public getCurrentUserSuggestionTargets(): Observable { + public getCurrentUserSuggestionTargets(): Observable { return this.store.pipe(select(getCurrentUserSuggestionTargetsSelector)); } diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts index 19f94c433d..23b34c4de9 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { OpenaireSuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; import { TranslateService } from '@ngx-translate/core'; import { SuggestionTargetsStateService } from '../suggestion-targets/suggestion-targets.state.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -18,7 +18,7 @@ export class SuggestionsNotificationComponent implements OnInit { /** * The user suggestion targets. */ - suggestionsRD$: Observable; + suggestionsRD$: Observable; constructor( private translateService: TranslateService, @@ -35,7 +35,7 @@ export class SuggestionsNotificationComponent implements OnInit { * Interpolated params to build the notification suggestions notification. * @param suggestionTarget */ - public getNotificationSuggestionInterpolation(suggestionTarget: OpenaireSuggestionTarget): any { + public getNotificationSuggestionInterpolation(suggestionTarget: SuggestionTarget): any { return this.suggestionsService.getNotificationSuggestionInterpolation(suggestionTarget); } diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts index 9478ed2d1d..0195f074ae 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts @@ -4,7 +4,7 @@ import { SuggestionTargetsStateService } from '../suggestion-targets/suggestion- import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { SuggestionsService } from '../suggestions.service'; import { takeUntil } from 'rxjs/operators'; -import { OpenaireSuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; import { isNotEmpty } from '../../../shared/empty.util'; import { combineLatest, Subject } from 'rxjs'; @@ -38,7 +38,7 @@ export class SuggestionsPopupComponent implements OnInit, OnDestroy { ]).pipe(takeUntil(notifier)).subscribe(([suggestions, visited]) => { if (isNotEmpty(suggestions)) { if (!visited) { - suggestions.forEach((suggestionTarget: OpenaireSuggestionTarget) => this.showNotificationForNewSuggestions(suggestionTarget)); + suggestions.forEach((suggestionTarget: SuggestionTarget) => this.showNotificationForNewSuggestions(suggestionTarget)); this.reciterSuggestionStateService.dispatchMarkUserSuggestionsAsVisitedAction(); notifier.next(null); notifier.complete(); @@ -52,7 +52,7 @@ export class SuggestionsPopupComponent implements OnInit, OnDestroy { * @param suggestionTarget * @private */ - private showNotificationForNewSuggestions(suggestionTarget: OpenaireSuggestionTarget): void { + 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); diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts index c4a2fa4b4b..be43285df2 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts @@ -3,11 +3,11 @@ import { Injectable } from '@angular/core'; import { of, forkJoin, Observable } from 'rxjs'; import { catchError, map, mergeMap, take } from 'rxjs/operators'; -import { OpenaireSuggestionsDataService } from '../../core/suggestion-notifications/reciter-suggestions/openaire-suggestions-data.service'; +import { SuggestionsDataService } from '../../core/suggestion-notifications/reciter-suggestions/suggestions-data.service'; 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 { OpenaireSuggestionTarget } from '../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionTarget } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; import { AuthService } from '../../core/auth/auth.service'; import { EPerson } from '../../core/eperson/models/eperson.model'; @@ -19,7 +19,7 @@ import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../../core/shared/operators'; -import { OpenaireSuggestion } from '../../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model'; +import { Suggestion } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service'; import { TranslateService } from '@ngx-translate/core'; import { NoContent } from '../../core/shared/NoContent.model'; @@ -43,12 +43,12 @@ export class SuggestionsService { * Initialize the service variables. * @param {AuthService} authService * @param {ResearcherProfileService} researcherProfileService - * @param {OpenaireSuggestionsDataService} suggestionsDataService + * @param {SuggestionsDataService} suggestionsDataService */ constructor( private authService: AuthService, private researcherProfileService: ResearcherProfileService, - private suggestionsDataService: OpenaireSuggestionsDataService, + private suggestionsDataService: SuggestionsDataService, private translateService: TranslateService ) { } @@ -65,7 +65,7 @@ export class SuggestionsService { * @return Observable> * The list of Suggestion Targets. */ - public getTargets(source, elementsPerPage, currentPage): Observable> { + public getTargets(source, elementsPerPage, currentPage): Observable> { const sortOptions = new SortOptions('display', SortDirection.ASC); const findListOptions: FindListOptions = { @@ -77,7 +77,7 @@ export class SuggestionsService { return this.suggestionsDataService.getTargets(source, findListOptions).pipe( getFinishedRemoteData(), take(1), - map((rd: RemoteData>) => { + map((rd: RemoteData>) => { if (rd.hasSucceeded) { return rd.payload; } else { @@ -98,10 +98,10 @@ export class SuggestionsService { * The page number to retrieve * @param sortOptions * The sort options - * @return Observable>> + * @return Observable>> * The list of Suggestion. */ - public getSuggestions(targetId: string, elementsPerPage, currentPage, sortOptions: SortOptions): Observable> { + public getSuggestions(targetId: string, elementsPerPage, currentPage, sortOptions: SortOptions): Observable> { const [source, target] = targetId.split(':'); const findListOptions: FindListOptions = { @@ -145,7 +145,7 @@ export class SuggestionsService { * @param userUuid * The EPerson id for which to retrieve suggestion targets */ - public retrieveCurrentUserSuggestions(userUuid: string): Observable { + public retrieveCurrentUserSuggestions(userUuid: string): Observable { return this.researcherProfileService.findById(userUuid).pipe( getFirstSucceededRemoteDataPayload(), mergeMap((profile: ResearcherProfile) => { @@ -173,7 +173,7 @@ export class SuggestionsService { * @private */ public approveAndImport(workspaceitemService: WorkspaceitemDataService, - suggestion: OpenaireSuggestion, + suggestion: Suggestion, collectionId: string): Observable { const resolvedCollectionId = this.resolveCollectionId(suggestion, collectionId); @@ -201,10 +201,10 @@ export class SuggestionsService { * @param collectionId the collectionId */ public approveAndImportMultiple(workspaceitemService: WorkspaceitemDataService, - suggestions: OpenaireSuggestion[], + suggestions: Suggestion[], collectionId: string): Observable { - return forkJoin(suggestions.map((suggestion: OpenaireSuggestion) => + return forkJoin(suggestions.map((suggestion: Suggestion) => this.approveAndImport(workspaceitemService, suggestion, collectionId))) .pipe(map((results: WorkspaceItem[]) => { return { @@ -218,8 +218,8 @@ export class SuggestionsService { * Perform a bulk notMine operation. * @param suggestions the array containing the suggestions */ - public notMineMultiple(suggestions: OpenaireSuggestion[]): Observable { - return forkJoin(suggestions.map((suggestion: OpenaireSuggestion) => this.notMine(suggestion.id))) + public notMineMultiple(suggestions: Suggestion[]): Observable { + return forkJoin(suggestions.map((suggestion: Suggestion) => this.notMine(suggestion.id))) .pipe(map((results: RemoteData[]) => { return { success: results.filter((result) => result != null).length, @@ -234,7 +234,7 @@ export class SuggestionsService { * @param target * @return the researchUuid */ - public getTargetUuid(target: OpenaireSuggestionTarget): string { + public getTargetUuid(target: SuggestionTarget): string { const tokens = target.id.split(':'); return tokens.length === 2 ? tokens[1] : null; } @@ -243,7 +243,7 @@ export class SuggestionsService { * Interpolated params to build the notification suggestions notification. * @param suggestionTarget */ - public getNotificationSuggestionInterpolation(suggestionTarget: OpenaireSuggestionTarget): any { + public getNotificationSuggestionInterpolation(suggestionTarget: SuggestionTarget): any { return { count: suggestionTarget.total, source: this.translateService.instant(this.translateSuggestionSource(suggestionTarget.source)), @@ -266,7 +266,7 @@ export class SuggestionsService { * @param suggestion * @param collectionId */ - public resolveCollectionId(suggestion: OpenaireSuggestion, collectionId): string { + public resolveCollectionId(suggestion: Suggestion, collectionId): string { if (hasValue(collectionId)) { return collectionId; } @@ -280,13 +280,13 @@ export class SuggestionsService { * in the configuration. * @param suggestions */ - public isCollectionFixed(suggestions: OpenaireSuggestion[]): boolean { + public isCollectionFixed(suggestions: Suggestion[]): boolean { return this.getFixedCollectionIds(suggestions).length === 1; } - private getFixedCollectionIds(suggestions: OpenaireSuggestion[]): string[] { + private getFixedCollectionIds(suggestions: Suggestion[]): string[] { const collectionIds = {}; - suggestions.forEach((suggestion: OpenaireSuggestion) => { + suggestions.forEach((suggestion: Suggestion) => { const conf = environment.suggestion.find((suggestionConf: SuggestionConfig) => suggestionConf.source === suggestion.source); if (hasValue(conf)) { collectionIds[conf.collectionId] = true; diff --git a/src/app/suggestion-notifications/selectors.ts b/src/app/suggestion-notifications/selectors.ts index c5947e3196..a20e900025 100644 --- a/src/app/suggestion-notifications/selectors.ts +++ b/src/app/suggestion-notifications/selectors.ts @@ -12,7 +12,7 @@ import { QualityAssuranceSourceObject } from '../core/suggestion-notifications/q * @param {AppState} state Top level state. * @return {SuggestionNotificationsState} */ -const _getNotificationsState = createFeatureSelector('notifications'); +const _getNotificationsState = createFeatureSelector('suggestion-notifications'); // Quality Assurance topics // ---------------------------------------------------------------------------- diff --git a/src/app/suggestion-notifications/suggestion-notifications.module.ts b/src/app/suggestion-notifications/suggestion-notifications.module.ts index b69427d70d..2b2e41ece3 100644 --- a/src/app/suggestion-notifications/suggestion-notifications.module.ts +++ b/src/app/suggestion-notifications/suggestion-notifications.module.ts @@ -34,13 +34,13 @@ import {SuggestionActionsComponent} from './reciter-suggestions/suggestion-actio import {SuggestionTargetsComponent} from './reciter-suggestions/suggestion-targets/suggestion-targets.component'; import {SuggestionTargetsStateService} from './reciter-suggestions/suggestion-targets/suggestion-targets.state.service'; import {SuggestionsService} from './reciter-suggestions/suggestions.service'; -import {OpenaireSuggestionsDataService} from '../core/suggestion-notifications/reciter-suggestions/openaire-suggestions-data.service'; +import {SuggestionsDataService} from '../core/suggestion-notifications/reciter-suggestions/suggestions-data.service'; const MODULES = [ CommonModule, SharedModule, CoreModule.forRoot(), - StoreModule.forFeature('notifications', suggestionNotificationsReducers, storeModuleConfig as StoreConfig), + StoreModule.forFeature('suggestion-notifications', suggestionNotificationsReducers, storeModuleConfig as StoreConfig), EffectsModule.forFeature(suggestionNotificationsEffects), TranslateModule ]; @@ -72,7 +72,7 @@ const PROVIDERS = [ QualityAssuranceEventRestService, SuggestionTargetsStateService, SuggestionsService, - OpenaireSuggestionsDataService + SuggestionsDataService ]; @NgModule({ diff --git a/src/app/suggestion-notifications/suggestion-notifications.reducer.ts b/src/app/suggestion-notifications/suggestion-notifications.reducer.ts index cdd072bb39..bab0984304 100644 --- a/src/app/suggestion-notifications/suggestion-notifications.reducer.ts +++ b/src/app/suggestion-notifications/suggestion-notifications.reducer.ts @@ -21,4 +21,4 @@ export const suggestionNotificationsReducers: ActionReducerMap('notifications'); +export const suggestionNotificationsSelector = createFeatureSelector('suggestion-notifications'); diff --git a/src/app/suggestions-page/suggestions-page.component.spec.ts b/src/app/suggestions-page/suggestions-page.component.spec.ts index aef9332605..61e66a99cf 100644 --- a/src/app/suggestions-page/suggestions-page.component.spec.ts +++ b/src/app/suggestions-page/suggestions-page.component.spec.ts @@ -9,9 +9,9 @@ import { of as observableOf } from 'rxjs'; import { SuggestionsPageComponent } from './suggestions-page.component'; import { SuggestionListElementComponent } from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; import { SuggestionsService } from '../suggestion-notifications/reciter-suggestions/suggestions.service'; -import { getMockSuggestionNotificationsStateService, getMockSuggestionsService } from '../shared/mocks/openaire.mock'; +import { getMockSuggestionNotificationsStateService, getMockSuggestionsService } from '../shared/mocks/suggestion.mock'; import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model'; -import { OpenaireSuggestion } from '../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model'; +import { Suggestion } from '../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; import { mockSuggestionPublicationOne, mockSuggestionPublicationTwo } from '../shared/mocks/reciter-suggestion.mock'; import { SuggestionEvidencesComponent } from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component'; import { ObjectKeysPipe } from '../shared/utils/object-keys-pipe'; @@ -38,7 +38,7 @@ describe('SuggestionPageComponent', () => { let scheduler: TestScheduler; const mockSuggestionsService = getMockSuggestionsService(); const mockSuggestionsTargetStateService = getMockSuggestionNotificationsStateService(); - const suggestionTargetsList: PaginatedList = buildPaginatedList(new PageInfo(), [mockSuggestionPublicationOne, mockSuggestionPublicationTwo]); + const suggestionTargetsList: PaginatedList = buildPaginatedList(new PageInfo(), [mockSuggestionPublicationOne, mockSuggestionPublicationTwo]); const router = new RouterStub(); const routeStub = { data: observableOf({ diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts index e8e76ff606..b4ca0108d5 100644 --- a/src/app/suggestions-page/suggestions-page.component.ts +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -11,8 +11,8 @@ import { RemoteData } from '../core/data/remote-data'; import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; import { SuggestionBulkResult, SuggestionsService } from '../suggestion-notifications/reciter-suggestions/suggestions.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { OpenaireSuggestion } from '../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion.model'; -import { OpenaireSuggestionTarget } from '../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { Suggestion } from '../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; +import { SuggestionTarget } from '../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; import { AuthService } from '../core/auth/auth.service'; import { SuggestionApproveAndImport } from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; import { NotificationsService } from '../shared/notifications/notifications.service'; @@ -56,18 +56,18 @@ export class SuggestionsPageComponent implements OnInit { /** * A list of remote data objects of suggestions */ - suggestionsRD$: BehaviorSubject> = new BehaviorSubject>({} as any); + suggestionsRD$: BehaviorSubject> = new BehaviorSubject>({} as any); - targetRD$: Observable>; + targetRD$: Observable>; targetId$: Observable; - suggestionTarget: OpenaireSuggestionTarget; + suggestionTarget: SuggestionTarget; suggestionId: any; suggestionSource: any; researcherName: any; researcherUuid: any; - selectedSuggestions: { [id: string]: OpenaireSuggestion } = {}; + selectedSuggestions: { [id: string]: Suggestion } = {}; isBulkOperationPending = false; constructor( @@ -85,17 +85,17 @@ export class SuggestionsPageComponent implements OnInit { ngOnInit(): void { this.targetRD$ = this.route.data.pipe( - map((data: Data) => data.suggestionTargets as RemoteData), + map((data: Data) => data.suggestionTargets as RemoteData), redirectOn4xx(this.router, this.authService) ); this.targetId$ = this.targetRD$.pipe( getFirstSucceededRemoteDataPayload(), - map((target: OpenaireSuggestionTarget) => target.id) + map((target: SuggestionTarget) => target.id) ); this.targetRD$.pipe( getFirstSucceededRemoteDataPayload() - ).subscribe((suggestionTarget: OpenaireSuggestionTarget) => { + ).subscribe((suggestionTarget: SuggestionTarget) => { this.suggestionTarget = suggestionTarget; this.suggestionId = suggestionTarget.id; this.researcherName = suggestionTarget.display; @@ -135,7 +135,7 @@ export class SuggestionsPageComponent implements OnInit { ); }), take(1) - ).subscribe((results: PaginatedList) => { + ).subscribe((results: PaginatedList) => { this.processing$.next(false); this.suggestionsRD$.next(results); this.suggestionService.clearSuggestionRequests(); @@ -232,7 +232,7 @@ export class SuggestionsPageComponent implements OnInit { * @param object the suggestions * @param selected the new selected value for the suggestion */ - onSelected(object: OpenaireSuggestion, selected: boolean) { + onSelected(object: Suggestion, selected: boolean) { if (selected) { this.selectedSuggestions[object.id] = object; } else { @@ -244,7 +244,7 @@ export class SuggestionsPageComponent implements OnInit { * When Toggle Select All occurs. * @param suggestions all the visible suggestions inside the page */ - onToggleSelectAll(suggestions: OpenaireSuggestion[]) { + onToggleSelectAll(suggestions: Suggestion[]) { if ( this.getSelectedSuggestionsCount() > 0) { this.selectedSuggestions = {}; } else { @@ -265,7 +265,7 @@ export class SuggestionsPageComponent implements OnInit { * Return true if all the suggestion are configured with the same fixed collection in the configuration. * @param suggestions */ - isCollectionFixed(suggestions: OpenaireSuggestion[]): boolean { + isCollectionFixed(suggestions: Suggestion[]): boolean { return this.suggestionService.isCollectionFixed(suggestions); } diff --git a/src/app/suggestions-page/suggestions-page.module.ts b/src/app/suggestions-page/suggestions-page.module.ts index 726af26c0f..a3f24f3468 100644 --- a/src/app/suggestions-page/suggestions-page.module.ts +++ b/src/app/suggestions-page/suggestions-page.module.ts @@ -5,7 +5,7 @@ import { SuggestionsPageComponent } from './suggestions-page.component'; import { SharedModule } from '../shared/shared.module'; import { SuggestionsPageRoutingModule } from './suggestions-page-routing.module'; import { SuggestionsService } from '../suggestion-notifications/reciter-suggestions/suggestions.service'; -import { OpenaireSuggestionsDataService } from '../core/suggestion-notifications/reciter-suggestions/openaire-suggestions-data.service'; +import { SuggestionsDataService } from '../core/suggestion-notifications/reciter-suggestions/suggestions-data.service'; import {SuggestionNotificationsModule} from '../suggestion-notifications/suggestion-notifications.module'; @NgModule({ @@ -17,7 +17,7 @@ import {SuggestionNotificationsModule} from '../suggestion-notifications/suggest SuggestionsPageRoutingModule ], providers: [ - OpenaireSuggestionsDataService, + SuggestionsDataService, SuggestionsService ] }) diff --git a/src/app/suggestions-page/suggestions-page.resolver.ts b/src/app/suggestions-page/suggestions-page.resolver.ts index cf22b04aa6..6cc89a72d4 100644 --- a/src/app/suggestions-page/suggestions-page.resolver.ts +++ b/src/app/suggestions-page/suggestions-page.resolver.ts @@ -6,15 +6,15 @@ import { find } from 'rxjs/operators'; import { RemoteData } from '../core/data/remote-data'; import { hasValue } from '../shared/empty.util'; -import { OpenaireSuggestionsDataService } from '../core/suggestion-notifications/reciter-suggestions/openaire-suggestions-data.service'; -import { OpenaireSuggestionTarget } from '../core/suggestion-notifications/reciter-suggestions/models/openaire-suggestion-target.model'; +import { SuggestionsDataService } from '../core/suggestion-notifications/reciter-suggestions/suggestions-data.service'; +import { SuggestionTarget } from '../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; /** * This class represents a resolver that requests a specific collection before the route is activated */ @Injectable() -export class SuggestionsPageResolver implements Resolve> { - constructor(private suggestionsDataService: OpenaireSuggestionsDataService) { +export class SuggestionsPageResolver implements Resolve> { + constructor(private suggestionsDataService: SuggestionsDataService) { } /** @@ -24,7 +24,7 @@ export class SuggestionsPageResolver implements Resolve> Emits the found collection based on the parameters in the current route, * or an error if something went wrong */ - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { return this.suggestionsDataService.getTargetById(route.params.targetId).pipe( find((RD) => hasValue(RD.hasFailed) || RD.hasSucceeded), ); From a9c5c20c5aa810d516eb28d87ee1f217336fe90a Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 16 Jun 2023 11:45:26 +0200 Subject: [PATCH 05/94] 102415: Provide all external sources of correct entity type in edit relationships ({item-page}/edit/relationship) --- .../dynamic-lookup-relation-modal.component.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts index 69c2ac3b7f..95acd0f41d 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts @@ -30,6 +30,10 @@ import { getAllSucceededRemoteDataPayload } from '../../../../../core/shared/ope import { followLink } from '../../../../utils/follow-link-config.model'; import { RelationshipType } from '../../../../../core/shared/item-relationships/relationship-type.model'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { FindListOptions } from '../../../../../core/data/request.models'; +import { RequestParam } from '../../../../../core/cache/models/request-param.model'; +import { getFirstSucceededRemoteDataPayload } from '../../../../../core/shared/operators'; +import { PaginatedList } from '../../../../../core/data/paginated-list.model'; @Component({ selector: 'ds-dynamic-lookup-relation-modal', @@ -202,6 +206,19 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy ).pipe( getAllSucceededRemoteDataPayload() ); + } else { + const findListOptions = Object.assign({}, new FindListOptions(), { + elementsPerPage: 5, + currentPage: 1, + searchParams: [ + new RequestParam('entityType', this.relationshipOptions.relationshipType) + ] + }); + this.externalSourcesRD$ = this.externalSourceService.searchBy('findByEntityType', findListOptions, + true, true, followLink('entityTypes')) + .pipe(getFirstSucceededRemoteDataPayload(), map((r: PaginatedList) => { + return r.page; + })); } this.setTotals(); From cf9179d800dbaab74fd48b15bde4840cc1155f57 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Mon, 19 Jun 2023 16:29:15 +0200 Subject: [PATCH 06/94] 102415: Fix for import modal i18n labels --- .../dynamic-lookup-relation-modal.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts index 95acd0f41d..38b547d4a9 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts @@ -178,6 +178,7 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy if (!!this.currentItemIsLeftItem$) { this.currentItemIsLeftItem$.subscribe((isLeft) => { this.isLeft = isLeft; + this.label = this.relationshipType.leftwardType; }); } From 9b556fd7039449c9b8a762cb335f0b1b813001da Mon Sep 17 00:00:00 2001 From: Francesco Bacchelli Date: Tue, 22 Aug 2023 11:16:30 +0200 Subject: [PATCH 07/94] Merge branch CST-5249_suggestion of https://github.com/4Science/DSpace into CST-11299 --- .../builders/remote-data-build.service.ts | 1 + src/app/core/core.module.ts | 6 +- .../suggestion-objects.resource-type.ts | 16 ----- .../suggestion-source-object.resource-type.ts | 9 +++ .../models/suggestion-source.model.ts | 2 +- .../suggestion-target-object.resource-type.ts | 9 +++ .../models/suggestion-target.model.ts | 2 +- .../source/suggestion-source-data.service.ts | 2 +- .../suggestions-data.service.ts | 4 +- .../target/suggestion-target-data.service.ts | 3 +- src/app/home-page/home-page.component.html | 1 + src/app/home-page/home-page.module.ts | 18 ++--- src/app/menu.resolver.ts | 12 ++++ .../my-dspace-page.component.html | 1 + .../my-dspace-page/my-dspace-page.module.ts | 18 ++--- .../profile-page/profile-page.component.html | 1 + src/app/profile-page/profile-page.module.ts | 16 +++-- .../dso-selector-modal-wrapper.component.ts | 18 ++++- .../reciter-suggestions/selectors.ts | 10 +-- .../suggestion-targets.component.ts | 1 + .../suggestion-targets.effects.ts | 2 +- .../suggestion-targets.state.service.ts | 14 ++-- .../suggestions-popup.component.ts | 2 + .../suggestions.service.ts | 4 +- .../suggestion-notifications.module.ts | 12 ++++ src/assets/i18n/en.json5 | 68 +++++++++++++++++++ src/themes/custom/lazy-theme.module.ts | 3 + 27 files changed, 193 insertions(+), 62 deletions(-) create mode 100644 src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source-object.resource-type.ts create mode 100644 src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target-object.resource-type.ts diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts index 075bf3ca0c..5b5e362406 100644 --- a/src/app/core/cache/builders/remote-data-build.service.ts +++ b/src/app/core/cache/builders/remote-data-build.service.ts @@ -161,6 +161,7 @@ export class RemoteDataBuildService { } else { // in case the elements of the paginated list were already filled in, because they're UnCacheableObjects paginatedList.page = paginatedList.page + .filter((obj: any) => obj != null) .map((obj: any) => this.plainObjectToInstance(obj)) .map((obj: any) => this.linkService.resolveLinks(obj, ...pageLink.linksToFollow) diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index e176af7d55..7acf132df9 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -185,6 +185,8 @@ import { FlatBrowseDefinition } from './shared/flat-browse-definition.model'; import { ValueListBrowseDefinition } from './shared/value-list-browse-definition.model'; import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition'; import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model'; +import { SuggestionTarget } from './suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { SuggestionSource } from './suggestion-notifications/reciter-suggestions/models/suggestion-source.model'; /** * When not in production, endpoint responses can be mocked for testing purposes @@ -386,7 +388,9 @@ export const models = IdentifierData, Subscription, ItemRequest, - BulkAccessConditionOptions + BulkAccessConditionOptions, + SuggestionTarget, + SuggestionSource ]; @NgModule({ diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type.ts b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type.ts index e31006959f..8f87027a8c 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type.ts @@ -1,21 +1,5 @@ import { ResourceType } from '../../../shared/resource-type'; -/** - * The resource type for the Suggestion Target object - * - * Needs to be in a separate file to prevent circular - * dependencies in webpack. - */ -export const SUGGESTION_TARGET = new ResourceType('suggestiontarget'); - -/** - * The resource type for the Suggestion Source object - * - * Needs to be in a separate file to prevent circular - * dependencies in webpack. - */ -export const SUGGESTION_SOURCE = new ResourceType('suggestionsource'); - /** * The resource type for the Suggestion object * diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source-object.resource-type.ts b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source-object.resource-type.ts new file mode 100644 index 0000000000..2e26fe4301 --- /dev/null +++ b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source-object.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../../../shared/resource-type'; + +/** + * The resource type for the Suggestion Source object + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const SUGGESTION_SOURCE = new ResourceType('suggestionsource'); diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts index 64ddf9863c..007520800d 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts @@ -1,6 +1,6 @@ import { autoserialize, deserialize } from 'cerialize'; -import { SUGGESTION_SOURCE } from './suggestion-objects.resource-type'; +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'; diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target-object.resource-type.ts b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target-object.resource-type.ts new file mode 100644 index 0000000000..71dd41912a --- /dev/null +++ b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target-object.resource-type.ts @@ -0,0 +1,9 @@ +import { ResourceType } from '../../../shared/resource-type'; + +/** + * The resource type for the Suggestion Target object + * + * Needs to be in a separate file to prevent circular + * dependencies in webpack. + */ +export const SUGGESTION_TARGET = new ResourceType('suggestiontarget'); diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts index 970a8e7e28..2afe170e77 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts @@ -2,7 +2,7 @@ import { autoserialize, deserialize } from 'cerialize'; import { CacheableObject } from '../../../cache/cacheable-object.model'; -import { SUGGESTION_TARGET } from './suggestion-objects.resource-type'; +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'; diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts b/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts index e4286c6b5b..c3e142044e 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { dataService } from '../../../data/base/data-service.decorator'; -import { SUGGESTION_SOURCE } from '../models/suggestion-objects.resource-type'; +import { SUGGESTION_SOURCE } from '../models/suggestion-source-object.resource-type'; import { IdentifiableDataService } from '../../../data/base/identifiable-data.service'; import { SuggestionSource } from '../models/suggestion-source.model'; import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data'; diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts b/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts index abdcbca0c1..944a13e3a3 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts @@ -168,8 +168,8 @@ export class SuggestionsDataService { ...linksToFollow: FollowLinkConfig[] ): Observable>> { options.searchParams = [new RequestParam('target', userId)]; - - return this.suggestionTargetsDataService.getTargetsByUser(this.searchFindByTargetMethod, options, ...linksToFollow); + //return this.suggestionTargetsDataService.getTargetsByUser(this.searchFindByTargetMethod, options, ...linksToFollow); + return this.suggestionTargetsDataService.getTargetsByUser(userId, options, ...linksToFollow); } /** diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts b/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts index 372d1f9917..ce5f131c1d 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { dataService } from '../../../data/base/data-service.decorator'; -import { SUGGESTION_TARGET } from '../models/suggestion-objects.resource-type'; + import { IdentifiableDataService } from '../../../data/base/identifiable-data.service'; import { SuggestionTarget } from '../models/suggestion-target.model'; import { FindAllData, FindAllDataImpl } from '../../../data/base/find-all-data'; @@ -20,6 +20,7 @@ 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) diff --git a/src/app/home-page/home-page.component.html b/src/app/home-page/home-page.component.html index caa86ac290..49329b3f04 100644 --- a/src/app/home-page/home-page.component.html +++ b/src/app/home-page/home-page.component.html @@ -7,3 +7,4 @@ + diff --git a/src/app/home-page/home-page.module.ts b/src/app/home-page/home-page.module.ts index 1681abd805..00f9dbd8f9 100644 --- a/src/app/home-page/home-page.module.ts +++ b/src/app/home-page/home-page.module.ts @@ -13,6 +13,7 @@ import { RecentItemListComponent } from './recent-item-list/recent-item-list.com import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module'; import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module'; import { ThemedTopLevelCommunityListComponent } from './top-level-community-list/themed-top-level-community-list.component'; +import { SuggestionNotificationsModule } from '../suggestion-notifications/suggestion-notifications.module'; const DECLARATIONS = [ HomePageComponent, @@ -25,14 +26,15 @@ const DECLARATIONS = [ ]; @NgModule({ - imports: [ - CommonModule, - SharedModule.withEntryComponents(), - JournalEntitiesModule.withEntryComponents(), - ResearchEntitiesModule.withEntryComponents(), - HomePageRoutingModule, - StatisticsModule.forRoot() - ], + imports: [ + CommonModule, + SharedModule.withEntryComponents(), + JournalEntitiesModule.withEntryComponents(), + ResearchEntitiesModule.withEntryComponents(), + HomePageRoutingModule, + StatisticsModule.forRoot(), + SuggestionNotificationsModule + ], declarations: [ ...DECLARATIONS, ], diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index 7a3a16d626..70e2b6462f 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -47,6 +47,7 @@ import { import { ExportBatchSelectorComponent } from './shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component'; +import { NOTIFICATIONS_RECITER_SUGGESTION_PATH } from './admin/admin-notifications/admin-notifications-routing-paths'; /** * Creates all of the app's menus @@ -555,6 +556,17 @@ export class MenuResolver implements Resolve { link: '/admin/notifications/quality-assurance' } as LinkMenuItemModel, }, + { + id: 'notifications_reciter', + parentID: 'notifications', + active: false, + visible: authorized, + model: { + type: MenuItemType.LINK, + text: 'menu.section.notifications_reciter', + link: '/admin/notifications/' + NOTIFICATIONS_RECITER_SUGGESTION_PATH + } as LinkMenuItemModel, + }, /* Admin Search */ { id: 'admin_search', diff --git a/src/app/my-dspace-page/my-dspace-page.component.html b/src/app/my-dspace-page/my-dspace-page.component.html index ea5784170f..c5e49b0cec 100644 --- a/src/app/my-dspace-page/my-dspace-page.component.html +++ b/src/app/my-dspace-page/my-dspace-page.component.html @@ -1,5 +1,6 @@
+
+ diff --git a/src/app/profile-page/profile-page.module.ts b/src/app/profile-page/profile-page.module.ts index 0e2902de33..c5bfad1713 100644 --- a/src/app/profile-page/profile-page.module.ts +++ b/src/app/profile-page/profile-page.module.ts @@ -12,16 +12,18 @@ import { ThemedProfilePageComponent } from './themed-profile-page.component'; import { FormModule } from '../shared/form/form.module'; import { UiSwitchModule } from 'ngx-ui-switch'; import { ProfileClaimItemModalComponent } from './profile-claim-item-modal/profile-claim-item-modal.component'; +import { SuggestionNotificationsModule } from '../suggestion-notifications/suggestion-notifications.module'; @NgModule({ - imports: [ - ProfilePageRoutingModule, - CommonModule, - SharedModule, - FormModule, - UiSwitchModule - ], + imports: [ + ProfilePageRoutingModule, + CommonModule, + SharedModule, + FormModule, + UiSwitchModule, + SuggestionNotificationsModule + ], exports: [ ProfilePageComponent, ThemedProfilePageComponent, diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts index 3f81687c9f..6798449094 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { RemoteData } from '../../../core/data/remote-data'; @@ -29,6 +29,11 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit { */ @Input() dsoRD: RemoteData; + /** + * Representing if component should emit value of selected entries or navigate + */ + @Input() emitOnly = false; + /** * Optional header to display above the selection list * Supports i18n keys @@ -50,6 +55,11 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit { */ action: SelectorActionType; + /** + * Event emitted when a DSO entry is selected if emitOnly is set to true + */ + @Output() select: EventEmitter = new EventEmitter(); + /** * Default DSO ordering */ @@ -93,7 +103,11 @@ export abstract class DSOSelectorModalWrapperComponent implements OnInit { */ selectObject(dso: DSpaceObject) { this.close(); - this.navigate(dso); + if (this.emitOnly) { + this.select.emit(dso); + } else { + this.navigate(dso); + } } /** diff --git a/src/app/suggestion-notifications/reciter-suggestions/selectors.ts b/src/app/suggestion-notifications/reciter-suggestions/selectors.ts index cbb99fe869..ee90613271 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/selectors.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/selectors.ts @@ -27,7 +27,7 @@ export function reciterSuggestionTargetStateSelector(): MemoizedSelector { return subStateSelector(reciterSuggestionTargetStateSelector(), 'targets'); @@ -47,7 +47,7 @@ export const isReciterSuggestionTargetLoadedSelector = createSelector(_getRecite * @function isDeduplicationSetsProcessingSelector * @return {boolean} */ -export const isreciterSuggestionTargetProcessingSelector = createSelector(_getReciterSuggestionTargetState, +export const isReciterSuggestionTargetProcessingSelector = createSelector(_getReciterSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.processing ); @@ -56,7 +56,7 @@ export const isreciterSuggestionTargetProcessingSelector = createSelector(_getRe * @function getreciterSuggestionTargetTotalPagesSelector * @return {number} */ -export const getreciterSuggestionTargetTotalPagesSelector = createSelector(_getReciterSuggestionTargetState, +export const getReciterSuggestionTargetTotalPagesSelector = createSelector(_getReciterSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.totalPages ); @@ -65,7 +65,7 @@ export const getreciterSuggestionTargetTotalPagesSelector = createSelector(_getR * @function getreciterSuggestionTargetCurrentPageSelector * @return {number} */ -export const getreciterSuggestionTargetCurrentPageSelector = createSelector(_getReciterSuggestionTargetState, +export const getReciterSuggestionTargetCurrentPageSelector = createSelector(_getReciterSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.currentPage ); @@ -74,7 +74,7 @@ export const getreciterSuggestionTargetCurrentPageSelector = createSelector(_get * @function getreciterSuggestionTargetTotalsSelector * @return {number} */ -export const getreciterSuggestionTargetTotalsSelector = createSelector(_getReciterSuggestionTargetState, +export const getReciterSuggestionTargetTotalsSelector = createSelector(_getReciterSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.totalElements ); diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts index cfa168e2b8..e5524765da 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts @@ -136,6 +136,7 @@ export class SuggestionTargetsComponent implements OnInit { distinctUntilChanged(), take(1) ).subscribe((options: PaginationComponentOptions) => { + console.log('HELLO suggestion called!', options); this.suggestionTargetsStateService.dispatchRetrieveReciterSuggestionTargets( this.source, options.pageSize, diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts index 3718e0cfad..6ab587a6c2 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts @@ -62,7 +62,7 @@ export class SuggestionTargetsEffects { /** * Fetch the current user suggestion */ - refreshUserTargets$ = createEffect(() => this.actions$.pipe( + RefreshUserSuggestionsAction = createEffect(() => this.actions$.pipe( ofType(SuggestionTargetActionTypes.REFRESH_USER_SUGGESTIONS), switchMap((action: RefreshUserSuggestionsAction) => { return this.store$.select((state: any) => state.core.auth.userId) diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts index 97792df6af..fec9e01f3c 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts @@ -7,10 +7,10 @@ import { map } from 'rxjs/operators'; import { getCurrentUserSuggestionTargetsSelector, getCurrentUserSuggestionTargetsVisitedSelector, - getreciterSuggestionTargetCurrentPageSelector, - getreciterSuggestionTargetTotalsSelector, + getReciterSuggestionTargetCurrentPageSelector, + getReciterSuggestionTargetTotalsSelector, isReciterSuggestionTargetLoadedSelector, - isreciterSuggestionTargetProcessingSelector, + isReciterSuggestionTargetProcessingSelector, reciterSuggestionTargetObjectSelector } from '../selectors'; import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; @@ -74,7 +74,7 @@ export class SuggestionTargetsStateService { * 'true' if there are operations running on the targets (ex.: a REST call), 'false' otherwise. */ public isReciterSuggestionTargetsProcessing(): Observable { - return this.store.pipe(select(isreciterSuggestionTargetProcessingSelector)); + return this.store.pipe(select(isReciterSuggestionTargetProcessingSelector)); } /** @@ -84,7 +84,7 @@ export class SuggestionTargetsStateService { * The number of the Reciter Suggestion Targets pages. */ public getReciterSuggestionTargetsTotalPages(): Observable { - return this.store.pipe(select(getreciterSuggestionTargetTotalsSelector)); + return this.store.pipe(select(getReciterSuggestionTargetTotalsSelector)); } /** @@ -94,7 +94,7 @@ export class SuggestionTargetsStateService { * The number of the current Reciter Suggestion Targets page. */ public getReciterSuggestionTargetsCurrentPage(): Observable { - return this.store.pipe(select(getreciterSuggestionTargetCurrentPageSelector)); + return this.store.pipe(select(getReciterSuggestionTargetCurrentPageSelector)); } /** @@ -104,7 +104,7 @@ export class SuggestionTargetsStateService { * The number of the Reciter Suggestion Targets. */ public getReciterSuggestionTargetsTotals(): Observable { - return this.store.pipe(select(getreciterSuggestionTargetTotalsSelector)); + return this.store.pipe(select(getReciterSuggestionTargetTotalsSelector)); } /** diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts index 0195f074ae..c0f94cadce 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts @@ -31,6 +31,8 @@ export class SuggestionsPopupComponent implements OnInit, OnDestroy { } public initializePopup() { + console.log('POPUP INIT dispatchRefreshUserSuggestionsAction'); + this.reciterSuggestionStateService.dispatchRefreshUserSuggestionsAction(); const notifier = new Subject(); this.subscription = combineLatest([ this.reciterSuggestionStateService.getCurrentUserSuggestionTargets(), diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts index 662b4c03de..b760ddf34d 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts @@ -49,7 +49,9 @@ export class SuggestionsService { /** * Initialize the service variables. * @param {AuthService} authService - * @param {ResearcherProfileService} researcherProfileService + * @param {ResearcherProfileDataService} researcherProfileService + * @param {SuggestionSourceDataService} suggestionSourceDataService + * @param {SuggestionTargetDataService} suggestionTargetDataService * @param {SuggestionsDataService} suggestionsDataService */ constructor( diff --git a/src/app/suggestion-notifications/suggestion-notifications.module.ts b/src/app/suggestion-notifications/suggestion-notifications.module.ts index 4369d17375..fa9d81eb75 100644 --- a/src/app/suggestion-notifications/suggestion-notifications.module.ts +++ b/src/app/suggestion-notifications/suggestion-notifications.module.ts @@ -40,6 +40,15 @@ import { } from './reciter-suggestions/suggestions-notification/suggestions-notification.component'; import { SuggestionsService } from './reciter-suggestions/suggestions.service'; import { SuggestionsDataService } from '../core/suggestion-notifications/reciter-suggestions/suggestions-data.service'; +import { + SuggestionSourceDataService +} from '../core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service'; +import { + SuggestionTargetDataService +} from '../core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service'; +import { + SuggestionTargetsStateService +} from './reciter-suggestions/suggestion-targets/suggestion-targets.state.service'; const MODULES = [ CommonModule, @@ -77,6 +86,9 @@ const PROVIDERS = [ QualityAssuranceSourceDataService, QualityAssuranceEventDataService, SuggestionsService, + SuggestionSourceDataService, + SuggestionTargetDataService, + SuggestionTargetsStateService, SuggestionsDataService ]; diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4c13ec73d1..0d90ca693d 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -24,6 +24,10 @@ "404.page-not-found": "page not found", + "admin.notifications.recitersuggestion.breadcrumbs": "Suggestions", + + "admin.notifications.recitersuggestion.page.title": "Suggestions", + "error-page.description.401": "unauthorized", "error-page.description.403": "forbidden", @@ -3052,6 +3056,12 @@ "mydspace.view-btn": "View", + "mydspace.import": "Import", + + "mydspace.notification.suggestion": "We found {{count}} publications
in the {{source}} that seems to be related to your profile.
Please review the suggestions", + + "mydspace.notification.suggestion.page": "We found {{count}} {{type}} in the {{source}} that seems to be related to your profile. Please review the suggestions.", + "nav.browse.header": "All of DSpace", "nav.community-browse.header": "By Community", @@ -3514,6 +3524,64 @@ "media-viewer.playlist": "Playlist", + "reciter.suggestion.loading": "Loading ...", + + "reciter.suggestion.title": "Suggestions", + + "reciter.suggestion.targets.description": "Below you can see all the suggestions ", + + "reciter.suggestion.targets": "Current Suggestions", + + "reciter.suggestion.table.name": "Researcher Name", + + "reciter.suggestion.table.actions": "Actions", + + "reciter.suggestion.button.review": "Review {{ total }} suggestion(s)", + + "reciter.suggestion.noTargets": "No target found.", + + "reciter.suggestion.target.error.service.retrieve": "An error occurred while loading the Suggestion targets", + + "reciter.suggestion.evidence.type": "Type", + + "reciter.suggestion.evidence.score": "Score", + + "reciter.suggestion.evidence.notes": "Notes", + + "reciter.suggestion.approveAndImport": "Approve & import", + + "reciter.suggestion.approveAndImport.success": "The suggestion has been imported successfully. View.", + + "reciter.suggestion.approveAndImport.bulk": "Approve & import Selected", + + "reciter.suggestion.approveAndImport.bulk.success": "{{ count }} suggestions have been imported successfully ", + + "reciter.suggestion.approveAndImport.bulk.error": "{{ count }} suggestions haven't been imported due to unexpected server errors", + + "reciter.suggestion.notMine": "Not mine", + + "reciter.suggestion.notMine.success": "The suggestion has been discarded", + + "reciter.suggestion.notMine.bulk": "Not mine Selected", + + "reciter.suggestion.notMine.bulk.success": "{{ count }} suggestions have been discarded ", + + "reciter.suggestion.notMine.bulk.error": "{{ count }} suggestions haven't been discarded due to unexpected server errors", + + "reciter.suggestion.seeEvidence": "See evidence", + + "reciter.suggestion.hideEvidence": "Hide evidence", + + "reciter.suggestion.suggestionFor": "Suggestion for", + + "reciter.suggestion.source.oaire": "OpenAIRE Graph", + + "reciter.suggestion.from.source": "from the ", + + "reciter.suggestion.totalScore": "Total Score", + + "reciter.suggestion.type.oaire": "OpenAIRE", + "register-email.title": "New user registration", "register-page.create-profile.header": "Create Profile", diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index edb3f5478c..771e9e1b08 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -156,6 +156,8 @@ import { ItemStatusComponent } from './app/item-page/edit-item-page/item-status/ import { EditBitstreamPageComponent } from './app/bitstream-page/edit-bitstream-page/edit-bitstream-page.component'; import { FormModule } from '../../app/shared/form/form.module'; import { RequestCopyModule } from 'src/app/request-copy/request-copy.module'; +import { SuggestionNotificationsModule } from '../../app/suggestion-notifications/suggestion-notifications.module'; + const DECLARATIONS = [ FileSectionComponent, @@ -299,6 +301,7 @@ const DECLARATIONS = [ NgxGalleryModule, FormModule, RequestCopyModule, + SuggestionNotificationsModule ], declarations: DECLARATIONS, exports: [ From 91b4d3dcd10cbd8248a41505eac96586707bac2b Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 10 Nov 2023 16:13:20 +0100 Subject: [PATCH 08/94] [DURACOM-204][#2622] Makes forgot-password link removable --- .../data/feature-authorization/feature-id.ts | 1 + .../password/log-in-password.component.html | 16 ++++++------ .../password/log-in-password.component.ts | 26 ++++++++++++++++--- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index 8fef45a953..bd1f65b72d 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -34,4 +34,5 @@ export enum FeatureID { CanEditItem = 'canEditItem', CanRegisterDOI = 'canRegisterDOI', CanSubscribe = 'canSubscribeDso', + EPersonForgotPassword = 'epersonForgotPassword', } diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.html b/src/app/shared/log-in/methods/password/log-in-password.component.html index 60477d141d..52f28e7190 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.html +++ b/src/app/shared/log-in/methods/password/log-in-password.component.html @@ -29,11 +29,11 @@ [disabled]="!form.valid"> {{"login.form.submit" | translate}} - + + + diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.ts b/src/app/shared/log-in/methods/password/log-in-password.component.ts index 6132394019..1f96c4c0d8 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.ts +++ b/src/app/shared/log-in/methods/password/log-in-password.component.ts @@ -1,9 +1,9 @@ -import { map } from 'rxjs/operators'; +import { combineLatest, Observable, shareReplay } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; import { Component, Inject, OnInit } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { select, Store } from '@ngrx/store'; -import { Observable } from 'rxjs'; import { AuthenticateAction, ResetAuthenticationMessagesAction } from '../../../../core/auth/auth.actions'; import { getAuthenticationError, getAuthenticationInfo, } from '../../../../core/auth/selectors'; @@ -73,6 +73,17 @@ export class LogInPasswordComponent implements OnInit { */ public canRegister$: Observable; + /** + * Whether or not the current user (or anonymous) is authorized to register an account + */ + canForgot$: Observable; + + /** + * Shows the divider only if contains at least one link to show + */ + canShowDivider$: Observable; + + constructor( @Inject('authMethodProvider') public injectedAuthMethodModel: AuthMethod, @Inject('isStandalonePage') public isStandalonePage: boolean, @@ -114,8 +125,15 @@ export class LogInPasswordComponent implements OnInit { return message; }) ); - - this.canRegister$ = this.authorizationService.isAuthorized(FeatureID.EPersonRegistration); + + this.canRegister$ = this.authorizationService.isAuthorized(FeatureID.EPersonRegistration).pipe(shareReplay(1)); + this.canForgot$ = this.authorizationService.isAuthorized(FeatureID.EPersonForgotPassword).pipe(shareReplay(1)); + this.canShowDivider$ = + combineLatest([this.canRegister$, this.canForgot$]) + .pipe( + map(([canRegister, canForgot]) => canRegister || canForgot), + filter(Boolean) + ); } getRegisterRoute() { From 2429c3660b8ae370a8da945a9c3483b185b201e9 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 10 Nov 2023 16:49:06 +0100 Subject: [PATCH 09/94] [DURACOM-204][#2622] Makes forgot-password link removable --- .../log-in/methods/password/log-in-password.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/log-in/methods/password/log-in-password.component.ts b/src/app/shared/log-in/methods/password/log-in-password.component.ts index 1f96c4c0d8..008eb11eef 100644 --- a/src/app/shared/log-in/methods/password/log-in-password.component.ts +++ b/src/app/shared/log-in/methods/password/log-in-password.component.ts @@ -64,7 +64,7 @@ export class LogInPasswordComponent implements OnInit { /** * The authentication form. - * @type {FormGroup} + * @type {UntypedFormGroup} */ public form: UntypedFormGroup; @@ -125,7 +125,7 @@ export class LogInPasswordComponent implements OnInit { return message; }) ); - + this.canRegister$ = this.authorizationService.isAuthorized(FeatureID.EPersonRegistration).pipe(shareReplay(1)); this.canForgot$ = this.authorizationService.isAuthorized(FeatureID.EPersonForgotPassword).pipe(shareReplay(1)); this.canShowDivider$ = From 6fff475a5a0a8a2a234b114307e3df897400da60 Mon Sep 17 00:00:00 2001 From: Michael Spalti Date: Thu, 16 Nov 2023 19:38:13 -0800 Subject: [PATCH 10/94] File edit component updated to work for forms without access conditions. Fixed unit test Fixed accidental reformatting --- ...section-upload-file-edit.component.spec.ts | 25 ++++++ .../section-upload-file-edit.component.ts | 78 ++++++++++--------- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts index d008bf61f1..72c555f39c 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts @@ -80,6 +80,9 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { const fileData: any = mockUploadFiles[0]; const pathCombiner = new JsonPatchOperationPathCombiner('sections', sectionId, 'files', fileIndex); + let noAccessConditionsMock = Object.assign({}, mockFileFormData); + delete noAccessConditionsMock.accessConditions; + beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ @@ -299,6 +302,28 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => { })); + it('should update Bitstream data properly when access options are omitted', fakeAsync(() => { + compAsAny.formRef = {formGroup: null}; + compAsAny.fileData = fileData; + compAsAny.pathCombiner = pathCombiner; + formService.validateAllFormFields.and.callFake(() => null); + formService.isValid.and.returnValue(of(true)); + formService.getFormData.and.returnValue(of(noAccessConditionsMock)); + const response = [ + Object.assign(mockSubmissionObject, { + sections: { + upload: { + files: mockUploadFiles + } + } + }) + ]; + operationsService.jsonPatchByResourceID.and.returnValue(of(response)); + comp.saveBitstreamData(); + tick(); + expect(uploadService.updateFileData).toHaveBeenCalled(); + })); + it('should not save Bitstream File data properly when form is not valid', fakeAsync(() => { compAsAny.formRef = {formGroup: null}; compAsAny.pathCombiner = pathCombiner; diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts index 9ee4a7dda5..eb4dc5aaf8 100644 --- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts +++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts @@ -416,56 +416,58 @@ export class SubmissionSectionUploadFileEditComponent this.operationsBuilder.remove(this.pathCombiner.getPath(path)); }); const accessConditionsToSave = []; - formData.accessConditions - .map((accessConditions) => accessConditions.accessConditionGroup) - .filter((accessCondition) => isNotEmpty(accessCondition)) - .forEach((accessCondition) => { - let accessConditionOpt; + if (formData.hasOwnProperty('accessConditions')) { + formData.accessConditions + .filter((accessConditions) => isNotNull(accessConditions)) + .map((accessConditions) => accessConditions.accessConditionGroup) + .filter((accessCondition) => isNotEmpty(accessCondition)) + .forEach((accessCondition) => { + let accessConditionOpt; - this.availableAccessConditionOptions - .filter((element) => isNotNull(accessCondition.name) && element.name === accessCondition.name[0].value) - .forEach((element) => accessConditionOpt = element); + this.availableAccessConditionOptions + .filter((element) => isNotNull(accessCondition.name) && element.name === accessCondition.name[0].value) + .forEach((element) => accessConditionOpt = element); - if (accessConditionOpt) { - const currentAccessCondition = Object.assign({}, accessCondition); - currentAccessCondition.name = this.retrieveValueFromField(accessCondition.name); + if (accessConditionOpt) { + const currentAccessCondition = Object.assign({}, accessCondition); + currentAccessCondition.name = this.retrieveValueFromField(accessCondition.name); - /* When start and end date fields are deactivated, their values may be still present in formData, - therefore it is necessary to delete them if they're not allowed by the current access condition option. */ - if (!accessConditionOpt.hasStartDate) { - delete currentAccessCondition.startDate; - } else if (accessCondition.startDate) { - const startDate = this.retrieveValueFromField(accessCondition.startDate); - // Clamp the start date to the maximum, if any, since the - // datepicker sometimes exceeds it. - let startDateDate = new Date(startDate); - if (accessConditionOpt.maxStartDate) { + /* When start and end date fields are deactivated, their values may be still present in formData, + therefore it is necessary to delete them if they're not allowed by the current access condition option. */ + if (!accessConditionOpt.hasStartDate) { + delete currentAccessCondition.startDate; + } else if (accessCondition.startDate) { + const startDate = this.retrieveValueFromField(accessCondition.startDate); + // Clamp the start date to the maximum, if any, since the + // datepicker sometimes exceeds it. + let startDateDate = new Date(startDate); + if (accessConditionOpt.maxStartDate) { const maxStartDateDate = new Date(accessConditionOpt.maxStartDate); if (startDateDate > maxStartDateDate) { - startDateDate = maxStartDateDate; + startDateDate = maxStartDateDate; } + } + currentAccessCondition.startDate = dateToISOFormat(startDateDate); } - currentAccessCondition.startDate = dateToISOFormat(startDateDate); - } - if (!accessConditionOpt.hasEndDate) { - delete currentAccessCondition.endDate; - } else if (accessCondition.endDate) { - const endDate = this.retrieveValueFromField(accessCondition.endDate); - // Clamp the end date to the maximum, if any, since the - // datepicker sometimes exceeds it. - let endDateDate = new Date(endDate); - if (accessConditionOpt.maxEndDate) { + if (!accessConditionOpt.hasEndDate) { + delete currentAccessCondition.endDate; + } else if (accessCondition.endDate) { + const endDate = this.retrieveValueFromField(accessCondition.endDate); + // Clamp the end date to the maximum, if any, since the + // datepicker sometimes exceeds it. + let endDateDate = new Date(endDate); + if (accessConditionOpt.maxEndDate) { const maxEndDateDate = new Date(accessConditionOpt.maxEndDate); if (endDateDate > maxEndDateDate) { - endDateDate = maxEndDateDate; + endDateDate = maxEndDateDate; } + } + currentAccessCondition.endDate = dateToISOFormat(endDateDate); } - currentAccessCondition.endDate = dateToISOFormat(endDateDate); + accessConditionsToSave.push(currentAccessCondition); } - accessConditionsToSave.push(currentAccessCondition); - } - }); - + }); + } if (isNotEmpty(accessConditionsToSave)) { this.operationsBuilder.add(this.pathCombiner.getPath('accessConditions'), accessConditionsToSave, true); } From 98241d8925cbc394c78feb124cdea476c67da4be Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 8 Dec 2023 21:54:32 +0300 Subject: [PATCH 11/94] src/assets/i18n: change "controller" to "reviewer" Reviewer is a less obscure term for what this actually is in most cases. --- src/assets/i18n/ar.json5 | 4 ++-- src/assets/i18n/bn.json5 | 2 +- src/assets/i18n/ca.json5 | 2 +- src/assets/i18n/cs.json5 | 4 ++-- src/assets/i18n/de.json5 | 2 +- src/assets/i18n/en.json5 | 4 ++-- src/assets/i18n/es.json5 | 2 +- src/assets/i18n/fi.json5 | 2 +- src/assets/i18n/fr.json5 | 2 +- src/assets/i18n/gd.json5 | 2 +- src/assets/i18n/hu.json5 | 2 +- src/assets/i18n/it.json5 | 2 +- src/assets/i18n/ja.json5 | 4 ++-- src/assets/i18n/kk.json5 | 2 +- src/assets/i18n/lv.json5 | 2 +- src/assets/i18n/nl.json5 | 2 +- src/assets/i18n/pt-BR.json5 | 2 +- src/assets/i18n/pt-PT.json5 | 2 +- src/assets/i18n/sv.json5 | 2 +- src/assets/i18n/sw.json5 | 4 ++-- src/assets/i18n/tr.json5 | 2 +- src/assets/i18n/uk.json5 | 2 +- 22 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/assets/i18n/ar.json5 b/src/assets/i18n/ar.json5 index 3069104dd9..617d2a93de 100644 --- a/src/assets/i18n/ar.json5 +++ b/src/assets/i18n/ar.json5 @@ -4284,9 +4284,9 @@ // TODO New key - Add a translation "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // TODO New key - Add a translation - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation diff --git a/src/assets/i18n/bn.json5 b/src/assets/i18n/bn.json5 index c70cc6f459..c9c5ba2640 100644 --- a/src/assets/i18n/bn.json5 +++ b/src/assets/i18n/bn.json5 @@ -3886,7 +3886,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "বৈধতা", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "নিয়ামক জন্য অপেক্ষা করছে", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/ca.json5 b/src/assets/i18n/ca.json5 index ad8fe49424..db624f2c3f 100644 --- a/src/assets/i18n/ca.json5 +++ b/src/assets/i18n/ca.json5 @@ -4196,7 +4196,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validació", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Esperant el controlador", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 7f9583a50e..9816571e05 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -4195,9 +4195,9 @@ // TODO New key - Add a translation "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // TODO New key - Add a translation - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index c185a13432..f77d1a21fd 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -3491,7 +3491,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validierung", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Warten auf die Überprüfung", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 643a3ce0d1..78d034c417 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3042,7 +3042,7 @@ "mydspace.status.mydspaceValidation": "Validation", - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWorkflow": "Workflow", @@ -3864,7 +3864,7 @@ "search.filters.namedresourcetype.Validation": "Validation", - "search.filters.namedresourcetype.Waiting for Controller": "Waiting for Controller", + "search.filters.namedresourcetype.Waiting for Controller": "Waiting for reviewer", "search.filters.namedresourcetype.Workflow": "Workflow", diff --git a/src/assets/i18n/es.json5 b/src/assets/i18n/es.json5 index 0d5b27473c..dc4e3afcc7 100644 --- a/src/assets/i18n/es.json5 +++ b/src/assets/i18n/es.json5 @@ -4524,7 +4524,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validación", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Esperando al controlador", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/fi.json5 b/src/assets/i18n/fi.json5 index ede41ffb0c..914dfa8f45 100644 --- a/src/assets/i18n/fi.json5 +++ b/src/assets/i18n/fi.json5 @@ -4479,7 +4479,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validointi", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Odottaa tarkastajaa", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/fr.json5 b/src/assets/i18n/fr.json5 index 699ca5cc27..80566e589e 100644 --- a/src/assets/i18n/fr.json5 +++ b/src/assets/i18n/fr.json5 @@ -3828,7 +3828,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "En cours de validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "En attente d'assignation", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/gd.json5 b/src/assets/i18n/gd.json5 index 55a53bc6f1..929ab87d10 100644 --- a/src/assets/i18n/gd.json5 +++ b/src/assets/i18n/gd.json5 @@ -3873,7 +3873,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Dearbhadh", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "A' feitheamh riaghladair", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/hu.json5 b/src/assets/i18n/hu.json5 index d186f6435a..afbfa25a13 100644 --- a/src/assets/i18n/hu.json5 +++ b/src/assets/i18n/hu.json5 @@ -4989,7 +4989,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Érvényesítés", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Várakozás a kontrollerre", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/it.json5 b/src/assets/i18n/it.json5 index 7f410ce0b1..ad2478ae61 100644 --- a/src/assets/i18n/it.json5 +++ b/src/assets/i18n/it.json5 @@ -4570,7 +4570,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Convalida", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "In attesa del controllo", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/ja.json5 b/src/assets/i18n/ja.json5 index da2385fd62..03323eb7ef 100644 --- a/src/assets/i18n/ja.json5 +++ b/src/assets/i18n/ja.json5 @@ -4284,9 +4284,9 @@ // TODO New key - Add a translation "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // TODO New key - Add a translation - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation diff --git a/src/assets/i18n/kk.json5 b/src/assets/i18n/kk.json5 index d23dc23c47..dbaa8078e2 100644 --- a/src/assets/i18n/kk.json5 +++ b/src/assets/i18n/kk.json5 @@ -4145,7 +4145,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Валидация", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Контроллерді күтуде", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/lv.json5 b/src/assets/i18n/lv.json5 index 81e2383a1f..3ceaac7fea 100644 --- a/src/assets/i18n/lv.json5 +++ b/src/assets/i18n/lv.json5 @@ -3498,7 +3498,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validācija", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Gaida kontrolieri", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/nl.json5 b/src/assets/i18n/nl.json5 index 280a87b96f..fc52543c38 100644 --- a/src/assets/i18n/nl.json5 +++ b/src/assets/i18n/nl.json5 @@ -3770,7 +3770,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validatie", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Wachten op controlleur", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/pt-BR.json5 b/src/assets/i18n/pt-BR.json5 index ce35f1ec05..5061c5a0e9 100644 --- a/src/assets/i18n/pt-BR.json5 +++ b/src/assets/i18n/pt-BR.json5 @@ -4533,7 +4533,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validação", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Esperando pelo controlador", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/pt-PT.json5 b/src/assets/i18n/pt-PT.json5 index faa027705e..328cb20810 100644 --- a/src/assets/i18n/pt-PT.json5 +++ b/src/assets/i18n/pt-PT.json5 @@ -4478,7 +4478,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Em validação", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Aguarda validador", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/sv.json5 b/src/assets/i18n/sv.json5 index 4e3576ccfc..d2fe72536c 100644 --- a/src/assets/i18n/sv.json5 +++ b/src/assets/i18n/sv.json5 @@ -3940,7 +3940,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Validering", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Väntar på kontrollant", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/sw.json5 b/src/assets/i18n/sw.json5 index a470ee4b58..d2d663cdc5 100644 --- a/src/assets/i18n/sw.json5 +++ b/src/assets/i18n/sw.json5 @@ -4284,9 +4284,9 @@ // TODO New key - Add a translation "mydspace.status.mydspaceValidation": "Validation", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // TODO New key - Add a translation - "mydspace.status.mydspaceWaitingController": "Waiting for controller", + "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", // "mydspace.status.mydspaceWorkflow": "Workflow", // TODO New key - Add a translation diff --git a/src/assets/i18n/tr.json5 b/src/assets/i18n/tr.json5 index 153eaa1281..75bc5c9a36 100644 --- a/src/assets/i18n/tr.json5 +++ b/src/assets/i18n/tr.json5 @@ -3263,7 +3263,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Doğrulama", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Kontrolör bekleniyor", // "mydspace.status.mydspaceWorkflow": "Workflow", diff --git a/src/assets/i18n/uk.json5 b/src/assets/i18n/uk.json5 index 7df55fa236..fae770b6bd 100644 --- a/src/assets/i18n/uk.json5 +++ b/src/assets/i18n/uk.json5 @@ -3389,7 +3389,7 @@ // "mydspace.status.mydspaceValidation": "Validation", "mydspace.status.mydspaceValidation": "Перевірка", - // "mydspace.status.mydspaceWaitingController": "Waiting for controller", + // "mydspace.status.mydspaceWaitingController": "Waiting for reviewer", "mydspace.status.mydspaceWaitingController": "Чекаємо контролера", // "mydspace.status.mydspaceWorkflow": "Workflow", From f78f4b45fcb82654c2cca08cccb254f414a3244f Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 8 Dec 2023 22:02:19 +0300 Subject: [PATCH 12/94] src/assets/i18n/en.json5: minor updates for consistency We should be capitalizing acronyms and project-specific nouns like MyDSpace consistently in our interface. --- src/assets/i18n/en.json5 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 78d034c417..b3e1759c1b 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -590,9 +590,9 @@ "admin.metadata-import.page.error.addFile": "Select file first!", - "admin.metadata-import.page.error.addFileUrl": "Insert file url first!", + "admin.metadata-import.page.error.addFileUrl": "Insert file URL first!", - "admin.batch-import.page.error.addFile": "Select Zip file first!", + "admin.batch-import.page.error.addFile": "Select ZIP file first!", "admin.metadata-import.page.toggle.upload": "Upload", @@ -3028,9 +3028,9 @@ "mydspace.results.no-title": "No title", - "mydspace.results.no-uri": "No Uri", + "mydspace.results.no-uri": "No URI", - "mydspace.search-form.placeholder": "Search in mydspace...", + "mydspace.search-form.placeholder": "Search in MyDSpace...", "mydspace.show.workflow": "Workflow tasks", From d6c46847c2340c2e2755a81b0909e30a25052ea5 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Fri, 8 Dec 2023 22:03:11 +0300 Subject: [PATCH 13/94] src/assets/i18n/en.json5: minor changes for consistency Fix some random capitalizations and strange wording. --- src/assets/i18n/en.json5 | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index b3e1759c1b..7bf16c4ee8 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -4,15 +4,15 @@ "401.link.home-page": "Take me to the home page", - "401.unauthorized": "unauthorized", + "401.unauthorized": "Unauthorized", "403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.", "403.link.home-page": "Take me to the home page", - "403.forbidden": "forbidden", + "403.forbidden": "Forbidden", - "500.page-internal-server-error": "Service Unavailable", + "500.page-internal-server-error": "Service unavailable", "500.help": "The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.", @@ -22,15 +22,15 @@ "404.link.home-page": "Take me to the home page", - "404.page-not-found": "page not found", + "404.page-not-found": "Page not found", - "error-page.description.401": "unauthorized", + "error-page.description.401": "Unauthorized", - "error-page.description.403": "forbidden", + "error-page.description.403": "Forbidden", - "error-page.description.500": "Service Unavailable", + "error-page.description.500": "Service unavailable", - "error-page.description.404": "page not found", + "error-page.description.404": "Page not found", "error-page.orcid.generic-error": "An error occurred during login via ORCID. Make sure you have shared your ORCID account email address with DSpace. If the error persists, contact the administrator", @@ -58,7 +58,7 @@ "admin.registries.bitstream-formats.create.failure.head": "Failure", - "admin.registries.bitstream-formats.create.head": "Create Bitstream format", + "admin.registries.bitstream-formats.create.head": "Create bitstream format", "admin.registries.bitstream-formats.create.new": "Add a new bitstream format", @@ -300,7 +300,7 @@ "admin.access-control.epeople.form.email": "E-mail", - "admin.access-control.epeople.form.emailHint": "Must be valid e-mail address", + "admin.access-control.epeople.form.emailHint": "Must be a valid e-mail address", "admin.access-control.epeople.form.canLogIn": "Can log in", @@ -1192,9 +1192,9 @@ "community.edit.logo.label": "Community logo", - "community.edit.logo.notifications.add.error": "Uploading Community logo failed. Please verify the content before retrying.", + "community.edit.logo.notifications.add.error": "Uploading community logo failed. Please verify the content before retrying.", - "community.edit.logo.notifications.add.success": "Upload Community logo successful.", + "community.edit.logo.notifications.add.success": "Upload community logo successful.", "community.edit.logo.notifications.delete.success.title": "Logo deleted", @@ -1202,13 +1202,13 @@ "community.edit.logo.notifications.delete.error.title": "Error deleting logo", - "community.edit.logo.upload": "Drop a Community Logo to upload", + "community.edit.logo.upload": "Drop a community logo to upload", "community.edit.notifications.success": "Successfully edited the Community", "community.edit.notifications.unauthorized": "You do not have privileges to make this change", - "community.edit.notifications.error": "An error occured while editing the Community", + "community.edit.notifications.error": "An error occured while editing the community", "community.edit.return": "Back", @@ -1602,9 +1602,9 @@ "error.validation.required": "This field is required", - "error.validation.NotValidEmail": "This E-mail is not a valid email", + "error.validation.NotValidEmail": "This is not a valid e-mail", - "error.validation.emailTaken": "This E-mail is already taken", + "error.validation.emailTaken": "This e-mail is already taken", "error.validation.groupExists": "This group already exists", @@ -3034,7 +3034,7 @@ "mydspace.show.workflow": "Workflow tasks", - "mydspace.show.workspace": "Your Submissions", + "mydspace.show.workspace": "Your submissions", "mydspace.show.supervisedWorkspace": "Supervised items", @@ -3070,7 +3070,7 @@ "nav.login": "Log In", - "nav.user-profile-menu-and-logout": "User profile menu and Log Out", + "nav.user-profile-menu-and-logout": "User profile menu and log out", "nav.logout": "Log Out", From 2327513dd02db70c8a3e75388ef91028b341ba0d Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 15:12:22 +0200 Subject: [PATCH 14/94] Created AbstractComponentLoaderComponent to load components dynamically --- .../abstract-component-loader.component.html | 1 + .../abstract-component-loader.component.ts | 113 ++++++++++++++++++ .../dynamic-component-loader.directive.ts | 16 +++ src/app/shared/shared.module.ts | 2 + 4 files changed, 132 insertions(+) create mode 100644 src/app/shared/abstract-component-loader/abstract-component-loader.component.html create mode 100644 src/app/shared/abstract-component-loader/abstract-component-loader.component.ts create mode 100644 src/app/shared/abstract-component-loader/dynamic-component-loader.directive.ts diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.html b/src/app/shared/abstract-component-loader/abstract-component-loader.component.html new file mode 100644 index 0000000000..2035dbadd0 --- /dev/null +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.html @@ -0,0 +1 @@ + diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts new file mode 100644 index 0000000000..6f934f5b4b --- /dev/null +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -0,0 +1,113 @@ +import { Component, ComponentRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core'; +import { Context } from '../../core/shared/context.model'; +import { ThemeService } from '../theme-support/theme.service'; +import { GenericConstructor } from '../../core/shared/generic-constructor'; +import { hasNoValue, hasValue, isNotEmpty } from '../empty.util'; +import { Subscription } from 'rxjs'; +import { DynamicComponentLoaderDirective } from './dynamic-component-loader.directive'; + +@Component({ + selector: 'ds-abstract-component-loader', + templateUrl: './abstract-component-loader.component.html', +}) +export abstract class AbstractComponentLoaderComponent implements OnInit, OnChanges, OnDestroy { + + /** + * The optional context + */ + @Input() context: Context; + + /** + * Directive to determine where the dynamic child component is located + */ + @ViewChild(DynamicComponentLoaderDirective, { static: true }) componentDirective: DynamicComponentLoaderDirective; + + /** + * The reference to the dynamic component + */ + protected compRef: ComponentRef; + + /** + * Array to track all subscriptions and unsubscribe them onDestroy + */ + protected subs: Subscription[] = []; + + protected inAndOutputNames: (keyof this)[] = [ + 'context', + ]; + + constructor( + protected themeService: ThemeService, + ) { + } + + /** + * Set up the dynamic child component + */ + ngOnInit(): void { + this.instantiateComponent(); + } + + /** + * Whenever the inputs change, update the inputs of the dynamic component + */ + ngOnChanges(changes: SimpleChanges): void { + if (hasNoValue(this.compRef)) { + // sometimes the component has not been initialized yet, so it first needs to be initialized + // before being called again + this.instantiateComponent(changes); + } else { + // if an input or output has changed + if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { + this.connectInputsAndOutputs(); + if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { + (this.compRef.instance as any).ngOnChanges(changes); + } + } + } + } + + ngOnDestroy(): void { + this.subs + .filter((subscription: Subscription) => hasValue(subscription)) + .forEach((subscription: Subscription) => subscription.unsubscribe()); + } + + public instantiateComponent(changes?: SimpleChanges): void { + const component: GenericConstructor = this.getComponent(); + + const viewContainerRef: ViewContainerRef = this.componentDirective.viewContainerRef; + viewContainerRef.clear(); + + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + }, + ); + + if (hasValue(changes)) { + this.ngOnChanges(changes); + } else { + this.connectInputsAndOutputs(); + } + } + + /** + * Fetch the component depending on the item's entity type, metadata representation type and context + */ + public abstract getComponent(): GenericConstructor; + + /** + * Connect the in and outputs of this component to the dynamic component, + * to ensure they're in sync + */ + protected connectInputsAndOutputs(): void { + if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { + this.compRef.instance[name] = this[name]; + }); + } + } + +} diff --git a/src/app/shared/abstract-component-loader/dynamic-component-loader.directive.ts b/src/app/shared/abstract-component-loader/dynamic-component-loader.directive.ts new file mode 100644 index 0000000000..8c77df1cdb --- /dev/null +++ b/src/app/shared/abstract-component-loader/dynamic-component-loader.directive.ts @@ -0,0 +1,16 @@ +import { Directive, ViewContainerRef } from '@angular/core'; + +/** + * Directive used as a hook to know where to inject the dynamic loaded component + */ +@Directive({ + selector: '[dsDynamicComponentLoader]' +}) +export class DynamicComponentLoaderDirective { + + constructor( + public viewContainerRef: ViewContainerRef, + ) { + } + +} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 0f7871f7f9..2f9d3317fb 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -284,6 +284,7 @@ import { } from '../item-page/simple/field-components/specific-field/title/themed-item-page-field.component'; import { BitstreamListItemComponent } from './object-list/bitstream-list-item/bitstream-list-item.component'; import { NgxPaginationModule } from 'ngx-pagination'; +import { DynamicComponentLoaderDirective } from './abstract-component-loader/dynamic-component-loader.directive'; const MODULES = [ CommonModule, @@ -491,6 +492,7 @@ const DIRECTIVES = [ MetadataFieldValidator, HoverClassDirective, ContextHelpDirective, + DynamicComponentLoaderDirective, ]; @NgModule({ From fb7afaddd047c73ebf4cc97db70236e56f1a8f96 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 15:45:59 +0200 Subject: [PATCH 15/94] Make ListableObjectComponentLoaderComponent extend AbstractComponentLoaderComponent --- ...-search-result-grid-element.component.html | 2 +- ...in-search-result-grid-element.component.ts | 6 +- ...admin-workflow-grid-element.component.html | 2 +- ...in-workflow-grid-element.component.spec.ts | 12 +- ...t-admin-workflow-grid-element.component.ts | 6 +- ...admin-workflow-grid-element.component.html | 2 +- ...in-workflow-grid-element.component.spec.ts | 12 +- ...t-admin-workflow-grid-element.component.ts | 8 +- ...ble-object-component-loader.component.html | 1 - ...-object-component-loader.component.spec.ts | 22 +-- ...table-object-component-loader.component.ts | 135 +++--------------- .../listable-object.directive.ts | 11 -- src/app/shared/shared.module.ts | 2 - 13 files changed, 59 insertions(+), 162 deletions(-) delete mode 100644 src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.html delete mode 100644 src/app/shared/object-collection/shared/listable-object/listable-object.directive.ts diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html index c4b737849b..639c47f7f8 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts index dab6694f36..b6271b5ad5 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts @@ -11,7 +11,7 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; -import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; +import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { ThemeService } from '../../../../../shared/theme-support/theme.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; @@ -25,7 +25,7 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service * The component for displaying a list element for an item search result on the admin search page */ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnInit { - @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; + @ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective; @ViewChild('badges', { static: true }) badges: ElementRef; @ViewChild('buttons', { static: true }) buttons: ElementRef; @@ -46,7 +46,7 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE super.ngOnInit(); const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); - const viewContainerRef = this.listableObjectDirective.viewContainerRef; + const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef; viewContainerRef.clear(); const componentRef = viewContainerRef.createComponent( diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.html b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.html index 87bae0c261..c5c2a5331a 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.html +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.spec.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.spec.ts index 8035c53547..b02fa476ea 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.spec.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.spec.ts @@ -18,8 +18,8 @@ import { ItemGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component'; import { - ListableObjectDirective -} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; + DynamicComponentLoaderDirective +} from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; @@ -38,7 +38,7 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => { let itemRD$; let linkService; let object; - let themeService; + let themeService: ThemeService; function init() { itemRD$ = createSuccessfulRemoteDataObject$(new Item()); @@ -55,7 +55,11 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => { init(); TestBed.configureTestingModule( { - declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective], + declarations: [ + WorkflowItemSearchResultAdminWorkflowGridElementComponent, + ItemGridElementComponent, + DynamicComponentLoaderDirective, + ], imports: [ NoopAnimationsModule, TranslateModule.forRoot(), diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts index fd9d21e227..d4a69b829d 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts @@ -10,7 +10,7 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; -import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; +import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model'; import { Observable } from 'rxjs'; import { LinkService } from '../../../../../core/cache/builders/link.service'; @@ -38,7 +38,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S /** * Directive used to render the dynamic component in */ - @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; + @ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective; /** * The html child that contains the badges html @@ -77,7 +77,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S this.item$.pipe(take(1)).subscribe((item: Item) => { const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item)); - const viewContainerRef = this.listableObjectDirective.viewContainerRef; + const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef; viewContainerRef.clear(); const componentRef = viewContainerRef.createComponent( diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.html b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.html index 767ad79786..f78a0a3ca4 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.html +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.spec.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.spec.ts index b9e752c104..d023e57709 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.spec.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.spec.ts @@ -20,8 +20,8 @@ import { ItemGridElementComponent } from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component'; import { - ListableObjectDirective -} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; + DynamicComponentLoaderDirective +} from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; @@ -45,7 +45,7 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => { let itemRD$; let linkService; let object; - let themeService; + let themeService: ThemeService; let supervisionOrderDataService; function init() { @@ -67,7 +67,11 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => { init(); TestBed.configureTestingModule( { - declarations: [WorkspaceItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective], + declarations: [ + WorkspaceItemSearchResultAdminWorkflowGridElementComponent, + ItemGridElementComponent, + DynamicComponentLoaderDirective, + ], imports: [ NoopAnimationsModule, TranslateModule.forRoot(), diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts index d6f39e79fe..f74d8b3e5c 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts @@ -16,9 +16,7 @@ import { import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service'; import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service'; import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; -import { - ListableObjectDirective -} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; +import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model'; import { LinkService } from '../../../../../core/cache/builders/link.service'; import { followLink } from '../../../../../shared/utils/follow-link-config.model'; @@ -67,7 +65,7 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends /** * Directive used to render the dynamic component in */ - @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; + @ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective; /** * The html child that contains the badges html @@ -102,7 +100,7 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends this.item$.pipe(take(1)).subscribe((item: Item) => { const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item)); - const viewContainerRef = this.listableObjectDirective.viewContainerRef; + const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef; viewContainerRef.clear(); const componentRef = viewContainerRef.createComponent( diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.html b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.html deleted file mode 100644 index 58561f0277..0000000000 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts index e893fe807b..7b9d010e39 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts @@ -8,7 +8,7 @@ import { ViewMode } from '../../../../core/shared/view-mode.model'; import { ItemListElementComponent } from '../../../object-list/item-list-element/item-types/item/item-list-element.component'; -import { ListableObjectDirective } from './listable-object.directive'; +import { DynamicComponentLoaderDirective } from '../../../abstract-component-loader/dynamic-component-loader.directive'; import { TranslateModule } from '@ngx-translate/core'; import { By } from '@angular/platform-browser'; import { provideMockStore } from '@ngrx/store/testing'; @@ -36,7 +36,7 @@ describe('ListableObjectComponentLoaderComponent', () => { }); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, ListableObjectDirective], + declarations: [ListableObjectComponentLoaderComponent, ItemListElementComponent, DynamicComponentLoaderDirective], schemas: [NO_ERRORS_SCHEMA], providers: [ provideMockStore({}), @@ -65,7 +65,7 @@ describe('ListableObjectComponentLoaderComponent', () => { describe('When the component is rendered', () => { it('should call the getListableObjectComponent function with the right types, view mode and context', () => { - expect(comp.getComponent).toHaveBeenCalledWith([testType], testViewMode, testContext); + expect(comp.getComponent).toHaveBeenCalled(); }); it('should connectInputsAndOutputs of loaded component', () => { @@ -78,29 +78,29 @@ describe('ListableObjectComponentLoaderComponent', () => { let reloadedObject: any; beforeEach(() => { - spyOn((comp as any), 'instantiateComponent').and.returnValue(null); - spyOn((comp as any).contentChange, 'emit').and.returnValue(null); + spyOn(comp, 'instantiateComponent').and.returnValue(null); + spyOn(comp.contentChange, 'emit').and.returnValue(null); listableComponent = fixture.debugElement.query(By.css('ds-item-list-element')).componentInstance; reloadedObject = 'object'; }); it('should re-instantiate the listable component', fakeAsync(() => { - expect((comp as any).instantiateComponent).not.toHaveBeenCalled(); + expect(comp.instantiateComponent).not.toHaveBeenCalled(); - (listableComponent as any).reloadedObject.emit(reloadedObject); + listableComponent.reloadedObject.emit(reloadedObject); tick(200); - expect((comp as any).instantiateComponent).toHaveBeenCalledWith(reloadedObject, undefined); + expect(comp.instantiateComponent).toHaveBeenCalledWith(undefined); })); it('should re-emit it as a contentChange', fakeAsync(() => { - expect((comp as any).contentChange.emit).not.toHaveBeenCalled(); + expect(comp.contentChange.emit).not.toHaveBeenCalled(); - (listableComponent as any).reloadedObject.emit(reloadedObject); + listableComponent.reloadedObject.emit(reloadedObject); tick(200); - expect((comp as any).contentChange.emit).toHaveBeenCalledWith(reloadedObject); + expect(comp.contentChange.emit).toHaveBeenCalledWith(reloadedObject); })); }); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 7a3cc42bf5..f62ee44a1c 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,40 +1,26 @@ -import { - ChangeDetectorRef, - Component, - ComponentRef, - EventEmitter, - Input, - OnChanges, - OnDestroy, - OnInit, - Output, - SimpleChanges, - ViewChild -} from '@angular/core'; - -import { Subscription, combineLatest, of as observableOf, Observable } from 'rxjs'; +import { ChangeDetectorRef, Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core'; +import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { take } from 'rxjs/operators'; - import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; -import { Context } from '../../../../core/shared/context.model'; +import { Context } from 'src/app/core/shared/context.model'; import { getListableObjectComponent } from './listable-object.decorator'; import { GenericConstructor } from '../../../../core/shared/generic-constructor'; -import { ListableObjectDirective } from './listable-object.directive'; import { CollectionElementLinkType } from '../../collection-element-link.type'; -import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; -import { ThemeService } from '../../../theme-support/theme.service'; +import { AbstractComponentLoaderComponent } from '../../../abstract-component-loader/abstract-component-loader.component'; +import { ThemeService } from 'src/app/shared/theme-support/theme.service'; @Component({ selector: 'ds-listable-object-component-loader', styleUrls: ['./listable-object-component-loader.component.scss'], - templateUrl: './listable-object-component-loader.component.html' + templateUrl: '../../../abstract-component-loader/abstract-component-loader.component.html', }) /** * Component for determining what component to use depending on the item's entity type (dspace.entity.type) */ -export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges, OnDestroy { +export class ListableObjectComponentLoaderComponent extends AbstractComponentLoaderComponent { + /** * The item or metadata to determine the component for */ @@ -80,99 +66,36 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges */ @Input() value: string; - /** - * Directive hook used to place the dynamic child component - */ - @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; - /** * Emit when the listable object has been reloaded. */ @Output() contentChange = new EventEmitter(); - /** - * Array to track all subscriptions and unsubscribe them onDestroy - * @type {Array} - */ - protected subs: Subscription[] = []; - - /** - * The reference to the dynamic component - */ - protected compRef: ComponentRef; - /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: string[] = [ + protected inAndOutputNames: (keyof this)[] = [ + ...this.inAndOutputNames, 'object', 'index', 'linkType', 'listID', 'showLabel', 'showThumbnails', - 'context', 'viewMode', 'value', - 'hideBadges', 'contentChange', ]; - constructor(private cdr: ChangeDetectorRef, private themeService: ThemeService) { + constructor( + protected themeService: ThemeService, + protected cdr: ChangeDetectorRef, + ) { + super(themeService); } - /** - * Setup the dynamic child component - */ - ngOnInit(): void { - this.instantiateComponent(this.object); - } - - /** - * Whenever the inputs change, update the inputs of the dynamic component - */ - ngOnChanges(changes: SimpleChanges): void { - if (hasNoValue(this.compRef)) { - // sometimes the component has not been initialized yet, so it first needs to be initialized - // before being called again - this.instantiateComponent(this.object, changes); - } else { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { - this.connectInputsAndOutputs(); - if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); - } - } - } - } - - ngOnDestroy() { - this.subs - .filter((subscription) => hasValue(subscription)) - .forEach((subscription) => subscription.unsubscribe()); - } - - private instantiateComponent(object: ListableObject, changes?: SimpleChanges): void { - - const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context); - - const viewContainerRef = this.listableObjectDirective.viewContainerRef; - viewContainerRef.clear(); - - this.compRef = viewContainerRef.createComponent( - component, { - index: 0, - injector: undefined - } - ); - - if (hasValue(changes)) { - this.ngOnChanges(changes); - } else { - this.connectInputsAndOutputs(); - } - + public instantiateComponent(changes?: SimpleChanges): void { + super.instantiateComponent(changes); if ((this.compRef.instance as any).reloadedObject) { combineLatest([ observableOf(changes), @@ -181,7 +104,7 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges if (reloadedObject) { this.compRef.destroy(); this.object = reloadedObject; - this.instantiateComponent(reloadedObject, simpleChanges); + this.instantiateComponent(simpleChanges); this.cdr.detectChanges(); this.contentChange.emit(reloadedObject); } @@ -189,26 +112,8 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges } } - /** - * Fetch the component depending on the item's entity type, view mode and context - * @returns {GenericConstructor} - */ - getComponent(renderTypes: (string | GenericConstructor)[], - viewMode: ViewMode, - context: Context): GenericConstructor { - return getListableObjectComponent(renderTypes, viewMode, context, this.themeService.getThemeName()); - } - - /** - * Connect the in and outputs of this component to the dynamic component, - * to ensure they're in sync - */ - protected connectInputsAndOutputs(): void { - if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { - this.compRef.instance[name] = this[name]; - }); - } + public getComponent(): GenericConstructor { + return getListableObjectComponent(this.object.getRenderTypes(), this.viewMode, this.context, this.themeService.getThemeName()); } } diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object.directive.ts b/src/app/shared/object-collection/shared/listable-object/listable-object.directive.ts deleted file mode 100644 index 93c06961f4..0000000000 --- a/src/app/shared/object-collection/shared/listable-object/listable-object.directive.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Directive, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[dsListableObject]', -}) -/** - * Directive used as a hook to know where to inject the dynamic listable object component - */ -export class ListableObjectDirective { - constructor(public viewContainerRef: ViewContainerRef) { } -} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 2f9d3317fb..f185847596 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -177,7 +177,6 @@ import { import { ItemSearchResultListElementComponent } from './object-list/search-result-list-element/item-search-result/item-types/item/item-search-result-list-element.component'; -import { ListableObjectDirective } from './object-collection/shared/listable-object/listable-object.directive'; import { ItemMetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component'; @@ -484,7 +483,6 @@ const DIRECTIVES = [ AutoFocusDirective, RoleDirective, MetadataRepresentationDirective, - ListableObjectDirective, ClaimedTaskActionsDirective, FileValueAccessorDirective, FileValidator, From fe60adb47f47f304433599e97df9f6b50320ab70 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 15:58:31 +0200 Subject: [PATCH 16/94] Make ClaimedTaskActionsLoaderComponent extend AbstractComponentLoaderComponent --- ...claimed-task-actions-loader.component.html | 1 - ...imed-task-actions-loader.component.spec.ts | 22 +++-- .../claimed-task-actions-loader.component.ts | 98 +++---------------- .../claimed-task-actions.directive.ts | 11 --- src/app/shared/shared.module.ts | 2 - 5 files changed, 27 insertions(+), 107 deletions(-) delete mode 100644 src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.html delete mode 100644 src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions.directive.ts diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.html b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.html deleted file mode 100644 index 364443c47f..0000000000 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts index 95e31f5523..e48983d449 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.spec.ts @@ -1,7 +1,7 @@ import { ClaimedTaskActionsLoaderComponent } from './claimed-task-actions-loader.component'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; -import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive'; +import { DynamicComponentLoaderDirective } from '../../../abstract-component-loader/dynamic-component-loader.directive'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { TranslateModule } from '@ngx-translate/core'; import { ClaimedTaskActionsEditMetadataComponent } from '../edit-metadata/claimed-task-actions-edit-metadata.component'; @@ -17,6 +17,8 @@ import { getMockSearchService } from '../../../mocks/search-service.mock'; import { getMockRequestService } from '../../../mocks/request.service.mock'; import { Item } from '../../../../core/shared/item.model'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; +import { ThemeService } from 'src/app/shared/theme-support/theme.service'; +import { getMockThemeService } from '../../../mocks/theme-service.mock'; const searchService = getMockSearchService(); @@ -25,6 +27,7 @@ const requestService = getMockRequestService(); describe('ClaimedTaskActionsLoaderComponent', () => { let comp: ClaimedTaskActionsLoaderComponent; let fixture: ComponentFixture; + let themeService: ThemeService; const option = 'test_option'; const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' }); @@ -61,9 +64,15 @@ describe('ClaimedTaskActionsLoaderComponent', () => { const workflowitem = Object.assign(new WorkflowItem(), { id: '333' }); beforeEach(waitForAsync(() => { + themeService = getMockThemeService('dspace'); + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot()], - declarations: [ClaimedTaskActionsLoaderComponent, ClaimedTaskActionsEditMetadataComponent, ClaimedTaskActionsDirective], + declarations: [ + ClaimedTaskActionsLoaderComponent, + ClaimedTaskActionsEditMetadataComponent, + DynamicComponentLoaderDirective, + ], schemas: [NO_ERRORS_SCHEMA], providers: [ { provide: ClaimedTaskDataService, useValue: {} }, @@ -72,7 +81,8 @@ describe('ClaimedTaskActionsLoaderComponent', () => { { provide: Router, useValue: new RouterStub() }, { provide: SearchService, useValue: searchService }, { provide: RequestService, useValue: requestService }, - { provide: PoolTaskDataService, useValue: {} } + { provide: PoolTaskDataService, useValue: {} }, + { provide: ThemeService, useValue: themeService }, ] }).overrideComponent(ClaimedTaskActionsLoaderComponent, { set: { @@ -89,14 +99,14 @@ describe('ClaimedTaskActionsLoaderComponent', () => { comp.object = object; comp.option = option; comp.workflowitem = workflowitem; - spyOn(comp, 'getComponentByWorkflowTaskOption').and.returnValue(ClaimedTaskActionsEditMetadataComponent); + spyOn(comp, 'getComponent').and.returnValue(ClaimedTaskActionsEditMetadataComponent); fixture.detectChanges(); })); describe('When the component is rendered', () => { - it('should call the getComponentByWorkflowTaskOption function with the right option', () => { - expect(comp.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(option); + it('should call the getComponent function', () => { + expect(comp.getComponent).toHaveBeenCalled(); }); }); }); diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index c0dc1cad02..75b392fe51 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -1,33 +1,22 @@ -import { - Component, - ComponentFactoryResolver, - EventEmitter, - Input, - OnInit, - Output, - ViewChild, - OnChanges, - SimpleChanges, - ComponentRef, -} from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, } from '@angular/core'; import { getComponentByWorkflowTaskOption } from './claimed-task-actions-decorator'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; -import { ClaimedTaskActionsDirective } from './claimed-task-actions.directive'; -import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util'; import { MyDSpaceActionsResult } from '../../mydspace-actions'; import { Item } from '../../../../core/shared/item.model'; import { WorkflowItem } from '../../../../core/submission/models/workflowitem.model'; import { ClaimedTaskActionsAbstractComponent } from '../abstract/claimed-task-actions-abstract.component'; +import { AbstractComponentLoaderComponent } from '../../../abstract-component-loader/abstract-component-loader.component'; +import { GenericConstructor } from '../../../../core/shared/generic-constructor'; @Component({ selector: 'ds-claimed-task-actions-loader', - templateUrl: './claimed-task-actions-loader.component.html' + templateUrl: '../../../abstract-component-loader/abstract-component-loader.component.html', }) /** * Component for loading a ClaimedTaskAction component depending on the "option" input * Passes on the ClaimedTask to the component and subscribes to the processCompleted output */ -export class ClaimedTaskActionsLoaderComponent implements OnInit, OnChanges { +export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderComponent implements OnInit, OnChanges { /** * The item object that belonging to the ClaimedTask object */ @@ -54,85 +43,20 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnChanges { */ @Output() processCompleted = new EventEmitter(); - /** - * Directive to determine where the dynamic child component is located - */ - @ViewChild(ClaimedTaskActionsDirective, {static: true}) claimedTaskActionsDirective: ClaimedTaskActionsDirective; - - /** - * The reference to the dynamic component - */ - protected compRef: ComponentRef; - /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: (keyof ClaimedTaskActionsAbstractComponent & keyof this)[] = [ + protected inAndOutputNames: (keyof this)[] = [ + ...this.inAndOutputNames, + 'item', 'object', 'option', + 'workflowitem', 'processCompleted', ]; - constructor(private componentFactoryResolver: ComponentFactoryResolver) { + public getComponent(): GenericConstructor { + return getComponentByWorkflowTaskOption(this.option); } - /** - * Fetch, create and initialize the relevant component - */ - ngOnInit(): void { - this.instantiateComponent(); - } - - /** - * Whenever the inputs change, update the inputs of the dynamic component - */ - ngOnChanges(changes: SimpleChanges): void { - if (hasNoValue(this.compRef)) { - // sometimes the component has not been initialized yet, so it first needs to be initialized - // before being called again - this.instantiateComponent(changes); - } else { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { - this.connectInputsAndOutputs(); - if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); - } - } - } - } - - private instantiateComponent(changes?: SimpleChanges): void { - const comp = this.getComponentByWorkflowTaskOption(this.option); - if (hasValue(comp)) { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp); - - const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef; - viewContainerRef.clear(); - - this.compRef = viewContainerRef.createComponent(componentFactory); - - if (hasValue(changes)) { - this.ngOnChanges(changes); - } else { - this.connectInputsAndOutputs(); - } - } - } - - getComponentByWorkflowTaskOption(option: string) { - return getComponentByWorkflowTaskOption(option); - } - - /** - * Connect the in and outputs of this component to the dynamic component, - * to ensure they're in sync - */ - protected connectInputsAndOutputs(): void { - if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { - this.compRef.instance[name] = this[name]; - }); - } - } } diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions.directive.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions.directive.ts deleted file mode 100644 index a4a55b541b..0000000000 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions.directive.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Directive, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[dsClaimedTaskActions]', -}) -/** - * Directive used as a hook to know where to inject the dynamic Claimed Task Actions component - */ -export class ClaimedTaskActionsDirective { - constructor(public viewContainerRef: ViewContainerRef) { } -} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index f185847596..c00b09e20f 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -197,7 +197,6 @@ import { FileValueAccessorDirective } from './utils/file-value-accessor.directiv import { ModifyItemOverviewComponent } from '../item-page/edit-item-page/modify-item-overview/modify-item-overview.component'; -import { ClaimedTaskActionsDirective } from './mydspace-actions/claimed-task/switcher/claimed-task-actions.directive'; import { ImpersonateNavbarComponent } from './impersonate-navbar/impersonate-navbar.component'; import { NgForTrackByIdDirective } from './ng-for-track-by-id.directive'; import { FileDownloadLinkComponent } from './file-download-link/file-download-link.component'; @@ -483,7 +482,6 @@ const DIRECTIVES = [ AutoFocusDirective, RoleDirective, MetadataRepresentationDirective, - ClaimedTaskActionsDirective, FileValueAccessorDirective, FileValidator, NgForTrackByIdDirective, From 774784a9b949943c8bdd87ead3dff280acac115e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 16:03:07 +0200 Subject: [PATCH 17/94] Make AdvancedWorkflowActionsLoaderComponent extend AbstractComponentLoaderComponent --- ...ced-workflow-actions-loader.component.html | 1 - ...ced-workflow-actions-loader.component.scss | 0 ...-workflow-actions-loader.component.spec.ts | 17 ++++++--- ...anced-workflow-actions-loader.component.ts | 38 ++++++++----------- .../advanced-workflow-actions.directive.ts | 16 -------- .../workflowitems-edit-page.module.ts | 4 -- 6 files changed, 27 insertions(+), 49 deletions(-) delete mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html delete mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.scss delete mode 100644 src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html deleted file mode 100644 index 0904d0fcde..0000000000 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.scss b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts index 2c12b07589..3188d00891 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.spec.ts @@ -3,12 +3,14 @@ import { AdvancedWorkflowActionsLoaderComponent } from './advanced-workflow-acti import { Router } from '@angular/router'; import { RouterStub } from '../../../shared/testing/router.stub'; import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive'; +import { DynamicComponentLoaderDirective } from '../../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { rendersAdvancedWorkflowTaskOption } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; import { By } from '@angular/platform-browser'; import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; +import { ThemeService } from 'src/app/shared/theme-support/theme.service'; +import { getMockThemeService } from 'src/app/shared/mocks/theme-service.mock'; const ADVANCED_WORKFLOW_ACTION_TEST = 'testaction'; @@ -17,17 +19,20 @@ describe('AdvancedWorkflowActionsLoaderComponent', () => { let fixture: ComponentFixture; let router: RouterStub; + let themeService: ThemeService; beforeEach(async () => { router = new RouterStub(); + themeService = getMockThemeService(); await TestBed.configureTestingModule({ declarations: [ - AdvancedWorkflowActionsDirective, + DynamicComponentLoaderDirective, AdvancedWorkflowActionsLoaderComponent, ], providers: [ { provide: Router, useValue: router }, + { provide: ThemeService, useValue: themeService }, ], }).overrideComponent(AdvancedWorkflowActionsLoaderComponent, { set: { @@ -50,24 +55,24 @@ describe('AdvancedWorkflowActionsLoaderComponent', () => { describe('When the component is rendered', () => { it('should display the AdvancedWorkflowActionTestComponent when the type has been defined in a rendersAdvancedWorkflowTaskOption', () => { - spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(AdvancedWorkflowActionTestComponent); + spyOn(component, 'getComponent').and.returnValue(AdvancedWorkflowActionTestComponent); component.ngOnInit(); fixture.detectChanges(); - expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith(ADVANCED_WORKFLOW_ACTION_TEST); + expect(component.getComponent).toHaveBeenCalled(); expect(fixture.debugElement.query(By.css('#AdvancedWorkflowActionsLoaderComponent'))).not.toBeNull(); expect(router.navigate).not.toHaveBeenCalled(); }); it('should redirect to page not found when the type has not been defined in a rendersAdvancedWorkflowTaskOption', () => { - spyOn(component, 'getComponentByWorkflowTaskOption').and.returnValue(undefined); + spyOn(component, 'getComponent').and.returnValue(undefined); component.type = 'nonexistingaction'; component.ngOnInit(); fixture.detectChanges(); - expect(component.getComponentByWorkflowTaskOption).toHaveBeenCalledWith('nonexistingaction'); + expect(component.getComponent).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]); }); }); diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts index 32f14c015d..10cae7ec7d 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts @@ -1,21 +1,22 @@ -import { Component, Input, ViewChild, ComponentFactoryResolver, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { hasValue } from '../../../shared/empty.util'; import { getAdvancedComponentByWorkflowTaskOption } from '../../../shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-decorator'; -import { AdvancedWorkflowActionsDirective } from './advanced-workflow-actions.directive'; import { Router } from '@angular/router'; import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; +import { GenericConstructor } from '../../../core/shared/generic-constructor'; +import { AbstractComponentLoaderComponent } from 'src/app/shared/abstract-component-loader/abstract-component-loader.component'; +import { ThemeService } from '../../../shared/theme-support/theme.service'; /** * Component for loading a {@link AdvancedWorkflowActionComponent} depending on the "{@link type}" input */ @Component({ selector: 'ds-advanced-workflow-actions-loader', - templateUrl: './advanced-workflow-actions-loader.component.html', - styleUrls: ['./advanced-workflow-actions-loader.component.scss'], + templateUrl: '../../../shared/abstract-component-loader/abstract-component-loader.component.html', }) -export class AdvancedWorkflowActionsLoaderComponent implements OnInit { +export class AdvancedWorkflowActionsLoaderComponent extends AbstractComponentLoaderComponent implements OnInit { /** * The name of the type to render @@ -23,35 +24,28 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { */ @Input() type: string; - /** - * Directive to determine where the dynamic child component is located - */ - @ViewChild(AdvancedWorkflowActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedWorkflowActionsDirective; + protected inAndOutputNames: (keyof this)[] = [ + ...this.inAndOutputNames, + 'type', + ]; constructor( - private componentFactoryResolver: ComponentFactoryResolver, + protected themeService: ThemeService, private router: Router, ) { + super(themeService); } - /** - * Fetch, create and initialize the relevant component - */ ngOnInit(): void { - const comp = this.getComponentByWorkflowTaskOption(this.type); - if (hasValue(comp)) { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp); - - const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef; - viewContainerRef.clear(); - viewContainerRef.createComponent(componentFactory); + if (hasValue(this.getComponent())) { + super.ngOnInit(); } else { void this.router.navigate([PAGE_NOT_FOUND_PATH]); } } - getComponentByWorkflowTaskOption(type: string): any { - return getAdvancedComponentByWorkflowTaskOption(type); + public getComponent(): GenericConstructor { + return getAdvancedComponentByWorkflowTaskOption(this.type); } } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts deleted file mode 100644 index e569f6cc6f..0000000000 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Directive, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[dsAdvancedWorkflowActions]', -}) -/** - * Directive used as a hook to know where to inject the dynamic Advanced Claimed Task Actions component - */ -export class AdvancedWorkflowActionsDirective { - - constructor( - public viewContainerRef: ViewContainerRef, - ) { - } - -} diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts index cf998c5274..c2bd3d5ad7 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page.module.ts @@ -23,9 +23,6 @@ import { import { AdvancedWorkflowActionPageComponent } from './advanced-workflow-action/advanced-workflow-action-page/advanced-workflow-action-page.component'; -import { - AdvancedWorkflowActionsDirective -} from './advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions.directive'; import { AccessControlModule } from '../access-control/access-control.module'; import { ReviewersListComponent @@ -54,7 +51,6 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; AdvancedWorkflowActionRatingComponent, AdvancedWorkflowActionSelectReviewerComponent, AdvancedWorkflowActionPageComponent, - AdvancedWorkflowActionsDirective, ReviewersListComponent, ] }) From 2bae174350689887de4b856ab3ebae20b2389586 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 10 Jun 2023 16:14:54 +0200 Subject: [PATCH 18/94] Make MetadataRepresentationLoaderComponent extend AbstractComponentLoaderComponent --- ...adata-representation-loader.component.html | 1 - ...ta-representation-loader.component.spec.ts | 18 ++-- ...etadata-representation-loader.component.ts | 99 ++----------------- .../metadata-representation.directive.ts | 11 --- src/app/shared/shared.module.ts | 2 - 5 files changed, 20 insertions(+), 111 deletions(-) delete mode 100644 src/app/shared/metadata-representation/metadata-representation-loader.component.html delete mode 100644 src/app/shared/metadata-representation/metadata-representation.directive.ts diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.html b/src/app/shared/metadata-representation/metadata-representation-loader.component.html deleted file mode 100644 index 3979c238ad..0000000000 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts index 7edf1a700e..c9bec402d1 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.spec.ts @@ -6,10 +6,11 @@ import { MetadataRepresentationType } from '../../core/shared/metadata-representation/metadata-representation.model'; import { MetadataRepresentationLoaderComponent } from './metadata-representation-loader.component'; -import { MetadataRepresentationDirective } from './metadata-representation.directive'; +import { DynamicComponentLoaderDirective } from '../abstract-component-loader/dynamic-component-loader.directive'; import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representation.decorator'; import { ThemeService } from '../theme-support/theme.service'; import { PlainTextMetadataListElementComponent } from '../object-list/metadata-representation-list-element/plain-text/plain-text-metadata-list-element.component'; +import { getMockThemeService } from '../mocks/theme-service.mock'; const testType = 'TestType'; const testContext = Context.Search; @@ -36,12 +37,14 @@ describe('MetadataRepresentationLoaderComponent', () => { const themeName = 'test-theme'; beforeEach(waitForAsync(() => { - themeService = jasmine.createSpyObj('themeService', { - getThemeName: themeName, - }); + themeService = getMockThemeService(themeName); TestBed.configureTestingModule({ imports: [], - declarations: [MetadataRepresentationLoaderComponent, PlainTextMetadataListElementComponent, MetadataRepresentationDirective], + declarations: [ + MetadataRepresentationLoaderComponent, + PlainTextMetadataListElementComponent, + DynamicComponentLoaderDirective, + ], schemas: [NO_ERRORS_SCHEMA], providers: [ { @@ -64,6 +67,7 @@ describe('MetadataRepresentationLoaderComponent', () => { beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(MetadataRepresentationLoaderComponent); comp = fixture.componentInstance; + spyOn(comp, 'getComponent').and.callThrough(); comp.mdRepresentation = new TestType(); comp.context = testContext; @@ -71,8 +75,8 @@ describe('MetadataRepresentationLoaderComponent', () => { })); describe('When the component is rendered', () => { - it('should call the getMetadataRepresentationComponent function with the right entity type, representation type and context', () => { - expect((comp as any).getMetadataRepresentationComponent).toHaveBeenCalledWith(testType, testRepresentationType, testContext, themeName); + it('should call the getComponent function', () => { + expect(comp.getComponent).toHaveBeenCalled(); }); }); }); diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts index 42ee093278..83542512ed 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, Inject, Input, OnInit, ViewChild, OnChanges, SimpleChanges, ComponentRef, ViewContainerRef, ComponentFactory } from '@angular/core'; +import { Component, Inject, Input } from '@angular/core'; import { MetadataRepresentation, MetadataRepresentationType @@ -7,118 +7,37 @@ import { METADATA_REPRESENTATION_COMPONENT_FACTORY } from './metadata-representa import { Context } from '../../core/shared/context.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { MetadataRepresentationListElementComponent } from '../object-list/metadata-representation-list-element/metadata-representation-list-element.component'; -import { MetadataRepresentationDirective } from './metadata-representation.directive'; -import { hasValue, isNotEmpty, hasNoValue } from '../empty.util'; import { ThemeService } from '../theme-support/theme.service'; +import { AbstractComponentLoaderComponent } from '../abstract-component-loader/abstract-component-loader.component'; @Component({ selector: 'ds-metadata-representation-loader', - templateUrl: './metadata-representation-loader.component.html' + templateUrl: '../abstract-component-loader/abstract-component-loader.component.html', }) /** * Component for determining what component to use depending on the item's entity type (dspace.entity.type), its metadata representation and, optionally, its context */ -export class MetadataRepresentationLoaderComponent implements OnInit, OnChanges { +export class MetadataRepresentationLoaderComponent extends AbstractComponentLoaderComponent { /** * The item or metadata to determine the component for */ - private _mdRepresentation: MetadataRepresentation; - get mdRepresentation(): MetadataRepresentation { - return this._mdRepresentation; - } - @Input() set mdRepresentation(nextValue: MetadataRepresentation) { - this._mdRepresentation = nextValue; - if (hasValue(this.compRef?.instance)) { - this.compRef.instance.mdRepresentation = nextValue; - } - } - - /** - * The optional context - */ - @Input() context: Context; - - /** - * Directive to determine where the dynamic child component is located - */ - @ViewChild(MetadataRepresentationDirective, {static: true}) mdRepDirective: MetadataRepresentationDirective; - - /** - * The reference to the dynamic component - */ - protected compRef: ComponentRef; + @Input() mdRepresentation: MetadataRepresentation; protected inAndOutputNames: (keyof this)[] = [ - 'context', + ...this.inAndOutputNames, 'mdRepresentation', ]; constructor( - private componentFactoryResolver: ComponentFactoryResolver, - private themeService: ThemeService, + protected themeService: ThemeService, @Inject(METADATA_REPRESENTATION_COMPONENT_FACTORY) private getMetadataRepresentationComponent: (entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context, theme: string) => GenericConstructor, ) { + super(themeService); } - /** - * Set up the dynamic child component - */ - ngOnInit(): void { - this.instantiateComponent(); - } - - /** - * Whenever the inputs change, update the inputs of the dynamic component - */ - ngOnChanges(changes: SimpleChanges): void { - if (hasNoValue(this.compRef)) { - // sometimes the component has not been initialized yet, so it first needs to be initialized - // before being called again - this.instantiateComponent(changes); - } else { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { - this.connectInputsAndOutputs(); - if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); - } - } - } - } - - private instantiateComponent(changes?: SimpleChanges): void { - const componentFactory: ComponentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); - - const viewContainerRef: ViewContainerRef = this.mdRepDirective.viewContainerRef; - viewContainerRef.clear(); - - this.compRef = viewContainerRef.createComponent(componentFactory); - - if (hasValue(changes)) { - this.ngOnChanges(changes); - } else { - this.connectInputsAndOutputs(); - } - } - - /** - * Fetch the component depending on the item's entity type, metadata representation type and context - * @returns {string} - */ - private getComponent(): GenericConstructor { + public getComponent(): GenericConstructor { return this.getMetadataRepresentationComponent(this.mdRepresentation.itemType, this.mdRepresentation.representationType, this.context, this.themeService.getThemeName()); } - /** - * Connect the in and outputs of this component to the dynamic component, - * to ensure they're in sync - */ - protected connectInputsAndOutputs(): void { - if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { - this.compRef.instance[name] = this[name]; - }); - } - } } diff --git a/src/app/shared/metadata-representation/metadata-representation.directive.ts b/src/app/shared/metadata-representation/metadata-representation.directive.ts deleted file mode 100644 index 9ff0573baf..0000000000 --- a/src/app/shared/metadata-representation/metadata-representation.directive.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Directive, ViewContainerRef } from '@angular/core'; - -@Directive({ - selector: '[dsMetadataRepresentation]', -}) -/** - * Directive used as a hook to know where to inject the dynamic metadata representation component - */ -export class MetadataRepresentationDirective { - constructor(public viewContainerRef: ViewContainerRef) { } -} diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index c00b09e20f..bce7282e8b 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -170,7 +170,6 @@ import { AccessStatusBadgeComponent } from './object-collection/shared/badges/ac import { MetadataRepresentationLoaderComponent } from './metadata-representation/metadata-representation-loader.component'; -import { MetadataRepresentationDirective } from './metadata-representation/metadata-representation.directive'; import { ListableObjectComponentLoaderComponent } from './object-collection/shared/listable-object/listable-object-component-loader.component'; @@ -481,7 +480,6 @@ const DIRECTIVES = [ InListValidator, AutoFocusDirective, RoleDirective, - MetadataRepresentationDirective, FileValueAccessorDirective, FileValidator, NgForTrackByIdDirective, From 26e0bc81c7a58548d8557a3a672891b107f250d5 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 9 Dec 2023 22:11:50 +0100 Subject: [PATCH 19/94] Removed Themed components from PR 1842 because those should be themed using the decorator rendersBrowseBy(browseType, themeName) and fixed the components in the custom theme folder - Removed ngOnDestroy from BrowseByTitlePageComponent because super.ngOnDestroy already included the unsubscribe functionality - Removed BrowseBySwitcherComponent since a themed version of that isn't really useful --- .../browse-by-date-page.component.ts | 6 ++-- .../themed-browse-by-date-page.component.ts | 29 ------------------- .../browse-by-metadata-page.component.ts | 2 ++ ...hemed-browse-by-metadata-page.component.ts | 29 ------------------- .../browse-by-switcher/browse-by-decorator.ts | 3 +- .../themed-browse-by-switcher.component.ts | 28 ------------------ .../browse-by-taxonomy-page.component.ts | 3 +- ...hemed-browse-by-taxonomy-page.component.ts | 28 ------------------ .../browse-by-title-page.component.ts | 11 +++---- .../themed-browse-by-title-page.component.ts | 29 ------------------- src/app/browse-by/browse-by.module.ts | 9 ------ .../browse-by-date-page.component.ts | 7 ++--- .../browse-by-metadata-page.component.ts | 7 ++--- .../browse-by-switcher.component.ts | 15 ---------- .../browse-by-taxonomy-page.component.ts | 5 ++-- .../browse-by-title-page.component.ts | 7 ++--- src/themes/custom/lazy-theme.module.ts | 2 -- 17 files changed, 22 insertions(+), 198 deletions(-) delete mode 100644 src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts delete mode 100644 src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts delete mode 100644 src/app/browse-by/browse-by-switcher/themed-browse-by-switcher.component.ts delete mode 100644 src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts delete mode 100644 src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts delete mode 100644 src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index 7074190e1e..b106803351 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectorRef, Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core'; import { BrowseByMetadataPageComponent, browseParamsToOptions, @@ -19,6 +19,7 @@ import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-date-page', @@ -30,7 +31,8 @@ import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; * A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields. * An example would be 'dateissued' for 'dc.date.issued' */ -export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent { +@rendersBrowseBy(BrowseByDataType.Date) +export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent implements OnInit { /** * The default metadata keys to use for determining the lower limit of the StartsWith dropdown options diff --git a/src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts deleted file mode 100644 index 8eeae0c5de..0000000000 --- a/src/app/browse-by/browse-by-date-page/themed-browse-by-date-page.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {Component} from '@angular/core'; -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { BrowseByDatePageComponent } from './browse-by-date-page.component'; -import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator'; - -/** - * Themed wrapper for BrowseByDatePageComponent - * */ -@Component({ - selector: 'ds-themed-browse-by-metadata-page', - styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html', -}) - -@rendersBrowseBy(BrowseByDataType.Date) -export class ThemedBrowseByDatePageComponent - extends ThemedComponent { - protected getComponentName(): string { - return 'BrowseByDatePageComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-date-page/browse-by-date-page.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-date-page.component`); - } -} diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 113bc67c92..28d57ce3a4 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -22,6 +22,7 @@ import { Collection } from '../../core/shared/collection.model'; import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; export const BBM_PAGINATION_ID = 'bbm'; @@ -36,6 +37,7 @@ export const BBM_PAGINATION_ID = 'bbm'; * or multiple metadata fields. An example would be 'author' for * 'dc.contributor.*' */ +@rendersBrowseBy(BrowseByDataType.Metadata) export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { /** diff --git a/src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts deleted file mode 100644 index b0679258e9..0000000000 --- a/src/app/browse-by/browse-by-metadata-page/themed-browse-by-metadata-page.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {Component} from '@angular/core'; -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { BrowseByMetadataPageComponent } from './browse-by-metadata-page.component'; -import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator'; - -/** - * Themed wrapper for BrowseByMetadataPageComponent - **/ -@Component({ - selector: 'ds-themed-browse-by-metadata-page', - styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html', -}) - -@rendersBrowseBy(BrowseByDataType.Metadata) -export class ThemedBrowseByMetadataPageComponent - extends ThemedComponent { - protected getComponentName(): string { - return 'BrowseByMetadataPageComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-metadata-page.component`); - } -} diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts index b59a46cae1..11ff1cec28 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts @@ -9,7 +9,8 @@ import { export enum BrowseByDataType { Title = 'title', Metadata = 'text', - Date = 'date' + Date = 'date', + Hierarchy = 'hierarchy', } export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata; diff --git a/src/app/browse-by/browse-by-switcher/themed-browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/themed-browse-by-switcher.component.ts deleted file mode 100644 index 0187d4e3c5..0000000000 --- a/src/app/browse-by/browse-by-switcher/themed-browse-by-switcher.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component } from '@angular/core'; - -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { BrowseBySwitcherComponent } from './browse-by-switcher.component'; - -/** - * Themed wrapper for BrowseBySwitcherComponent - */ -@Component({ - selector: 'ds-themed-browse-by-switcher', - styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html' -}) -export class ThemedBrowseBySwitcherComponent extends ThemedComponent { - protected getComponentName(): string { - return 'BrowseBySwitcherComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-switcher/browse-by-switcher.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-switcher.component`); - } - - -} diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index cf6345bf39..3536d92e47 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -5,7 +5,7 @@ import { ActivatedRoute } from '@angular/router'; import { Observable, Subscription } from 'rxjs'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { BROWSE_BY_COMPONENT_FACTORY } from '../browse-by-switcher/browse-by-decorator'; +import { BROWSE_BY_COMPONENT_FACTORY, rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; import { map } from 'rxjs/operators'; import { ThemeService } from 'src/app/shared/theme-support/theme.service'; import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; @@ -18,6 +18,7 @@ import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-bro /** * Component for browsing items by metadata in a hierarchical controlled vocabulary */ +@rendersBrowseBy(BrowseByDataType.Hierarchy) export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { /** diff --git a/src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts deleted file mode 100644 index 212044b853..0000000000 --- a/src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Component } from '@angular/core'; -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; -import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.component'; - -@Component({ - selector: 'ds-themed-browse-by-taxonomy-page', - templateUrl: '../../shared/theme-support/themed.component.html', - styleUrls: [] -}) -/** - * Themed wrapper for BrowseByTaxonomyPageComponent - */ -@rendersBrowseBy('hierarchy') -export class ThemedBrowseByTaxonomyPageComponent extends ThemedComponent{ - - protected getComponentName(): string { - return 'BrowseByTaxonomyPageComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-taxonomy-page.component`); - } -} diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 58df79ebe8..cec30712da 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,7 +1,6 @@ import { combineLatest as observableCombineLatest } from 'rxjs'; -import { Component, Inject } from '@angular/core'; +import { Component, Inject, OnInit } from '@angular/core'; import { ActivatedRoute, Params, Router } from '@angular/router'; -import { hasValue } from '../../shared/empty.util'; import { BrowseByMetadataPageComponent, browseParamsToOptions, getBrowseSearchOptions @@ -14,6 +13,7 @@ import { map } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-title-page', @@ -23,7 +23,8 @@ import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; /** * Component for browsing items by title (dc.title) */ -export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { +@rendersBrowseBy(BrowseByDataType.Title) +export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent implements OnInit { public constructor(protected route: ActivatedRoute, protected browseService: BrowseService, @@ -57,8 +58,4 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent { this.updateStartsWithTextOptions(); } - ngOnDestroy(): void { - this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); - } - } diff --git a/src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts deleted file mode 100644 index 4a1bcc0bc1..0000000000 --- a/src/app/browse-by/browse-by-title-page/themed-browse-by-title-page.component.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {Component} from '@angular/core'; -import { ThemedComponent } from '../../shared/theme-support/themed.component'; -import { BrowseByTitlePageComponent } from './browse-by-title-page.component'; -import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator'; - -/** - * Themed wrapper for BrowseByTitlePageComponent - */ -@Component({ - selector: 'ds-themed-browse-by-title-page', - styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html', -}) - -@rendersBrowseBy(BrowseByDataType.Title) -export class ThemedBrowseByTitlePageComponent - extends ThemedComponent { - protected getComponentName(): string { - return 'BrowseByTitlePageComponent'; - } - - protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/browse-by/browse-by-title-page/browse-by-title-page.component`); - } - - protected importUnthemedComponent(): Promise { - return import(`./browse-by-title-page.component`); - } -} diff --git a/src/app/browse-by/browse-by.module.ts b/src/app/browse-by/browse-by.module.ts index c0e2d3f9ff..bc7f6a159a 100644 --- a/src/app/browse-by/browse-by.module.ts +++ b/src/app/browse-by/browse-by.module.ts @@ -7,10 +7,6 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switch import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component'; import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; import { ComcolModule } from '../shared/comcol/comcol.module'; -import { ThemedBrowseByMetadataPageComponent } from './browse-by-metadata-page/themed-browse-by-metadata-page.component'; -import { ThemedBrowseByDatePageComponent } from './browse-by-date-page/themed-browse-by-date-page.component'; -import { ThemedBrowseByTitlePageComponent } from './browse-by-title-page/themed-browse-by-title-page.component'; -import { ThemedBrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component'; import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module'; import { DsoPageModule } from '../shared/dso-page/dso-page.module'; import { FormModule } from '../shared/form/form.module'; @@ -21,11 +17,6 @@ const ENTRY_COMPONENTS = [ BrowseByMetadataPageComponent, BrowseByDatePageComponent, BrowseByTaxonomyPageComponent, - - ThemedBrowseByMetadataPageComponent, - ThemedBrowseByDatePageComponent, - ThemedBrowseByTitlePageComponent, - ThemedBrowseByTaxonomyPageComponent, ]; @NgModule({ diff --git a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index 9fcf773350..589effde60 100644 --- a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-date-page/browse-by-date-page.component'; +import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-date-page', @@ -8,10 +9,6 @@ import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/b // templateUrl: './browse-by-date-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) - -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ - +@rendersBrowseBy(BrowseByDataType.Date, 'custom') export class BrowseByDatePageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 9434ca936d..d2ee7ad694 100644 --- a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; +import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-metadata-page', @@ -8,10 +9,6 @@ import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../a // templateUrl: './browse-by-metadata-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) - -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ - +@rendersBrowseBy(BrowseByDataType.Metadata, 'custom') export class BrowseByMetadataPageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts deleted file mode 100644 index e65997eaf4..0000000000 --- a/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component } from '@angular/core'; -import { BrowseBySwitcherComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-switcher/browse-by-switcher.component'; - -@Component({ - selector: 'ds-browse-by-switcher', - // styleUrls: ['./browse-by-switcher.component.scss'], - // templateUrl: './browse-by-switcher.component.html' - templateUrl: '../../../../../app/browse-by/browse-by-switcher/browse-by-switcher.component.html' -}) - -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ -export class BrowseBySwitcherComponent extends BaseComponent {} - diff --git a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index 34d80a0cb8..459dc54d7e 100644 --- a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component'; +import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-taxonomy-page', @@ -8,8 +9,6 @@ import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../a // styleUrls: ['./browse-by-taxonomy-page.component.scss'], styleUrls: ['../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss'], }) -/** - * Component for browsing items by metadata in a hierarchical controlled vocabulary - */ +@rendersBrowseBy(BrowseByDataType.Hierarchy, 'custom') export class BrowseByTaxonomyPageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index ed96a81110..136970b38a 100644 --- a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-title-page/browse-by-title-page.component'; +import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; @Component({ selector: 'ds-browse-by-title-page', @@ -8,10 +9,6 @@ import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/ // templateUrl: './browse-by-title-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) - -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ - +@rendersBrowseBy(BrowseByDataType.Title, 'custom') export class BrowseByTitlePageComponent extends BaseComponent { } diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index edb3f5478c..e102089361 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -46,7 +46,6 @@ import { RootModule } from '../../app/root.module'; import { FileSectionComponent } from './app/item-page/simple/field-components/file-section/file-section.component'; import { HomePageComponent } from './app/home-page/home-page.component'; import { RootComponent } from './app/root/root.component'; -import { BrowseBySwitcherComponent } from './app/browse-by/browse-by-switcher/browse-by-switcher.component'; import { CommunityListPageComponent } from './app/community-list-page/community-list-page.component'; import { SearchPageComponent } from './app/search-page/search-page.component'; import { ConfigurationSearchPageComponent } from './app/search-page/configuration-search-page.component'; @@ -161,7 +160,6 @@ const DECLARATIONS = [ FileSectionComponent, HomePageComponent, RootComponent, - BrowseBySwitcherComponent, CommunityListPageComponent, SearchPageComponent, ConfigurationSearchPageComponent, From 14d42b0d93a18e129c0cb63af695592e825e7b74 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 10 Dec 2023 22:19:57 +0100 Subject: [PATCH 20/94] Fixed getComponent not triggering again when it's input values change by creating inputNamesDependentForComponent Also simplified the way @Input() values are passed to their child component and how ngOnChanges is called by using setInput instead --- .../abstract-component-loader.component.ts | 61 ++++++++++++++----- ...etadata-representation-loader.component.ts | 4 +- .../claimed-task-actions-loader.component.ts | 12 ++-- ...table-object-component-loader.component.ts | 26 ++++---- ...anced-workflow-actions-loader.component.ts | 4 +- 5 files changed, 71 insertions(+), 36 deletions(-) diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts index 6f934f5b4b..46074f7a6b 100644 --- a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -32,10 +32,22 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC */ protected subs: Subscription[] = []; - protected inAndOutputNames: (keyof this)[] = [ + /** + * The @{@link Input}() that are used to find the matching component using {@link getComponent}. When the value of + * one of these @{@link Input}() change this loader needs to retrieve the best matching component again using the + * {@link getComponent} method. + */ + protected inputNamesDependentForComponent: (keyof this & string)[] = [ 'context', ]; + protected inputNames: (keyof this & string)[] = [ + 'context', + ]; + + protected outputNames: (keyof this & string)[] = [ + ]; + constructor( protected themeService: ThemeService, ) { @@ -45,7 +57,9 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC * Set up the dynamic child component */ ngOnInit(): void { - this.instantiateComponent(); + if (hasNoValue(this.compRef)) { + this.instantiateComponent(); + } } /** @@ -55,14 +69,14 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC if (hasNoValue(this.compRef)) { // sometimes the component has not been initialized yet, so it first needs to be initialized // before being called again - this.instantiateComponent(changes); + this.instantiateComponent(); } else { - // if an input or output has changed - if (this.inAndOutputNames.some((name: any) => hasValue(changes[name]))) { + if (this.inputNamesDependentForComponent.some((name: any) => hasValue(changes[name]) && changes[name].previousValue !== changes[name].currentValue)) { + // Recreate the component when the @Input()s used by getComponent() aren't up-to-date anymore + this.destroyComponentInstance(); + this.instantiateComponent(); + } else { this.connectInputsAndOutputs(); - if (this.compRef?.instance && 'ngOnChanges' in this.compRef.instance) { - (this.compRef.instance as any).ngOnChanges(changes); - } } } } @@ -73,7 +87,10 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC .forEach((subscription: Subscription) => subscription.unsubscribe()); } - public instantiateComponent(changes?: SimpleChanges): void { + /** + * Creates the component and connects the @Input() & @Output() from the ThemedComponent to its child Component. + */ + public instantiateComponent(): void { const component: GenericConstructor = this.getComponent(); const viewContainerRef: ViewContainerRef = this.componentDirective.viewContainerRef; @@ -86,10 +103,16 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC }, ); - if (hasValue(changes)) { - this.ngOnChanges(changes); - } else { - this.connectInputsAndOutputs(); + this.connectInputsAndOutputs(); + } + + /** + * Destroys the themed component and calls it's `ngOnDestroy` + */ + public destroyComponentInstance(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = null; } } @@ -99,12 +122,18 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC public abstract getComponent(): GenericConstructor; /** - * Connect the in and outputs of this component to the dynamic component, + * Connect the inputs and outputs of this component to the dynamic component, * to ensure they're in sync */ protected connectInputsAndOutputs(): void { - if (isNotEmpty(this.inAndOutputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { - this.inAndOutputNames.filter((name: any) => this[name] !== undefined).forEach((name: any) => { + if (isNotEmpty(this.inputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.inputNames.filter((name: string) => this[name] !== undefined).filter((name: string) => this[name] !== this.compRef.instance[name]).forEach((name: string) => { + // Using setInput will automatically trigger the ngOnChanges + this.compRef.setInput(name, this[name]); + }); + } + if (isNotEmpty(this.outputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { + this.outputNames.filter((name: string) => this[name] !== undefined).filter((name: string) => this[name] !== this.compRef.instance[name]).forEach((name: string) => { this.compRef.instance[name] = this[name]; }); } diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts index 83542512ed..4b63a31a2b 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts @@ -24,8 +24,8 @@ export class MetadataRepresentationLoaderComponent extends AbstractComponentLoad */ @Input() mdRepresentation: MetadataRepresentation; - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'mdRepresentation', ]; diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index 75b392fe51..fb39347d63 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, OnInit, Output, } from '@angular/core'; +import { Component, EventEmitter, Input, Output, } from '@angular/core'; import { getComponentByWorkflowTaskOption } from './claimed-task-actions-decorator'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; import { MyDSpaceActionsResult } from '../../mydspace-actions'; @@ -16,7 +16,7 @@ import { GenericConstructor } from '../../../../core/shared/generic-constructor' * Component for loading a ClaimedTaskAction component depending on the "option" input * Passes on the ClaimedTask to the component and subscribes to the processCompleted output */ -export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderComponent implements OnInit, OnChanges { +export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderComponent { /** * The item object that belonging to the ClaimedTask object */ @@ -46,12 +46,16 @@ export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderCo /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'item', 'object', 'option', 'workflowitem', + ]; + + protected outputNames: (keyof this & string)[] = [ + ...this.outputNames, 'processCompleted', ]; diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index f62ee44a1c..a83cdfb6b2 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -1,5 +1,4 @@ -import { ChangeDetectorRef, Component, EventEmitter, Input, Output, SimpleChanges } from '@angular/core'; -import { combineLatest, Observable, of as observableOf } from 'rxjs'; +import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; import { take } from 'rxjs/operators'; import { ListableObject } from '../listable-object.model'; import { ViewMode } from '../../../../core/shared/view-mode.model'; @@ -74,8 +73,8 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa /** * The list of input and output names for the dynamic component */ - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'object', 'index', 'linkType', @@ -84,6 +83,10 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa 'showThumbnails', 'viewMode', 'value', + ]; + + protected outputNames: (keyof this & string)[] = [ + ...this.outputNames, 'contentChange', ]; @@ -94,17 +97,16 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa super(themeService); } - public instantiateComponent(changes?: SimpleChanges): void { - super.instantiateComponent(changes); + public instantiateComponent(): void { + super.instantiateComponent(); if ((this.compRef.instance as any).reloadedObject) { - combineLatest([ - observableOf(changes), - (this.compRef.instance as any).reloadedObject.pipe(take(1)) as Observable, - ]).subscribe(([simpleChanges, reloadedObject]: [SimpleChanges, DSpaceObject]) => { + (this.compRef.instance as any).reloadedObject.pipe( + take(1), + ).subscribe((reloadedObject: DSpaceObject) => { if (reloadedObject) { - this.compRef.destroy(); + this.destroyComponentInstance(); this.object = reloadedObject; - this.instantiateComponent(simpleChanges); + this.instantiateComponent(); this.cdr.detectChanges(); this.contentChange.emit(reloadedObject); } diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts index 10cae7ec7d..1db49b97e8 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts @@ -24,8 +24,8 @@ export class AdvancedWorkflowActionsLoaderComponent extends AbstractComponentLoa */ @Input() type: string; - protected inAndOutputNames: (keyof this)[] = [ - ...this.inAndOutputNames, + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, 'type', ]; From 7b8962a7cde749074df3c47e93e780fb21c895ca Mon Sep 17 00:00:00 2001 From: Thomas Misilo Date: Mon, 11 Dec 2023 13:16:46 -0600 Subject: [PATCH 21/94] Update "E-mail" to be "Email" for consistency --- .../privacy-content/privacy-content.component.html | 2 +- src/assets/i18n/en.json5 | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/info/privacy/privacy-content/privacy-content.component.html b/src/app/info/privacy/privacy-content/privacy-content.component.html index f29a786e8b..003e6b0d51 100644 --- a/src/app/info/privacy/privacy-content/privacy-content.component.html +++ b/src/app/info/privacy/privacy-content/privacy-content.component.html @@ -22,7 +22,7 @@

Information we collect about you and how we collect it

We collect several types of information from and about users of our Website, including information:

    -
  • by which you may be personally identified, such as name, e-mail address, telephone number, or any other identifier by which you may be contacted online or offline ("personal information"); and/or
  • +
  • by which you may be personally identified, such as name, email address, telephone number, or any other identifier by which you may be contacted online or offline ("personal information"); and/or
  • about your internet connection, the equipment you use to access our Website and usage details.

We collect this information:

diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 7bf16c4ee8..515e0338f1 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -266,7 +266,7 @@ "admin.access-control.epeople.search.scope.metadata": "Metadata", - "admin.access-control.epeople.search.scope.email": "E-mail (exact)", + "admin.access-control.epeople.search.scope.email": "Email (exact)", "admin.access-control.epeople.search.button": "Search", @@ -278,7 +278,7 @@ "admin.access-control.epeople.table.name": "Name", - "admin.access-control.epeople.table.email": "E-mail (exact)", + "admin.access-control.epeople.table.email": "Email (exact)", "admin.access-control.epeople.table.edit": "Edit", @@ -298,9 +298,9 @@ "admin.access-control.epeople.form.lastName": "Last name", - "admin.access-control.epeople.form.email": "E-mail", + "admin.access-control.epeople.form.email": "Email", - "admin.access-control.epeople.form.emailHint": "Must be a valid e-mail address", + "admin.access-control.epeople.form.emailHint": "Must be a valid email address", "admin.access-control.epeople.form.canLogIn": "Can log in", @@ -750,7 +750,7 @@ "bitstream-request-a-copy.name.error": "The name is required", - "bitstream-request-a-copy.email.label": "Your e-mail address *", + "bitstream-request-a-copy.email.label": "Your email address *", "bitstream-request-a-copy.email.hint": "This email address is used for sending the file.", @@ -1602,9 +1602,9 @@ "error.validation.required": "This field is required", - "error.validation.NotValidEmail": "This is not a valid e-mail", + "error.validation.NotValidEmail": "This is not a valid email", - "error.validation.emailTaken": "This e-mail is already taken", + "error.validation.emailTaken": "This email is already taken", "error.validation.groupExists": "This group already exists", From a83d69ee0eaf9233398d64344367ba5cc41a289b Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Mon, 11 Dec 2023 01:56:25 +0100 Subject: [PATCH 22/94] Created BrowseByPageComponent that uses the new refactored BrowseBySwitcherComponent extending AbstractComponentLoaderComponent - Added the context to the rendersBrowseBy decorator - Created AbstractBrowseByTypeComponent that is extended by all the browse type sections --- .../abstract-browse-by-type.component.ts | 32 +++++++++ .../browse-by-metadata-page.component.ts | 14 ++-- src/app/browse-by/browse-by-page.module.ts | 16 ++++- .../browse-by-page.component.html | 2 + .../browse-by-page.component.scss} | 0 .../browse-by-page.component.spec.ts | 69 +++++++++++++++++++ .../browse-by-page.component.ts | 31 +++++++++ src/app/browse-by/browse-by-routing.module.ts | 4 +- .../browse-by-switcher/browse-by-decorator.ts | 38 ++++++---- .../browse-by-switcher.component.html | 1 - .../browse-by-switcher.component.spec.ts | 61 +++++++++------- .../browse-by-switcher.component.ts | 45 +++++------- .../browse-by-taxonomy-page.component.ts | 35 ++++------ src/app/browse-by/browse-by.module.ts | 11 +-- .../core/shared/browse-definition.model.ts | 3 +- .../shared/flat-browse-definition.model.ts | 3 +- .../hierarchical-browse-definition.model.ts | 5 +- .../value-list-browse-definition.model.ts | 3 +- .../abstract-component-loader.component.ts | 2 +- ...-object-component-loader.component.spec.ts | 2 +- .../browse-by-date-page.component.ts | 3 +- .../browse-by-metadata-page.component.ts | 3 +- .../browse-by-switcher.component.scss | 0 .../browse-by-taxonomy-page.component.ts | 3 +- .../browse-by-title-page.component.ts | 3 +- 25 files changed, 271 insertions(+), 118 deletions(-) create mode 100644 src/app/browse-by/abstract-browse-by-type.component.ts create mode 100644 src/app/browse-by/browse-by-page/browse-by-page.component.html rename src/{themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.html => app/browse-by/browse-by-page/browse-by-page.component.scss} (100%) create mode 100644 src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts create mode 100644 src/app/browse-by/browse-by-page/browse-by-page.component.ts delete mode 100644 src/app/browse-by/browse-by-switcher/browse-by-switcher.component.html delete mode 100644 src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.scss diff --git a/src/app/browse-by/abstract-browse-by-type.component.ts b/src/app/browse-by/abstract-browse-by-type.component.ts new file mode 100644 index 0000000000..246e59a010 --- /dev/null +++ b/src/app/browse-by/abstract-browse-by-type.component.ts @@ -0,0 +1,32 @@ +import { Component, Input, OnDestroy } from '@angular/core'; +import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator'; +import { Context } from '../core/shared/context.model'; +import { Subscription } from 'rxjs'; +import { hasValue } from '../shared/empty.util'; + +@Component({ + selector: 'ds-abstract-browse-by-type', + template: '', +}) +export abstract class AbstractBrowseByTypeComponent implements OnDestroy { + + /** + * The optional context + */ + @Input() context: Context; + + /** + * The {@link BrowseByDataType} of this Component + */ + @Input() browseByType: BrowseByDataType; + + /** + * List of subscriptions + */ + subs: Subscription[] = []; + + ngOnDestroy(): void { + this.subs.filter((sub: Subscription) => hasValue(sub)).forEach((sub: Subscription) => sub.unsubscribe()); + } + +} diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 28d57ce3a4..225b9b503c 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,4 +1,4 @@ -import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Component, Inject, OnInit, OnDestroy } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; @@ -23,6 +23,7 @@ import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; export const BBM_PAGINATION_ID = 'bbm'; @@ -38,7 +39,7 @@ export const BBM_PAGINATION_ID = 'bbm'; * 'dc.contributor.*' */ @rendersBrowseBy(BrowseByDataType.Metadata) -export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { +export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent implements OnInit, OnDestroy { /** * The list of browse-entries to display @@ -75,11 +76,6 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { */ currentSort$: Observable; - /** - * List of subscriptions - */ - subs: Subscription[] = []; - /** * The default browse id to resort to when none is provided */ @@ -132,7 +128,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { @Inject(APP_CONFIG) public appConfig: AppConfig, public dsoNameService: DSONameService, ) { - + super(); this.fetchThumbnails = this.appConfig.browseBy.showThumbnails; this.paginationConfig = Object.assign(new PaginationComponentOptions(), { id: BBM_PAGINATION_ID, @@ -276,7 +272,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { } ngOnDestroy(): void { - this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); + super.ngOnDestroy(); this.paginationService.clearPagination(this.paginationConfig.id); } diff --git a/src/app/browse-by/browse-by-page.module.ts b/src/app/browse-by/browse-by-page.module.ts index 554a6c4f46..8a010f7105 100644 --- a/src/app/browse-by/browse-by-page.module.ts +++ b/src/app/browse-by/browse-by-page.module.ts @@ -5,12 +5,19 @@ import { ItemDataService } from '../core/data/item-data.service'; import { BrowseService } from '../core/browse/browse.service'; import { BrowseByGuard } from './browse-by-guard'; import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module'; +import { BrowseByPageComponent } from './browse-by-page/browse-by-page.component'; +import { SharedModule } from '../shared/shared.module'; + +const DECLARATIONS = [ + BrowseByPageComponent, +]; @NgModule({ imports: [ SharedBrowseByModule, BrowseByRoutingModule, - BrowseByModule.withEntryComponents(), + BrowseByModule, + SharedModule, ], providers: [ ItemDataService, @@ -18,8 +25,11 @@ import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.modul BrowseByGuard, ], declarations: [ - - ] + ...DECLARATIONS, + ], + exports: [ + ...DECLARATIONS, + ], }) export class BrowseByPageModule { diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.html b/src/app/browse-by/browse-by-page/browse-by-page.component.html new file mode 100644 index 0000000000..b7b109643b --- /dev/null +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.html @@ -0,0 +1,2 @@ + + diff --git a/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.html b/src/app/browse-by/browse-by-page/browse-by-page.component.scss similarity index 100% rename from src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.html rename to src/app/browse-by/browse-by-page/browse-by-page.component.scss diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts new file mode 100644 index 0000000000..d60ca02afa --- /dev/null +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts @@ -0,0 +1,69 @@ +// eslint-disable-next-line max-classes-per-file +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowseByPageComponent } from './browse-by-page.component'; +import { BrowseBySwitcherComponent } from '../browse-by-switcher/browse-by-switcher.component'; +import { DynamicComponentLoaderDirective } from '../../shared/abstract-component-loader/dynamic-component-loader.directive'; +import { ActivatedRoute } from '@angular/router'; +import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; +import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; +import { ThemeService } from '../../shared/theme-support/theme.service'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { Component } from '@angular/core'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseDefinition } from '../../core/shared/browse-definition.model'; +import { By } from '@angular/platform-browser'; + +@rendersBrowseBy('BrowseByPageComponent' as BrowseByDataType) +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: '', + template: '', +}) +class BrowseByTestComponent extends AbstractBrowseByTypeComponent { +} + +class TestBrowseByPageBrowseDefinition extends BrowseDefinition { + getRenderType(): BrowseByDataType { + return 'BrowseByPageComponent' as BrowseByDataType; + } +} + +describe('BrowseByPageComponent', () => { + let component: BrowseByPageComponent; + let fixture: ComponentFixture; + + let activatedRoute: ActivatedRouteStub; + let themeService: ThemeService; + + beforeEach(async () => { + activatedRoute = new ActivatedRouteStub(); + themeService = getMockThemeService(); + + await TestBed.configureTestingModule({ + declarations: [ + BrowseByPageComponent, + BrowseBySwitcherComponent, + DynamicComponentLoaderDirective, + ], + providers: [ + BrowseByTestComponent, + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: ThemeService, useValue: themeService }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(BrowseByPageComponent); + component = fixture.componentInstance; + }); + + it('should create the correct browse section based on the route browseDefinition', () => { + activatedRoute.testData = { + browseDefinition: new TestBrowseByPageBrowseDefinition(), + }; + + fixture.detectChanges(); + + expect(component).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#BrowseByTestComponent'))).not.toBeNull(); + }); +}); diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.ts b/src/app/browse-by/browse-by-page/browse-by-page.component.ts new file mode 100644 index 0000000000..ad26133485 --- /dev/null +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { BrowseDefinition } from '../../core/shared/browse-definition.model'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; + +@Component({ + selector: 'ds-browse-by-page', + templateUrl: './browse-by-page.component.html', + styleUrls: ['./browse-by-page.component.scss'], +}) +export class BrowseByPageComponent implements OnInit { + + browseByType$: Observable; + + constructor( + protected route: ActivatedRoute, + ) { + } + + /** + * Fetch the correct browse-by component by using the relevant config from the route data + */ + ngOnInit(): void { + this.browseByType$ = this.route.data.pipe( + map((data: { browseDefinition: BrowseDefinition }) => data.browseDefinition.getRenderType()), + ); + } + +} diff --git a/src/app/browse-by/browse-by-routing.module.ts b/src/app/browse-by/browse-by-routing.module.ts index bb67dc65ae..f07df26b32 100644 --- a/src/app/browse-by/browse-by-routing.module.ts +++ b/src/app/browse-by/browse-by-routing.module.ts @@ -3,7 +3,7 @@ import { NgModule } from '@angular/core'; import { BrowseByGuard } from './browse-by-guard'; import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver'; import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver'; -import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; +import { BrowseByPageComponent } from './browse-by-page/browse-by-page.component'; import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; @NgModule({ @@ -18,7 +18,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; children: [ { path: ':id', - component: ThemedBrowseBySwitcherComponent, + component: BrowseByPageComponent, canActivate: [BrowseByGuard], resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver }, data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata' } diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts index 11ff1cec28..6891fba003 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts @@ -1,10 +1,9 @@ import { hasNoValue } from '../../shared/empty.util'; import { InjectionToken } from '@angular/core'; +import { DEFAULT_THEME, resolveTheme } from '../../shared/object-collection/shared/listable-object/listable-object.decorator'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { Context } from '../../core/shared/context.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { - DEFAULT_THEME, - resolveTheme -} from '../../shared/object-collection/shared/listable-object/listable-object.decorator'; export enum BrowseByDataType { Title = 'title', @@ -14,28 +13,36 @@ export enum BrowseByDataType { } export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata; +export const DEFAULT_BROWSE_BY_CONTEXT = Context.Any; -export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType, theme) => GenericConstructor>('getComponentByBrowseByType', { +export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType: BrowseByDataType, context: Context, theme: string) => GenericConstructor>('getComponentByBrowseByType', { providedIn: 'root', factory: () => getComponentByBrowseByType }); -const map = new Map(); +const map: Map>>> = new Map(); /** * Decorator used for rendering Browse-By pages by type * @param browseByType The type of page + * @param context The optional context for the component * @param theme The optional theme for the component */ -export function rendersBrowseBy(browseByType: string, theme = DEFAULT_THEME) { +export function rendersBrowseBy(browseByType: BrowseByDataType, context = DEFAULT_BROWSE_BY_CONTEXT, theme = DEFAULT_THEME) { return function decorator(component: any) { + if (hasNoValue(browseByType)) { + return; + } if (hasNoValue(map.get(browseByType))) { map.set(browseByType, new Map()); } - if (hasNoValue(map.get(browseByType).get(theme))) { - map.get(browseByType).set(theme, component); + if (hasNoValue(map.get(browseByType).get(context))) { + map.get(browseByType).set(context, new Map()); + } + if (hasNoValue(map.get(browseByType).get(context).get(theme))) { + map.get(browseByType).get(context).set(theme, component); } else { - throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}" and theme "${theme}"`); + throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}", context "${context}" and theme "${theme}"`); } }; } @@ -43,12 +50,17 @@ export function rendersBrowseBy(browseByType: string, theme = DEFAULT_THEME) { /** * Get the component used for rendering a Browse-By page by type * @param browseByType The type of page + * @param context The context to match * @param theme the theme to match */ -export function getComponentByBrowseByType(browseByType, theme) { - let themeMap = map.get(browseByType); +export function getComponentByBrowseByType(browseByType: BrowseByDataType, context: Context, theme: string): GenericConstructor { + let contextMap: Map>> = map.get(browseByType); + if (hasNoValue(contextMap)) { + contextMap = map.get(DEFAULT_BROWSE_BY_TYPE); + } + let themeMap: Map> = contextMap.get(context); if (hasNoValue(themeMap)) { - themeMap = map.get(DEFAULT_BROWSE_BY_TYPE); + themeMap = contextMap.get(DEFAULT_BROWSE_BY_CONTEXT); } const comp = resolveTheme(themeMap, theme); if (hasNoValue(comp)) { diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.html b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.html deleted file mode 100644 index afe79cf2b1..0000000000 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts index c13405dd4d..5812a54269 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts @@ -1,13 +1,23 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher.component'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator'; -import { BehaviorSubject } from 'rxjs'; +import { SimpleChange, Component } from '@angular/core'; +import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator'; import { ThemeService } from '../../shared/theme-support/theme.service'; import { FlatBrowseDefinition } from '../../core/shared/flat-browse-definition.model'; import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-definition.model'; import { NonHierarchicalBrowseDefinition } from '../../core/shared/non-hierarchical-browse-definition'; +import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; +import { DynamicComponentLoaderDirective } from '../../shared/abstract-component-loader/dynamic-component-loader.directive'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; + +@rendersBrowseBy('BrowseBySwitcherComponent' as BrowseByDataType) +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: '', + template: '', +}) +class BrowseByTestComponent extends AbstractBrowseByTypeComponent { +} describe('BrowseBySwitcherComponent', () => { let comp: BrowseBySwitcherComponent; @@ -41,46 +51,45 @@ describe('BrowseBySwitcherComponent', () => { ), ]; - const data = new BehaviorSubject(createDataWithBrowseDefinition(new FlatBrowseDefinition())); - - const activatedRouteStub = { - data - }; - let themeService: ThemeService; - let themeName: string; + const themeName = 'dspace'; beforeEach(waitForAsync(() => { - themeName = 'dspace'; - themeService = jasmine.createSpyObj('themeService', { - getThemeName: themeName, - }); + themeService = getMockThemeService(themeName); - TestBed.configureTestingModule({ - declarations: [BrowseBySwitcherComponent], - providers: [ - { provide: ActivatedRoute, useValue: activatedRouteStub }, - { provide: ThemeService, useValue: themeService }, - { provide: BROWSE_BY_COMPONENT_FACTORY, useValue: jasmine.createSpy('getComponentByBrowseByType').and.returnValue(null) } + void TestBed.configureTestingModule({ + declarations: [ + BrowseBySwitcherComponent, + DynamicComponentLoaderDirective, + ], + providers: [ + BrowseByTestComponent, + { provide: ThemeService, useValue: themeService }, ], - schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); })); beforeEach(waitForAsync(() => { fixture = TestBed.createComponent(BrowseBySwitcherComponent); comp = fixture.componentInstance; + spyOn(comp, 'getComponent').and.returnValue(BrowseByTestComponent); + spyOn(comp, 'connectInputsAndOutputs').and.callThrough(); })); types.forEach((type: NonHierarchicalBrowseDefinition) => { describe(`when switching to a browse-by page for "${type.id}"`, () => { - beforeEach(() => { - data.next(createDataWithBrowseDefinition(type)); + beforeEach(async () => { + comp.browseByType = type.dataType; + comp.ngOnChanges({ + browseByType: new SimpleChange(undefined, type.dataType, true), + }); fixture.detectChanges(); + await fixture.whenStable(); }); - it(`should call getComponentByBrowseByType with type "${type.dataType}"`, () => { - expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType, themeName); + it(`should call getComponent with type "${type.dataType}"`, () => { + expect(comp.getComponent).toHaveBeenCalled(); + expect(comp.connectInputsAndOutputs).toHaveBeenCalled(); }); }); }); diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts index 35e4edf900..881df71311 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts @@ -1,38 +1,29 @@ -import { Component, Inject, OnInit } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator'; +import { Component, Input } from '@angular/core'; +import { getComponentByBrowseByType, BrowseByDataType } from './browse-by-decorator'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { BrowseDefinition } from '../../core/shared/browse-definition.model'; -import { ThemeService } from '../../shared/theme-support/theme.service'; +import { AbstractComponentLoaderComponent } from '../../shared/abstract-component-loader/abstract-component-loader.component'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; @Component({ selector: 'ds-browse-by-switcher', - templateUrl: './browse-by-switcher.component.html' + templateUrl: '../../shared/abstract-component-loader/abstract-component-loader.component.html' }) -/** - * Component for determining what Browse-By component to use depending on the metadata (browse ID) provided - */ -export class BrowseBySwitcherComponent implements OnInit { +export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent { - /** - * Resolved browse-by component - */ - browseByComponent: Observable; + @Input() browseByType: BrowseByDataType; - public constructor(protected route: ActivatedRoute, - protected themeService: ThemeService, - @Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor) { - } + protected inputNamesDependentForComponent: (keyof this & string)[] = [ + ...this.inputNamesDependentForComponent, + 'browseByType', + ]; - /** - * Fetch the correct browse-by component by using the relevant config from the route data - */ - ngOnInit(): void { - this.browseByComponent = this.route.data.pipe( - map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.getRenderType(), this.themeService.getThemeName())) - ); + protected inputNames: (keyof this & string)[] = [ + ...this.inputNames, + 'browseByType', + ]; + + public getComponent(): GenericConstructor { + return getComponentByBrowseByType(this.browseByType, this.context, this.themeService.getThemeName()); } } diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index 3536d92e47..f3c2b8b6b0 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -1,14 +1,13 @@ -import { Component, OnInit, Inject, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy } from '@angular/core'; import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model'; import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; import { ActivatedRoute } from '@angular/router'; -import { Observable, Subscription } from 'rxjs'; +import { Observable } from 'rxjs'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; -import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { BROWSE_BY_COMPONENT_FACTORY, rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; import { map } from 'rxjs/operators'; -import { ThemeService } from 'src/app/shared/theme-support/theme.service'; import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; +import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; @Component({ selector: 'ds-browse-by-taxonomy-page', @@ -19,7 +18,7 @@ import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-bro * Component for browsing items by metadata in a hierarchical controlled vocabulary */ @rendersBrowseBy(BrowseByDataType.Hierarchy) -export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { +export class BrowseByTaxonomyPageComponent extends AbstractBrowseByTypeComponent implements OnInit, OnDestroy { /** * The {@link VocabularyOptions} object @@ -52,28 +51,23 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { queryParams: any; /** - * Resolved browse-by component + * Resolved browse-by definition */ - browseByComponent: Observable; + browseDefinition$: Observable; - /** - * Subscriptions to track - */ - browseByComponentSubs: Subscription[] = []; - - public constructor( protected route: ActivatedRoute, - protected themeService: ThemeService, - @Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor) { + public constructor( + protected route: ActivatedRoute, + ) { + super(); } ngOnInit(): void { - this.browseByComponent = this.route.data.pipe( + this.browseDefinition$ = this.route.data.pipe( map((data: { browseDefinition: BrowseDefinition }) => { - this.getComponentByBrowseByType(data.browseDefinition.getRenderType(), this.themeService.getThemeName()); return data.browseDefinition; }) ); - this.browseByComponentSubs.push(this.browseByComponent.subscribe((browseDefinition: HierarchicalBrowseDefinition) => { + this.subs.push(this.browseDefinition$.subscribe((browseDefinition: HierarchicalBrowseDefinition) => { this.facetType = browseDefinition.facetType; this.vocabularyName = browseDefinition.vocabulary; this.vocabularyOptions = { name: this.vocabularyName, closed: true }; @@ -113,7 +107,4 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { }; } - ngOnDestroy(): void { - this.browseByComponentSubs.forEach((sub: Subscription) => sub.unsubscribe()); - } } diff --git a/src/app/browse-by/browse-by.module.ts b/src/app/browse-by/browse-by.module.ts index bc7f6a159a..e8d05dc15a 100644 --- a/src/app/browse-by/browse-by.module.ts +++ b/src/app/browse-by/browse-by.module.ts @@ -5,12 +5,15 @@ import { BrowseByMetadataPageComponent } from './browse-by-metadata-page/browse- import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component'; import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component'; import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component'; -import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component'; import { ComcolModule } from '../shared/comcol/comcol.module'; import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module'; import { DsoPageModule } from '../shared/dso-page/dso-page.module'; import { FormModule } from '../shared/form/form.module'; +const DECLARATIONS = [ + BrowseBySwitcherComponent, +]; + const ENTRY_COMPONENTS = [ // put only entry components that use custom decorator BrowseByTitlePageComponent, @@ -28,12 +31,12 @@ const ENTRY_COMPONENTS = [ FormModule, ], declarations: [ - BrowseBySwitcherComponent, - ThemedBrowseBySwitcherComponent, + ...DECLARATIONS, ...ENTRY_COMPONENTS ], exports: [ - BrowseBySwitcherComponent + ...DECLARATIONS, + ...ENTRY_COMPONENTS, ] }) export class BrowseByModule { diff --git a/src/app/core/shared/browse-definition.model.ts b/src/app/core/shared/browse-definition.model.ts index a5bed53c9f..8a1ce71b55 100644 --- a/src/app/core/shared/browse-definition.model.ts +++ b/src/app/core/shared/browse-definition.model.ts @@ -1,5 +1,6 @@ import { autoserialize } from 'cerialize'; import { CacheableObject } from '../cache/cacheable-object.model'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; /** * Base class for BrowseDefinition models @@ -12,5 +13,5 @@ export abstract class BrowseDefinition extends CacheableObject { /** * Get the render type of the BrowseDefinition model */ - abstract getRenderType(): string; + abstract getRenderType(): BrowseByDataType; } diff --git a/src/app/core/shared/flat-browse-definition.model.ts b/src/app/core/shared/flat-browse-definition.model.ts index 086fca891b..49e58e1f20 100644 --- a/src/app/core/shared/flat-browse-definition.model.ts +++ b/src/app/core/shared/flat-browse-definition.model.ts @@ -5,6 +5,7 @@ import { FLAT_BROWSE_DEFINITION } from './flat-browse-definition.resource-type'; import { ResourceType } from './resource-type'; import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition'; import { HALLink } from './hal-link.model'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; /** * BrowseDefinition model for browses of type 'flatBrowse' @@ -30,7 +31,7 @@ export class FlatBrowseDefinition extends NonHierarchicalBrowseDefinition { items: HALLink; }; - getRenderType(): string { + getRenderType(): BrowseByDataType { return this.dataType; } } diff --git a/src/app/core/shared/hierarchical-browse-definition.model.ts b/src/app/core/shared/hierarchical-browse-definition.model.ts index d561fff643..cf8d7a9ed9 100644 --- a/src/app/core/shared/hierarchical-browse-definition.model.ts +++ b/src/app/core/shared/hierarchical-browse-definition.model.ts @@ -5,6 +5,7 @@ import { HIERARCHICAL_BROWSE_DEFINITION } from './hierarchical-browse-definition import { HALLink } from './hal-link.model'; import { ResourceType } from './resource-type'; import { BrowseDefinition } from './browse-definition.model'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; /** * BrowseDefinition model for browses of type 'hierarchicalBrowse' @@ -39,7 +40,7 @@ export class HierarchicalBrowseDefinition extends BrowseDefinition { vocabulary: HALLink; }; - getRenderType(): string { - return 'hierarchy'; + getRenderType(): BrowseByDataType { + return BrowseByDataType.Hierarchy; } } diff --git a/src/app/core/shared/value-list-browse-definition.model.ts b/src/app/core/shared/value-list-browse-definition.model.ts index 3378263962..e22742bb78 100644 --- a/src/app/core/shared/value-list-browse-definition.model.ts +++ b/src/app/core/shared/value-list-browse-definition.model.ts @@ -5,6 +5,7 @@ import { VALUE_LIST_BROWSE_DEFINITION } from './value-list-browse-definition.res import { ResourceType } from './resource-type'; import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition'; import { HALLink } from './hal-link.model'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; /** * BrowseDefinition model for browses of type 'valueList' @@ -30,7 +31,7 @@ export class ValueListBrowseDefinition extends NonHierarchicalBrowseDefinition { entries: HALLink; }; - getRenderType(): string { + getRenderType(): BrowseByDataType { return this.dataType; } } diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts index 46074f7a6b..12f0f02821 100644 --- a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -125,7 +125,7 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC * Connect the inputs and outputs of this component to the dynamic component, * to ensure they're in sync */ - protected connectInputsAndOutputs(): void { + public connectInputsAndOutputs(): void { if (isNotEmpty(this.inputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { this.inputNames.filter((name: string) => this[name] !== undefined).filter((name: string) => this[name] !== this.compRef.instance[name]).forEach((name: string) => { // Using setInput will automatically trigger the ngOnChanges diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts index 7b9d010e39..3b97e2a86e 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.spec.ts @@ -91,7 +91,7 @@ describe('ListableObjectComponentLoaderComponent', () => { listableComponent.reloadedObject.emit(reloadedObject); tick(200); - expect(comp.instantiateComponent).toHaveBeenCalledWith(undefined); + expect(comp.instantiateComponent).toHaveBeenCalledWith(); })); it('should re-emit it as a contentChange', fakeAsync(() => { diff --git a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index 589effde60..fcbe15cfae 100644 --- a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-date-page/browse-by-date-page.component'; import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { Context } from '../../../../../app/core/shared/context.model'; @Component({ selector: 'ds-browse-by-date-page', @@ -9,6 +10,6 @@ import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/ // templateUrl: './browse-by-date-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) -@rendersBrowseBy(BrowseByDataType.Date, 'custom') +@rendersBrowseBy(BrowseByDataType.Date, Context.Any, 'custom') export class BrowseByDatePageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index d2ee7ad694..533dca29ca 100644 --- a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { Context } from '../../../../../app/core/shared/context.model'; @Component({ selector: 'ds-browse-by-metadata-page', @@ -9,6 +10,6 @@ import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/ // templateUrl: './browse-by-metadata-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) -@rendersBrowseBy(BrowseByDataType.Metadata, 'custom') +@rendersBrowseBy(BrowseByDataType.Metadata, Context.Any, 'custom') export class BrowseByMetadataPageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.scss b/src/themes/custom/app/browse-by/browse-by-switcher/browse-by-switcher.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index 459dc54d7e..afc0292bef 100644 --- a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component'; import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { Context } from '../../../../../app/core/shared/context.model'; @Component({ selector: 'ds-browse-by-taxonomy-page', @@ -9,6 +10,6 @@ import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/ // styleUrls: ['./browse-by-taxonomy-page.component.scss'], styleUrls: ['../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss'], }) -@rendersBrowseBy(BrowseByDataType.Hierarchy, 'custom') +@rendersBrowseBy(BrowseByDataType.Hierarchy, Context.Any, 'custom') export class BrowseByTaxonomyPageComponent extends BaseComponent { } diff --git a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 136970b38a..5424293529 100644 --- a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-title-page/browse-by-title-page.component'; import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { Context } from '../../../../../app/core/shared/context.model'; @Component({ selector: 'ds-browse-by-title-page', @@ -9,6 +10,6 @@ import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/ // templateUrl: './browse-by-title-page.component.html' templateUrl: '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html' }) -@rendersBrowseBy(BrowseByDataType.Title, 'custom') +@rendersBrowseBy(BrowseByDataType.Title, Context.Any, 'custom') export class BrowseByTitlePageComponent extends BaseComponent { } From e6bf2f0ca71adf02dca64a7a1a07de46c309a8d1 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 12 Dec 2023 00:34:12 +0100 Subject: [PATCH 23/94] Fixed bug in BrowseService where findListByHref was called with null instead of undefined, which prevented its default values from being used --- src/app/core/browse/browse.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts index b210b34949..58bbc0b870 100644 --- a/src/app/core/browse/browse.service.ts +++ b/src/app/core/browse/browse.service.ts @@ -105,7 +105,7 @@ export class BrowseService { }) ); if (options.fetchThumbnail ) { - return this.hrefOnlyDataService.findListByHref(href$, {}, null, null, ...BROWSE_LINKS_TO_FOLLOW); + return this.hrefOnlyDataService.findListByHref(href$, {}, undefined, undefined, ...BROWSE_LINKS_TO_FOLLOW); } return this.hrefOnlyDataService.findListByHref(href$); } @@ -153,7 +153,7 @@ export class BrowseService { }), ); if (options.fetchThumbnail) { - return this.hrefOnlyDataService.findListByHref(href$, {}, null, null, ...BROWSE_LINKS_TO_FOLLOW); + return this.hrefOnlyDataService.findListByHref(href$, {}, undefined, undefined, ...BROWSE_LINKS_TO_FOLLOW); } return this.hrefOnlyDataService.findListByHref(href$); } From a5b7e2a40fc7039789ba5e937ce505b43c27c33a Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 12 Dec 2023 01:20:41 +0100 Subject: [PATCH 24/94] Fixed circular dependency by extracting BrowseByDataType to a separate file --- .../browse-by/abstract-browse-by-type.component.ts | 2 +- .../browse-by-date-page.component.ts | 3 ++- src/app/browse-by/browse-by-guard.spec.ts | 2 +- .../browse-by-metadata-page.component.ts | 3 ++- .../browse-by-page.component.spec.ts | 3 ++- .../browse-by-page/browse-by-page.component.ts | 2 +- .../browse-by-switcher/browse-by-data-type.ts | 6 ++++++ .../browse-by-switcher/browse-by-decorator.spec.ts | 3 ++- .../browse-by-switcher/browse-by-decorator.ts | 14 +------------- .../browse-by-switcher.component.spec.ts | 3 ++- .../browse-by-switcher.component.ts | 3 ++- .../browse-by-taxonomy-page.component.ts | 3 ++- .../browse-by-title-page.component.ts | 3 ++- src/app/core/shared/browse-definition.model.ts | 2 +- .../core/shared/flat-browse-definition.model.ts | 2 +- .../shared/hierarchical-browse-definition.model.ts | 2 +- .../shared/non-hierarchical-browse-definition.ts | 2 +- .../shared/value-list-browse-definition.model.ts | 2 +- src/app/navbar/navbar.component.spec.ts | 2 +- .../browse-by-date-page.component.ts | 3 ++- .../browse-by-metadata-page.component.ts | 3 ++- .../browse-by-taxonomy-page.component.ts | 3 ++- .../browse-by-title-page.component.ts | 3 ++- 23 files changed, 40 insertions(+), 34 deletions(-) create mode 100644 src/app/browse-by/browse-by-switcher/browse-by-data-type.ts diff --git a/src/app/browse-by/abstract-browse-by-type.component.ts b/src/app/browse-by/abstract-browse-by-type.component.ts index 246e59a010..5bd39cfc50 100644 --- a/src/app/browse-by/abstract-browse-by-type.component.ts +++ b/src/app/browse-by/abstract-browse-by-type.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnDestroy } from '@angular/core'; -import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type'; import { Context } from '../core/shared/context.model'; import { Subscription } from 'rxjs'; import { hasValue } from '../shared/empty.util'; diff --git a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index b106803351..2fb9725bda 100644 --- a/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -19,7 +19,8 @@ import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @Component({ selector: 'ds-browse-by-date-page', diff --git a/src/app/browse-by/browse-by-guard.spec.ts b/src/app/browse-by/browse-by-guard.spec.ts index aac5ba2723..c7d3e1e0c0 100644 --- a/src/app/browse-by/browse-by-guard.spec.ts +++ b/src/app/browse-by/browse-by-guard.spec.ts @@ -2,7 +2,7 @@ import { first } from 'rxjs/operators'; import { BrowseByGuard } from './browse-by-guard'; import { of as observableOf } from 'rxjs'; import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; -import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type'; import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model'; import { DSONameServiceMock } from '../shared/mocks/dso-name.service.mock'; import { DSONameService } from '../core/breadcrumbs/dso-name.service'; diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 225b9b503c..4c897b40f8 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -22,8 +22,9 @@ import { Collection } from '../../core/shared/collection.model'; import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; export const BBM_PAGINATION_ID = 'bbm'; diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts index d60ca02afa..3f9271a67d 100644 --- a/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts @@ -7,11 +7,12 @@ import { ActivatedRoute } from '@angular/router'; import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { Component } from '@angular/core'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; import { By } from '@angular/platform-browser'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @rendersBrowseBy('BrowseByPageComponent' as BrowseByDataType) @Component({ diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.ts b/src/app/browse-by/browse-by-page/browse-by-page.component.ts index ad26133485..9df02562c6 100644 --- a/src/app/browse-by/browse-by-page/browse-by-page.component.ts +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.ts @@ -3,7 +3,7 @@ import { map } from 'rxjs/operators'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; -import { BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @Component({ selector: 'ds-browse-by-page', diff --git a/src/app/browse-by/browse-by-switcher/browse-by-data-type.ts b/src/app/browse-by/browse-by-switcher/browse-by-data-type.ts new file mode 100644 index 0000000000..5324018b34 --- /dev/null +++ b/src/app/browse-by/browse-by-switcher/browse-by-data-type.ts @@ -0,0 +1,6 @@ +export enum BrowseByDataType { + Title = 'title', + Metadata = 'text', + Date = 'date', + Hierarchy = 'hierarchy', +} diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.spec.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.spec.ts index 19a6277151..64604cdc04 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.spec.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.spec.ts @@ -1,4 +1,5 @@ -import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator'; +import { BrowseByDataType } from './browse-by-data-type'; +import { rendersBrowseBy } from './browse-by-decorator'; describe('BrowseByDecorator', () => { const titleDecorator = rendersBrowseBy(BrowseByDataType.Title); diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts index 6891fba003..835c34ebcb 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts @@ -1,25 +1,13 @@ import { hasNoValue } from '../../shared/empty.util'; -import { InjectionToken } from '@angular/core'; import { DEFAULT_THEME, resolveTheme } from '../../shared/object-collection/shared/listable-object/listable-object.decorator'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { Context } from '../../core/shared/context.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; - -export enum BrowseByDataType { - Title = 'title', - Metadata = 'text', - Date = 'date', - Hierarchy = 'hierarchy', -} +import { BrowseByDataType } from './browse-by-data-type'; export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata; export const DEFAULT_BROWSE_BY_CONTEXT = Context.Any; -export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType: BrowseByDataType, context: Context, theme: string) => GenericConstructor>('getComponentByBrowseByType', { - providedIn: 'root', - factory: () => getComponentByBrowseByType -}); - const map: Map>>> = new Map(); /** diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts index 5812a54269..418dfd45e1 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts @@ -1,7 +1,7 @@ import { BrowseBySwitcherComponent } from './browse-by-switcher.component'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SimpleChange, Component } from '@angular/core'; -import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator'; +import { rendersBrowseBy } from './browse-by-decorator'; import { ThemeService } from '../../shared/theme-support/theme.service'; import { FlatBrowseDefinition } from '../../core/shared/flat-browse-definition.model'; import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-definition.model'; @@ -9,6 +9,7 @@ import { NonHierarchicalBrowseDefinition } from '../../core/shared/non-hierarchi import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { DynamicComponentLoaderDirective } from '../../shared/abstract-component-loader/dynamic-component-loader.directive'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseByDataType } from './browse-by-data-type'; @rendersBrowseBy('BrowseBySwitcherComponent' as BrowseByDataType) @Component({ diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts index 881df71311..2ebea9a262 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts @@ -1,8 +1,9 @@ import { Component, Input } from '@angular/core'; -import { getComponentByBrowseByType, BrowseByDataType } from './browse-by-decorator'; +import { getComponentByBrowseByType } from './browse-by-decorator'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { AbstractComponentLoaderComponent } from '../../shared/abstract-component-loader/abstract-component-loader.component'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseByDataType } from './browse-by-data-type'; @Component({ selector: 'ds-browse-by-switcher', diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index f3c2b8b6b0..f005c66c9e 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -4,10 +4,11 @@ import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { map } from 'rxjs/operators'; import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @Component({ selector: 'ds-browse-by-taxonomy-page', diff --git a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index cec30712da..1e18429fa0 100644 --- a/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -13,7 +13,8 @@ import { map } from 'rxjs/operators'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; -import { rendersBrowseBy, BrowseByDataType } from '../browse-by-switcher/browse-by-decorator'; +import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @Component({ selector: 'ds-browse-by-title-page', diff --git a/src/app/core/shared/browse-definition.model.ts b/src/app/core/shared/browse-definition.model.ts index 8a1ce71b55..72239b26f7 100644 --- a/src/app/core/shared/browse-definition.model.ts +++ b/src/app/core/shared/browse-definition.model.ts @@ -1,6 +1,6 @@ import { autoserialize } from 'cerialize'; import { CacheableObject } from '../cache/cacheable-object.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; /** * Base class for BrowseDefinition models diff --git a/src/app/core/shared/flat-browse-definition.model.ts b/src/app/core/shared/flat-browse-definition.model.ts index 49e58e1f20..9f37f1c422 100644 --- a/src/app/core/shared/flat-browse-definition.model.ts +++ b/src/app/core/shared/flat-browse-definition.model.ts @@ -5,7 +5,7 @@ import { FLAT_BROWSE_DEFINITION } from './flat-browse-definition.resource-type'; import { ResourceType } from './resource-type'; import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition'; import { HALLink } from './hal-link.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; /** * BrowseDefinition model for browses of type 'flatBrowse' diff --git a/src/app/core/shared/hierarchical-browse-definition.model.ts b/src/app/core/shared/hierarchical-browse-definition.model.ts index cf8d7a9ed9..2410bf7b7a 100644 --- a/src/app/core/shared/hierarchical-browse-definition.model.ts +++ b/src/app/core/shared/hierarchical-browse-definition.model.ts @@ -5,7 +5,7 @@ import { HIERARCHICAL_BROWSE_DEFINITION } from './hierarchical-browse-definition import { HALLink } from './hal-link.model'; import { ResourceType } from './resource-type'; import { BrowseDefinition } from './browse-definition.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; /** * BrowseDefinition model for browses of type 'hierarchicalBrowse' diff --git a/src/app/core/shared/non-hierarchical-browse-definition.ts b/src/app/core/shared/non-hierarchical-browse-definition.ts index d5481fcc8d..e3319affdb 100644 --- a/src/app/core/shared/non-hierarchical-browse-definition.ts +++ b/src/app/core/shared/non-hierarchical-browse-definition.ts @@ -1,6 +1,6 @@ import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize'; import { SortOption } from './sort-option.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; import { BrowseDefinition } from './browse-definition.model'; /** diff --git a/src/app/core/shared/value-list-browse-definition.model.ts b/src/app/core/shared/value-list-browse-definition.model.ts index e22742bb78..0302ec59c7 100644 --- a/src/app/core/shared/value-list-browse-definition.model.ts +++ b/src/app/core/shared/value-list-browse-definition.model.ts @@ -5,7 +5,7 @@ import { VALUE_LIST_BROWSE_DEFINITION } from './value-list-browse-definition.res import { ResourceType } from './resource-type'; import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition'; import { HALLink } from './hal-link.model'; -import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type'; /** * BrowseDefinition model for browses of type 'valueList' diff --git a/src/app/navbar/navbar.component.spec.ts b/src/app/navbar/navbar.component.spec.ts index 983eace055..db1c448409 100644 --- a/src/app/navbar/navbar.component.spec.ts +++ b/src/app/navbar/navbar.component.spec.ts @@ -16,7 +16,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { BrowseService } from '../core/browse/browse.service'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { buildPaginatedList } from '../core/data/paginated-list.model'; -import { BrowseByDataType } from '../browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../browse-by/browse-by-switcher/browse-by-data-type'; import { Item } from '../core/shared/item.model'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { ThemeService } from '../shared/theme-support/theme.service'; diff --git a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts index fcbe15cfae..58a38c41e5 100644 --- a/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-date-page/browse-by-date-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByDatePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-date-page/browse-by-date-page.component'; -import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type'; +import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; import { Context } from '../../../../../app/core/shared/context.model'; @Component({ diff --git a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 533dca29ca..ef57478087 100644 --- a/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByMetadataPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; -import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type'; +import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; import { Context } from '../../../../../app/core/shared/context.model'; @Component({ diff --git a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index afc0292bef..b8fb968c76 100644 --- a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component'; -import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type'; +import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; import { Context } from '../../../../../app/core/shared/context.model'; @Component({ diff --git a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts index 5424293529..4f6a1a5020 100644 --- a/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts +++ b/src/themes/custom/app/browse-by/browse-by-title-page/browse-by-title-page.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import { BrowseByTitlePageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-title-page/browse-by-title-page.component'; -import { rendersBrowseBy, BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; +import { BrowseByDataType } from '../../../../../app/browse-by/browse-by-switcher/browse-by-data-type'; +import { rendersBrowseBy } from '../../../../../app/browse-by/browse-by-switcher/browse-by-decorator'; import { Context } from '../../../../../app/core/shared/context.model'; @Component({ From e7f5c48d8cf499024b1efbf6b0a19a1bf4dfb0bf Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 13 Dec 2023 18:00:06 +0100 Subject: [PATCH 25/94] [CST-12825] Ror integration --- .../org-unit/org-unit.component.html | 8 +++++ .../metadata-values.component.html | 11 +++++-- .../metadata-values.component.ts | 2 ++ src/app/item-page/item-shared.module.ts | 33 ++++++++++++++----- .../generic-item-page-field.component.ts | 2 ++ .../item-page-field.component.html | 1 + .../item-page-field.component.ts | 2 ++ src/assets/i18n/en.json5 | 24 +++++++++++++- src/assets/images/ror-icon.svg | 16 +++++++++ 9 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 src/assets/images/ror-icon.svg diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index c79d19e267..30bf7c4083 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -14,6 +14,14 @@ > + + diff --git a/src/app/item-page/field-components/metadata-values/metadata-values.component.html b/src/app/item-page/field-components/metadata-values/metadata-values.component.html index 61088edd16..2f822e11be 100644 --- a/src/app/item-page/field-components/metadata-values/metadata-values.component.html +++ b/src/app/item-page/field-components/metadata-values/metadata-values.component.html @@ -3,8 +3,8 @@ - + @@ -23,6 +23,13 @@ + + + + + {{value}} diff --git a/src/app/item-page/field-components/metadata-values/metadata-values.component.ts b/src/app/item-page/field-components/metadata-values/metadata-values.component.ts index cbbae9006d..2ec31159bc 100644 --- a/src/app/item-page/field-components/metadata-values/metadata-values.component.ts +++ b/src/app/item-page/field-components/metadata-values/metadata-values.component.ts @@ -55,6 +55,8 @@ export class MetadataValuesComponent implements OnChanges { @Input() browseDefinition?: BrowseDefinition; + @Input() img?: string; + ngOnChanges(changes: SimpleChanges): void { this.renderMarkdown = !!this.appConfig.markdown.enabled && this.enableMarkdown; } diff --git a/src/app/item-page/item-shared.module.ts b/src/app/item-page/item-shared.module.ts index 9c2bbba619..7a0ac43629 100644 --- a/src/app/item-page/item-shared.module.ts +++ b/src/app/item-page/item-shared.module.ts @@ -1,17 +1,31 @@ -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 @@ -42,7 +56,8 @@ const COMPONENTS = [ CommonModule, SearchModule, SharedModule, - TranslateModule + TranslateModule, + NgOptimizedImage ], exports: [ ...COMPONENTS diff --git a/src/app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.ts b/src/app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.ts index 53d2f6aa20..99a68745c1 100644 --- a/src/app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.ts +++ b/src/app/item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.ts @@ -45,5 +45,7 @@ export class GenericItemPageFieldComponent extends ItemPageFieldComponent { */ @Input() urlRegex?: string; + @Input() img?: string; + } diff --git a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.html b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.html index 91d40b0ad7..f45d4657a4 100644 --- a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.html +++ b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.html @@ -6,5 +6,6 @@ [enableMarkdown]="enableMarkdown" [urlRegex]="urlRegex" [browseDefinition]="browseDefinition|async" + [img]="img" >
diff --git a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts index fc526dabcc..99e5ae7d36 100644 --- a/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts +++ b/src/app/item-page/simple/field-components/specific-field/item-page-field.component.ts @@ -51,6 +51,8 @@ export class ItemPageFieldComponent { */ urlRegex?: string; + img?: string; + /** * 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.) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 643a3ce0d1..325dc9daab 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3114,6 +3114,8 @@ "orgunit.page.titleprefix": "Organizational Unit: ", + "orgunit.page.ror": "ROR Identifier", + "pagination.options.description": "Pagination options", "pagination.results-per-page": "Results Per Page", @@ -4074,6 +4076,8 @@ "submission.import-external.source.lcname": "Library of Congress Names", + "submission.import-external.source.ror": "Research Organization Registry (ROR)", + "submission.import-external.preview.title": "Item Preview", "submission.import-external.preview.title.Publication": "Publication Preview", @@ -4166,6 +4170,8 @@ "submission.sections.describe.relationship-lookup.external-source.import-modal.head.arxiv": "Importing from arXiv", + "submission.sections.describe.relationship-lookup.external-source.import-modal.head.ror": "Import from ROR", + "submission.sections.describe.relationship-lookup.external-source.import-modal.import": "Import", "submission.sections.describe.relationship-lookup.external-source.import-modal.Journal.title": "Import Remote Journal", @@ -4188,6 +4194,12 @@ "submission.sections.describe.relationship-lookup.external-source.import-modal.select": "Select a local match:", + "submission.sections.describe.relationship-lookup.external-source.import-modal.isOrgUnitOfProject.title": "Import Remote Organization", + + "submission.sections.describe.relationship-lookup.external-source.import-modal.isOrgUnitOfProject.added.local-entity": "Successfully added local organization to the selection", + + "submission.sections.describe.relationship-lookup.external-source.import-modal.isOrgUnitOfProject.added.new-entity": "Successfully imported and added external organization to the selection", + "submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all", "submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page", @@ -4244,6 +4256,8 @@ "submission.sections.describe.relationship-lookup.search-tab.tab-title.arxiv": "arXiv ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.ror": "ROR ({{ count }})", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingAgencyOfPublication": "Search for Funding Agencies", "submission.sections.describe.relationship-lookup.search-tab.tab-title.isFundingOfPublication": "Search for Funding", @@ -4258,6 +4272,8 @@ "submission.sections.describe.relationship-lookup.search-tab.tab-title.isPublicationOfAuthor": "Publication of the Author", + "submission.sections.describe.relationship-lookup.search-tab.tab-title.isOrgUnitOfProject": "OrgUnit of the Project", + "submission.sections.describe.relationship-lookup.selection-tab.title.openAIREFunding": "Funding OpenAIRE API", "submission.sections.describe.relationship-lookup.selection-tab.title.isProjectOfPublication": "Project", @@ -4304,6 +4320,8 @@ "submission.sections.describe.relationship-lookup.title.isPublicationOfAuthor": "Publication", + "submission.sections.describe.relationship-lookup.title.isOrgUnitOfProject": "OrgUnit", + "submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown", "submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings", @@ -4366,6 +4384,8 @@ "submission.sections.describe.relationship-lookup.selection-tab.title.wos": "Search Results", + "submission.sections.describe.relationship-lookup.selection-tab.title.ror": "Search Results", + "submission.sections.describe.relationship-lookup.selection-tab.title": "Search Results", "submission.sections.describe.relationship-lookup.name-variant.notification.content": "Would you like to save \"{{ value }}\" as a name variant for this person so you and others can reuse it for future submissions? If you don't you can still use it for this submission.", @@ -4836,6 +4856,8 @@ "supervision.search.results.head": "Workflow and Workspace tasks", + "orgunit.search.results.head": "Organizational Unit Search Results", + "workflow-item.edit.breadcrumbs": "Edit workflowitem", "workflow-item.edit.title": "Edit workflowitem", @@ -5294,6 +5316,6 @@ "access-control-option-end-date-note": "Select the date until which the related access condition is applied", - "vocabulary-treeview.search.form.add": "Add", + "vocabulary-treeview.search.form.add": "Add" } diff --git a/src/assets/images/ror-icon.svg b/src/assets/images/ror-icon.svg new file mode 100644 index 0000000000..24735df519 --- /dev/null +++ b/src/assets/images/ror-icon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + From 56d33387b5d5345ed38c8a8078d3aa2228e855a6 Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Fri, 15 Dec 2023 10:05:03 +0100 Subject: [PATCH 26/94] [CST-12825] Fixes ROR metadata display --- .../item-pages/org-unit/org-unit.component.html | 16 ++++++++-------- .../metadata-values.component.html | 3 ++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 30bf7c4083..2fd9031c9b 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -14,14 +14,6 @@ > - - @@ -40,6 +32,14 @@
+ + From 7942c900f4697368e1583eeab0ba6b5f73be3b9e Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 17 Dec 2023 16:17:16 +0100 Subject: [PATCH 27/94] Component doesn't have to be initialised anymore in ngOnChanges because connectInputsAndOutputs will automatically call the child component's ngOnChanges already --- .../abstract-component-loader.component.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts index 12f0f02821..888edffe67 100644 --- a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -2,7 +2,7 @@ import { Component, ComponentRef, Input, OnChanges, OnDestroy, OnInit, SimpleCha import { Context } from '../../core/shared/context.model'; import { ThemeService } from '../theme-support/theme.service'; import { GenericConstructor } from '../../core/shared/generic-constructor'; -import { hasNoValue, hasValue, isNotEmpty } from '../empty.util'; +import { hasValue, isNotEmpty } from '../empty.util'; import { Subscription } from 'rxjs'; import { DynamicComponentLoaderDirective } from './dynamic-component-loader.directive'; @@ -57,21 +57,15 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC * Set up the dynamic child component */ ngOnInit(): void { - if (hasNoValue(this.compRef)) { - this.instantiateComponent(); - } + this.instantiateComponent(); } /** * Whenever the inputs change, update the inputs of the dynamic component */ ngOnChanges(changes: SimpleChanges): void { - if (hasNoValue(this.compRef)) { - // sometimes the component has not been initialized yet, so it first needs to be initialized - // before being called again - this.instantiateComponent(); - } else { - if (this.inputNamesDependentForComponent.some((name: any) => hasValue(changes[name]) && changes[name].previousValue !== changes[name].currentValue)) { + if (hasValue(this.compRef)) { + if (this.inputNamesDependentForComponent.some((name: keyof this & string) => hasValue(changes[name]) && changes[name].previousValue !== changes[name].currentValue)) { // Recreate the component when the @Input()s used by getComponent() aren't up-to-date anymore this.destroyComponentInstance(); this.instantiateComponent(); @@ -123,7 +117,7 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC /** * Connect the inputs and outputs of this component to the dynamic component, - * to ensure they're in sync + * to ensure they're in sync, the ngOnChanges method will automatically be called by setInput */ public connectInputsAndOutputs(): void { if (isNotEmpty(this.inputNames) && hasValue(this.compRef) && hasValue(this.compRef.instance)) { From d979ea5c9f4dc8ae8d0020398ea8262c4785ffbb Mon Sep 17 00:00:00 2001 From: Vincenzo Mecca Date: Wed, 20 Dec 2023 11:47:33 +0100 Subject: [PATCH 28/94] [CST-12825] Removed unecessary escape --- .../item-pages/org-unit/org-unit.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 2fd9031c9b..92a13e9672 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -37,7 +37,7 @@ [img]="'./assets/images/ror-icon.svg'" [item]="object" [label]="'orgunit.page.ror'" - [urlRegex]="'(.*)ror\.org'" + [urlRegex]="'(.*)ror.org'" > Date: Wed, 20 Dec 2023 11:56:25 +0100 Subject: [PATCH 29/94] [CST-12825] Fixes comma in en.json5 --- src/assets/i18n/en.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 325dc9daab..a3a6942909 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -5316,6 +5316,6 @@ "access-control-option-end-date-note": "Select the date until which the related access condition is applied", - "vocabulary-treeview.search.form.add": "Add" + "vocabulary-treeview.search.form.add": "Add", } From e5cf16c4896a29a5ae40d3de5545ef8597a7197e Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 21 Dec 2023 14:52:39 +0100 Subject: [PATCH 30/94] fix lint --- src/app/core/data/data.service.ts | 1 - src/app/shared/mocks/suggestion.mock.ts | 1 - src/app/suggestions-page/suggestions-page-routing.module.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 1433ebacce..43b60f874d 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -25,7 +25,6 @@ import { RequestParam } from '../cache/models/request-param.model'; import { ObjectCacheEntry } from '../cache/object-cache.reducer'; import { ObjectCacheService } from '../cache/object-cache.service'; import { DSpaceSerializer } from '../dspace-rest/dspace.serializer'; -import { DSpaceObject } from '../shared/dspace-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { getFirstCompletedRemoteData, getFirstSucceededRemoteData, getRemoteDataPayload } from '../shared/operators'; import { URLCombiner } from '../url-combiner/url-combiner'; diff --git a/src/app/shared/mocks/suggestion.mock.ts b/src/app/shared/mocks/suggestion.mock.ts index fd1a7c41f1..8b1ab7acd5 100644 --- a/src/app/shared/mocks/suggestion.mock.ts +++ b/src/app/shared/mocks/suggestion.mock.ts @@ -1,7 +1,6 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { Item } from '../../core/shared/item.model'; import { SearchResult } from '../search/models/search-result.model'; -import { SuggestionsService } from '../../suggestion-notifications/reciter-suggestions/suggestions.service'; // REST Mock --------------------------------------------------------------------- // ------------------------------------------------------------------------------- diff --git a/src/app/suggestions-page/suggestions-page-routing.module.ts b/src/app/suggestions-page/suggestions-page-routing.module.ts index 20ed658707..05dc6321b7 100644 --- a/src/app/suggestions-page/suggestions-page-routing.module.ts +++ b/src/app/suggestions-page/suggestions-page-routing.module.ts @@ -5,7 +5,6 @@ import { SuggestionsPageResolver } from './suggestions-page.resolver'; import { SuggestionsPageComponent } from './suggestions-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; @NgModule({ imports: [ From 4106e438d6f1cc304bd7bd1ba1c2ff1f90dcedc6 Mon Sep 17 00:00:00 2001 From: frabacche Date: Fri, 22 Dec 2023 12:18:58 +0100 Subject: [PATCH 31/94] CST-5249_suggestion test fixes --- .../workspaceitem-data.service.spec.ts | 11 ++++-- .../submission/workspaceitem-data.service.ts | 37 ++++++++++++++++++- .../suggestions-popup.component.spec.ts | 1 + src/config/app-config.interface.ts | 2 +- src/environments/environment.test.ts | 1 + 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/app/core/submission/workspaceitem-data.service.spec.ts b/src/app/core/submission/workspaceitem-data.service.spec.ts index e766a6a039..50bc03a653 100644 --- a/src/app/core/submission/workspaceitem-data.service.spec.ts +++ b/src/app/core/submission/workspaceitem-data.service.spec.ts @@ -77,23 +77,26 @@ describe('WorkspaceitemDataService test', () => { const notificationsService = {} as NotificationsService; const http = {} as HttpClient; const comparator = {} as any; - const comparatorEntry = {} as any; const store = {} as Store; const pageInfo = new PageInfo(); function initTestService() { hrefOnlyDataService = getMockHrefOnlyDataService(); return new WorkspaceitemDataService( + comparator, + halService, + http, + notificationsService, requestService, rdbService, objectCache, - halService, - notificationsService, + store ); } describe('composition', () => { - const initService = () => new WorkspaceitemDataService(null, null, null, null, null); + const initService = () => new WorkspaceitemDataService(null, null, + null, null, null, null, null, null); testSearchDataImplementation(initService); testDeleteDataImplementation(initService); }); diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 8a036f6443..7352718c53 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -23,15 +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 { Bitstream } from '../shared/bitstream.model'; +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 { +export class WorkspaceitemDataService extends IdentifiableDataService implements SearchData, DeleteData { protected linkPath = 'workspaceitems'; protected searchByItemLinkPath = 'item'; + private searchData: SearchDataImpl; private deleteData: DeleteData; constructor( @@ -45,6 +49,7 @@ export class WorkspaceitemDataService extends IdentifiableDataService) { 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> { @@ -93,4 +98,34 @@ export class WorkspaceitemDataService extends IdentifiableDataService>} + * Return an observable that emits response from the server + */ + public searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + + /** + * 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> { + return this.deleteData.deleteByHref(href, copyVirtualMetadata); + } } diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts index 67678354ca..e276b7d5d3 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts @@ -71,6 +71,7 @@ describe('SuggestionsPopupComponent', () => { }); it('should show a notification when new publication suggestions are available', () => { + expect((component as any).notificationsService.success).toHaveBeenCalled(); expect(suggestionStateService.dispatchMarkUserSuggestionsAsVisitedAction).toHaveBeenCalled(); }); diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index a0d9156415..f89623e018 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -14,7 +14,7 @@ import { AuthConfig } from './auth-config.interfaces'; import { UIServerConfig } from './ui-server-config.interface'; import { MediaViewerConfig } from './media-viewer-config.interface'; import { BrowseByConfig } from './browse-by-config.interface'; -import {SuggestionConfig} from './layout-config.interfaces'; +import { SuggestionConfig } from './layout-config.interfaces'; import { BundleConfig } from './bundle-config.interface'; import { ActuatorsConfig } from './actuators.config'; import { InfoConfig } from './info-config.interface'; diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 8b90676462..0573aebebc 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -4,6 +4,7 @@ import { RestRequestMethod } from '../app/core/data/rest-request-method'; import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; export const environment: BuildConfig = { + suggestion: [], production: false, // Angular Universal settings From 480c7a6ce0664e3ca3d3a096dff45e59e8519e87 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 26 Dec 2023 14:31:07 +0100 Subject: [PATCH 32/94] Destroy dynamically generated components in onDestroy & replace deprecated createComponent --- ...in-search-result-grid-element.component.ts | 43 +++++++++++------- ...t-admin-workflow-grid-element.component.ts | 45 ++++++++++++------- .../loading/themed-loading.component.ts | 5 +-- ...etadata-representation-loader.component.ts | 19 +++++--- .../claimed-task-actions-loader.component.ts | 16 +++---- ...table-object-component-loader.component.ts | 12 +++-- .../shared/theme-support/themed.component.ts | 5 +-- 7 files changed, 88 insertions(+), 57 deletions(-) diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts index 1ab8fee8c2..06970661d5 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, OnInit, ViewChild, ComponentRef, OnDestroy } from '@angular/core'; import { Item } from '../../../../../core/shared/item.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { @@ -13,6 +13,7 @@ import { BitstreamDataService } from '../../../../../core/data/bitstream-data.se import { GenericConstructor } from '../../../../../core/shared/generic-constructor'; import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive'; import { ThemeService } from '../../../../../shared/theme-support/theme.service'; +import { hasValue } from '../../../../../shared/empty.util'; @listableObjectComponent(ItemSearchResult, ViewMode.GridElement, Context.AdminSearch) @Component({ @@ -23,15 +24,16 @@ import { ThemeService } from '../../../../../shared/theme-support/theme.service' /** * The component for displaying a list element for an item search result on the admin search page */ -export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnInit { +export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent implements OnDestroy, OnInit { @ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective; @ViewChild('badges', { static: true }) badges: ElementRef; @ViewChild('buttons', { static: true }) buttons: ElementRef; + protected compRef: ComponentRef; + constructor(protected truncatableService: TruncatableService, protected bitstreamDataService: BitstreamDataService, private themeService: ThemeService, - private componentFactoryResolver: ComponentFactoryResolver ) { super(truncatableService, bitstreamDataService); } @@ -41,23 +43,32 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE */ ngOnInit(): void { super.ngOnInit(); - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); + const component: GenericConstructor = this.getComponent(); const viewContainerRef = this.listableObjectDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent( - componentFactory, - 0, - undefined, - [ - [this.badges.nativeElement], - [this.buttons.nativeElement] - ]); - (componentRef.instance as any).object = this.object; - (componentRef.instance as any).index = this.index; - (componentRef.instance as any).linkType = this.linkType; - (componentRef.instance as any).listID = this.listID; + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + projectableNodes: [ + [this.badges.nativeElement], + [this.buttons.nativeElement], + ], + }, + ); + (this.compRef.instance as any).object = this.object; + (this.compRef.instance as any).index = this.index; + (this.compRef.instance as any).linkType = this.linkType; + (this.compRef.instance as any).listID = this.listID; + } + + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } } /** diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts index 68f10916d5..14d6e81aca 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, ElementRef, ViewChild } from '@angular/core'; +import { Component, ElementRef, ViewChild, ComponentRef, OnDestroy, OnInit } from '@angular/core'; import { Item } from '../../../../../core/shared/item.model'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { @@ -23,6 +23,7 @@ import { import { take } from 'rxjs/operators'; import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model'; import { ThemeService } from '../../../../../shared/theme-support/theme.service'; +import { hasValue } from '../../../../../shared/empty.util'; @listableObjectComponent(WorkflowItemSearchResult, ViewMode.GridElement, Context.AdminWorkflowSearch) @Component({ @@ -33,7 +34,7 @@ import { ThemeService } from '../../../../../shared/theme-support/theme.service' /** * The component for displaying a grid element for an workflow item on the admin workflow search page */ -export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent { +export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent implements OnDestroy, OnInit { /** * Directive used to render the dynamic component in */ @@ -54,8 +55,9 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S */ public item$: Observable; + protected compRef: ComponentRef; + constructor( - private componentFactoryResolver: ComponentFactoryResolver, private linkService: LinkService, protected truncatableService: TruncatableService, private themeService: ThemeService, @@ -73,28 +75,37 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S this.dso = this.linkService.resolveLink(this.dso, followLink('item')); this.item$ = (this.dso.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload()); this.item$.pipe(take(1)).subscribe((item: Item) => { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item)); + const component: GenericConstructor = this.getComponent(item); const viewContainerRef = this.listableObjectDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent( - componentFactory, - 0, - undefined, - [ - [this.badges.nativeElement], - [this.buttons.nativeElement] - ]); - (componentRef.instance as any).object = item; - (componentRef.instance as any).index = this.index; - (componentRef.instance as any).linkType = this.linkType; - (componentRef.instance as any).listID = this.listID; - componentRef.changeDetectorRef.detectChanges(); + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + projectableNodes: [ + [this.badges.nativeElement], + [this.buttons.nativeElement], + ], + }, + ); + (this.compRef.instance as any).object = item; + (this.compRef.instance as any).index = this.index; + (this.compRef.instance as any).linkType = this.linkType; + (this.compRef.instance as any).listID = this.listID; + this.compRef.changeDetectorRef.detectChanges(); } ); } + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } + } + /** * Fetch the component depending on the item's entity type, view mode and context * @returns {GenericConstructor} diff --git a/src/app/shared/loading/themed-loading.component.ts b/src/app/shared/loading/themed-loading.component.ts index ffdf9d3cbe..5b45cff904 100644 --- a/src/app/shared/loading/themed-loading.component.ts +++ b/src/app/shared/loading/themed-loading.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, ComponentFactoryResolver, ChangeDetectorRef } from '@angular/core'; +import { Component, Input, ChangeDetectorRef } from '@angular/core'; import { ThemedComponent } from '../theme-support/themed.component'; import { LoadingComponent } from './loading.component'; import { ThemeService } from '../theme-support/theme.service'; @@ -20,11 +20,10 @@ export class ThemedLoadingComponent extends ThemedComponent { protected inAndOutputNames: (keyof LoadingComponent & keyof this)[] = ['message', 'showMessage', 'spinner']; constructor( - protected resolver: ComponentFactoryResolver, protected cdr: ChangeDetectorRef, protected themeService: ThemeService ) { - super(resolver, cdr, themeService); + super(cdr, themeService); } protected getComponentName(): string { diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts index 7077949809..ddb764ed15 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, Inject, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Inject, Input, OnInit, ViewChild, ComponentRef, OnDestroy } from '@angular/core'; import { MetadataRepresentation, MetadataRepresentationType @@ -19,8 +19,9 @@ import { ThemeService } from '../theme-support/theme.service'; /** * Component for determining what component to use depending on the item's entity type (dspace.entity.type), its metadata representation and, optionally, its context */ -export class MetadataRepresentationLoaderComponent implements OnInit { +export class MetadataRepresentationLoaderComponent implements OnDestroy, OnInit { private componentRefInstance: MetadataRepresentationListElementComponent; + protected compRef: ComponentRef; /** * The item or metadata to determine the component for @@ -47,7 +48,6 @@ export class MetadataRepresentationLoaderComponent implements OnInit { @ViewChild(MetadataRepresentationDirective, {static: true}) mdRepDirective: MetadataRepresentationDirective; constructor( - private componentFactoryResolver: ComponentFactoryResolver, private themeService: ThemeService, @Inject(METADATA_REPRESENTATION_COMPONENT_FACTORY) private getMetadataRepresentationComponent: (entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context, theme: string) => GenericConstructor, ) { @@ -57,16 +57,23 @@ export class MetadataRepresentationLoaderComponent implements OnInit { * Set up the dynamic child component */ ngOnInit(): void { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent()); + const component: GenericConstructor = this.getComponent(); const viewContainerRef = this.mdRepDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent(componentFactory); - this.componentRefInstance = componentRef.instance as MetadataRepresentationListElementComponent; + this.compRef = viewContainerRef.createComponent(component); + this.componentRefInstance = this.compRef.instance as MetadataRepresentationListElementComponent; this.componentRefInstance.metadataRepresentation = this.mdRepresentation; } + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } + } + /** * Fetch the component depending on the item's entity type, metadata representation type and context * @returns {string} diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index 6a14aeb5bc..e835c1b1da 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -1,12 +1,11 @@ import { Component, - ComponentFactoryResolver, EventEmitter, Input, OnDestroy, OnInit, Output, - ViewChild + ViewChild, ComponentRef } from '@angular/core'; import { getComponentByWorkflowTaskOption } from './claimed-task-actions-decorator'; import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model'; @@ -64,8 +63,7 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { */ protected subs: Subscription[] = []; - constructor(private componentFactoryResolver: ComponentFactoryResolver) { - } + protected compRef: ComponentRef; /** * Fetch, create and initialize the relevant component @@ -74,13 +72,11 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { const comp = this.getComponentByWorkflowTaskOption(this.option); if (hasValue(comp)) { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp); - const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent(componentFactory); - const componentInstance = (componentRef.instance as ClaimedTaskActionsAbstractComponent); + this.compRef = viewContainerRef.createComponent(comp); + const componentInstance = (this.compRef.instance as ClaimedTaskActionsAbstractComponent); componentInstance.item = this.item; componentInstance.object = this.object; componentInstance.workflowitem = this.workflowitem; @@ -98,6 +94,10 @@ export class ClaimedTaskActionsLoaderComponent implements OnInit, OnDestroy { * Unsubscribe from open subscriptions */ ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index 6b75c59181..747a32cfec 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -23,7 +23,7 @@ import { getListableObjectComponent } from './listable-object.decorator'; import { GenericConstructor } from '../../../../core/shared/generic-constructor'; import { ListableObjectDirective } from './listable-object.directive'; import { CollectionElementLinkType } from '../../collection-element-link.type'; -import { hasValue, isNotEmpty } from '../../../empty.util'; +import { hasValue, isNotEmpty, hasNoValue } from '../../../empty.util'; import { DSpaceObject } from '../../../../core/shared/dspace-object.model'; import { ThemeService } from '../../../theme-support/theme.service'; @@ -141,7 +141,9 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges * Setup the dynamic child component */ ngOnInit(): void { - this.instantiateComponent(this.object); + if (hasNoValue(this.compRef)) { + this.instantiateComponent(this.object); + } } /** @@ -153,7 +155,11 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges } } - ngOnDestroy() { + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } this.subs .filter((subscription) => hasValue(subscription)) .forEach((subscription) => subscription.unsubscribe()); diff --git a/src/app/shared/theme-support/themed.component.ts b/src/app/shared/theme-support/themed.component.ts index 87f182a5ff..2b75f5429c 100644 --- a/src/app/shared/theme-support/themed.component.ts +++ b/src/app/shared/theme-support/themed.component.ts @@ -6,7 +6,6 @@ import { SimpleChanges, OnInit, OnDestroy, - ComponentFactoryResolver, ChangeDetectorRef, OnChanges } from '@angular/core'; @@ -31,7 +30,6 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges protected inAndOutputNames: (keyof T & keyof this)[] = []; constructor( - protected resolver: ComponentFactoryResolver, protected cdr: ChangeDetectorRef, protected themeService: ThemeService ) { @@ -87,8 +85,7 @@ export abstract class ThemedComponent implements OnInit, OnDestroy, OnChanges } }), ).subscribe((constructor: GenericConstructor) => { - const factory = this.resolver.resolveComponentFactory(constructor); - this.compRef = this.vcr.createComponent(factory); + this.compRef = this.vcr.createComponent(constructor); this.connectInputsAndOutputs(); this.cdr.markForCheck(); }); From 5feaa1b5a66fe6a36d33b9b48dbbd0232daa9f75 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Wed, 27 Dec 2023 13:00:55 +0100 Subject: [PATCH 33/94] fix tests --- .../workspaceitem-data.service.spec.ts | 26 +++++++++++-------- .../submission/workspaceitem-data.service.ts | 14 ++++++++-- .../suggestions-popup.component.spec.ts | 10 ++++--- src/environments/environment.test.ts | 4 ++- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/app/core/submission/workspaceitem-data.service.spec.ts b/src/app/core/submission/workspaceitem-data.service.spec.ts index e766a6a039..039e942317 100644 --- a/src/app/core/submission/workspaceitem-data.service.spec.ts +++ b/src/app/core/submission/workspaceitem-data.service.spec.ts @@ -21,6 +21,9 @@ 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'; describe('WorkspaceitemDataService test', () => { let scheduler: TestScheduler; @@ -68,15 +71,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; const pageInfo = new PageInfo(); @@ -84,18 +84,23 @@ describe('WorkspaceitemDataService test', () => { function initTestService() { hrefOnlyDataService = getMockHrefOnlyDataService(); return new WorkspaceitemDataService( + comparatorEntry, + halService, + http, + notificationsService, requestService, rdbService, objectCache, - halService, - notificationsService, + store ); } describe('composition', () => { - const initService = () => new WorkspaceitemDataService(null, null, null, null, null); - testSearchDataImplementation(initService); - testDeleteDataImplementation(initService); + const initSearchService = () => new WorkspaceitemDataService(null, null, null, null, null, null, null, null) as unknown as SearchData; + const initDeleteService = () => new WorkspaceitemDataService(null, null, null, null, null, null, null, null) as unknown as DeleteData; + + testSearchDataImplementation(initSearchService); + testDeleteDataImplementation(initDeleteService); }); describe('', () => { @@ -126,7 +131,6 @@ describe('WorkspaceitemDataService test', () => { service = initTestService(); spyOn((service as any), 'findByHref').and.callThrough(); - spyOn((service as any), 'getSearchByHref').and.returnValue(searchRequestURL$); }); afterEach(() => { @@ -137,8 +141,8 @@ describe('WorkspaceitemDataService test', () => { it('should proxy the call to DataService.findByHref', () => { scheduler.schedule(() => service.findByItem('1234-1234', true, true, pageInfo)); scheduler.flush(); - - expect((service as any).findByHref).toHaveBeenCalledWith(searchRequestURL$, true, true); + 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 for the search', () => { diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 8a036f6443..e2e7274e10 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -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 { +export class WorkspaceitemDataService extends IdentifiableDataService implements DeleteData, SearchData{ protected linkPath = 'workspaceitems'; protected searchByItemLinkPath = 'item'; private deleteData: DeleteData; + private searchData: SearchData; constructor( protected comparator: DSOChangeAnalyzer, @@ -45,7 +48,7 @@ export class WorkspaceitemDataService extends IdentifiableDataService) { 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> { return this.deleteData.delete(objectId, copyVirtualMetadata); @@ -93,4 +96,11 @@ export class WorkspaceitemDataService extends IdentifiableDataService> { + return this.deleteData.deleteByHref(href, copyVirtualMetadata); + } + + searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } } diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts index 67678354ca..aade2c78fa 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts @@ -13,11 +13,13 @@ import { SuggestionsService } from '../suggestions.service'; describe('SuggestionsPopupComponent', () => { let component: SuggestionsPopupComponent; let fixture: ComponentFixture; + let notificationsService: NotificationsService; const suggestionStateService = jasmine.createSpyObj('SuggestionTargetsStateService', { hasUserVisitedSuggestions: jasmine.createSpy('hasUserVisitedSuggestions'), getCurrentUserSuggestionTargets: jasmine.createSpy('getCurrentUserSuggestionTargets'), - dispatchMarkUserSuggestionsAsVisitedAction: jasmine.createSpy('dispatchMarkUserSuggestionsAsVisitedAction') + dispatchMarkUserSuggestionsAsVisitedAction: jasmine.createSpy('dispatchMarkUserSuggestionsAsVisitedAction'), + dispatchRefreshUserSuggestionsAction: jasmine.createSpy('dispatchRefreshUserSuggestionsAction') }); const mockNotificationInterpolation = { count: 12, source: 'source', suggestionId: 'id', displayName: 'displayName' }; @@ -60,18 +62,20 @@ 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; + notificationsService = (component as any).notificationsService; fixture.detectChanges(); }); it('should show a notification when new publication suggestions are available', () => { - expect((component as any).notificationsService.success).toHaveBeenCalled(); + expect(notificationsService.success).toHaveBeenCalled(); + expect(suggestionStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); expect(suggestionStateService.dispatchMarkUserSuggestionsAsVisitedAction).toHaveBeenCalled(); }); diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 8b90676462..afc4082dde 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -319,5 +319,7 @@ export const environment: BuildConfig = { vocabulary: 'srsc', enabled: true } - ] + ], + + suggestion: [] }; From ffd118abf9b427245b6a0ca502f0c55aea910966 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Wed, 27 Dec 2023 13:26:04 +0100 Subject: [PATCH 34/94] align with github --- src/environments/environment.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 0573aebebc..afc4082dde 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -4,7 +4,6 @@ import { RestRequestMethod } from '../app/core/data/rest-request-method'; import { NotificationAnimationsType } from '../app/shared/notifications/models/notification-animations-type'; export const environment: BuildConfig = { - suggestion: [], production: false, // Angular Universal settings @@ -320,5 +319,7 @@ export const environment: BuildConfig = { vocabulary: 'srsc', enabled: true } - ] + ], + + suggestion: [] }; From 3149842bcbcfd769dc69e8b6ec14450282d7bd7b Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Tue, 9 Jan 2024 16:17:50 +0100 Subject: [PATCH 35/94] add data services tests --- .../workspaceitem-data.service.spec.ts | 26 +++- .../source/suggestion-source-data.service.ts | 21 ++- .../suggestions-source-data.service.spec.ts | 115 +++++++++++++++ .../suggestions-data.service.ts | 12 +- .../target/suggestion-target-data.service.ts | 61 +++++--- .../suggestions-target-data.service.spec.ts | 138 ++++++++++++++++++ 6 files changed, 336 insertions(+), 37 deletions(-) create mode 100644 src/app/core/suggestion-notifications/reciter-suggestions/source/suggestions-source-data.service.spec.ts create mode 100644 src/app/core/suggestion-notifications/reciter-suggestions/target/suggestions-target-data.service.spec.ts diff --git a/src/app/core/submission/workspaceitem-data.service.spec.ts b/src/app/core/submission/workspaceitem-data.service.spec.ts index 039e942317..34548c1e53 100644 --- a/src/app/core/submission/workspaceitem-data.service.spec.ts +++ b/src/app/core/submission/workspaceitem-data.service.spec.ts @@ -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'; @@ -24,6 +24,8 @@ 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; @@ -109,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; @@ -125,7 +127,8 @@ describe('WorkspaceitemDataService test', () => { rdbService = jasmine.createSpyObj('rdbService', { buildSingle: hot('a|', { a: wsiRD - }) + }), + buildFromRequestUUID: createSuccessfulRemoteDataObject$({}) }); service = initTestService(); @@ -154,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(); + }); + }); + }); + }); }); diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts b/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts index c3e142044e..15b067f2ed 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts @@ -53,7 +53,7 @@ export class SuggestionSourceDataService extends IdentifiableDataService[]): Observable>> { - return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + return this.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } /** @@ -71,4 +71,23 @@ export class SuggestionSourceDataService extends IdentifiableDataService[]): Observable> { 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>>} + * Return an observable that emits object list + */ + findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } } diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestions-source-data.service.spec.ts b/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestions-source-data.service.spec.ts new file mode 100644 index 0000000000..5f8d51a641 --- /dev/null +++ b/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestions-source-data.service.spec.ts @@ -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; + let responseCacheEntry: RequestEntry; + + const store = {} as Store; + 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; + 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; + 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); + }); + }); +}); diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts b/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts index 944a13e3a3..407ea6a7e6 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts @@ -33,7 +33,7 @@ import { SuggestionTargetDataService } from './target/suggestion-target-data.ser /** * A private DataService implementation to delegate specific methods to. */ -class SuggestionDataServiceImpl extends DataService { +export class SuggestionDataServiceImpl extends DataService { /** * The REST endpoint. */ @@ -70,7 +70,6 @@ class SuggestionDataServiceImpl extends DataService { @dataService(SUGGESTION) export class SuggestionsDataService { protected searchFindBySourceMethod = 'findBySource'; - protected searchFindByTargetMethod = 'findByTarget'; protected searchFindByTargetAndSourceMethod = 'findByTargetAndSource'; /** @@ -168,7 +167,6 @@ export class SuggestionsDataService { ...linksToFollow: FollowLinkConfig[] ): Observable>> { options.searchParams = [new RequestParam('target', userId)]; - //return this.suggestionTargetsDataService.getTargetsByUser(this.searchFindByTargetMethod, options, ...linksToFollow); return this.suggestionTargetsDataService.getTargetsByUser(userId, options, ...linksToFollow); } @@ -193,14 +191,6 @@ export class SuggestionsDataService { return this.suggestionsDataService.delete(suggestionId); } - /** - * Used to fetch Suggestion notification for user - * @suggestionId - */ - public getSuggestion(suggestionId: string, ...linksToFollow: FollowLinkConfig[]): Observable> { - return this.suggestionsDataService.findById(suggestionId, true, true, ...linksToFollow); - } - /** * Return the list of Suggestion for a given target and source * diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts b/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts index ce5f131c1d..5a5f91a87b 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts @@ -28,10 +28,9 @@ export class SuggestionTargetDataService extends IdentifiableDataService; - private searchBy: SearchData; + private searchData: SearchData; protected searchFindBySourceMethod = 'findBySource'; protected searchFindByTargetMethod = 'findByTarget'; - protected searchFindByTargetAndSourceMethod = 'findByTargetAndSource'; constructor( protected requestService: RequestService, @@ -44,7 +43,7 @@ export class SuggestionTargetDataService extends IdentifiableDataService) { 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); + this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive); } /** * Return the list of Suggestion Target for a given source @@ -65,22 +64,7 @@ export class SuggestionTargetDataService extends IdentifiableDataService>> { 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> The Quality Assurance source. - */ - public getTarget(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable> { - return this.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + return this.searchBy(this.searchFindBySourceMethod, options, true, true, ...linksToFollow); } /** @@ -102,7 +86,7 @@ export class SuggestionTargetDataService extends IdentifiableDataService>> { options.searchParams = [new RequestParam('target', userId)]; - return this.searchBy.searchBy(this.searchFindByTargetMethod, options, true, true, ...linksToFollow); + return this.searchBy(this.searchFindByTargetMethod, options, true, true, ...linksToFollow); } /** * Return a Suggestion Target for a given id @@ -117,4 +101,41 @@ export class SuggestionTargetDataService extends IdentifiableDataService>} + * Return an observable that emits response from the server + */ + public searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig[]): Observable>> { + 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>>} + * Return an observable that emits object list + */ + findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + } + } diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestions-target-data.service.spec.ts b/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestions-target-data.service.spec.ts new file mode 100644 index 0000000000..ba5900b79f --- /dev/null +++ b/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestions-target-data.service.spec.ts @@ -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; + let responseCacheEntry: RequestEntry; + + const store = {} as Store; + 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; + 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; + const initFindAllService = () => new SuggestionTargetDataService(null, null, null, null, null, null, null, null) as unknown as FindAllData; + 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); + }); + }); +}); From d16bf492687306cff03a053032aa696e928c0d31 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Tue, 9 Jan 2024 21:48:27 +0100 Subject: [PATCH 36/94] add tests --- .../cache/builders/build-decorators.spec.ts | 8 +- .../suggestion-data.service.spec.ts | 173 ++++++++++++++++++ .../suggestions-data.service.ts | 2 +- ...o-selector-modal-wrapper.component.spec.ts | 30 ++- ...filter-input-suggestions.component.spec.ts | 27 +++ 5 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 src/app/core/suggestion-notifications/reciter-suggestions/suggestion-data.service.spec.ts diff --git a/src/app/core/cache/builders/build-decorators.spec.ts b/src/app/core/cache/builders/build-decorators.spec.ts index 150a07f006..90d4b4fef8 100644 --- a/src/app/core/cache/builders/build-decorators.spec.ts +++ b/src/app/core/cache/builders/build-decorators.spec.ts @@ -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, getLinkDefinition, link } from './build-decorators'; class TestHALResource implements HALResource { _links: { @@ -46,5 +46,11 @@ describe('build decorators', () => { expect(result).toBeUndefined(); }); }); + + describe(`set data service`, () => { + it(`should throw error`, () => { + expect(dataService(null)).toThrow(); + }); + }); }); }); diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/suggestion-data.service.spec.ts b/src/app/core/suggestion-notifications/reciter-suggestions/suggestion-data.service.spec.ts new file mode 100644 index 0000000000..93b29eff06 --- /dev/null +++ b/src/app/core/suggestion-notifications/reciter-suggestions/suggestion-data.service.spec.ts @@ -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; + let comparatorSuggestionSource: DefaultChangeAnalyzer; + let comparatorSuggestionTarget: DefaultChangeAnalyzer; + 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; + comparatorSuggestionTarget = {} as DefaultChangeAnalyzer; + comparatorSuggestionSource = {} as DefaultChangeAnalyzer; + 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, true, 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(); + }); + }); +}); diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts b/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts index 407ea6a7e6..58fb165031 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts +++ b/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts @@ -116,7 +116,7 @@ export class SuggestionsDataService { } /** - * Return the list of Suggestion Target + * Return the list of Suggestion Sources * * @param options * Find list options object. diff --git a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.spec.ts b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.spec.ts index e2dbaaa0ff..edefff8592 100644 --- a/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.spec.ts +++ b/src/app/shared/dso-selector/modal-wrappers/dso-selector-modal-wrapper.component.spec.ts @@ -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({ diff --git a/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.spec.ts b/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.spec.ts index 6a0324d2ac..6a67d84c28 100644 --- a/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.spec.ts +++ b/src/app/shared/input-suggestions/filter-suggestions/filter-input-suggestions.component.spec.ts @@ -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(); + }); + }); + }); From 6fda2cb4f78df2883fbdb60e1860b5d1fcae1211 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Wed, 10 Jan 2024 12:57:13 +0100 Subject: [PATCH 37/94] add tests --- .../shared/mocks/reciter-suggestion.mock.ts | 2 +- src/app/shared/mocks/suggestion.mock.ts | 13 +- .../suggestion-list-element.component.spec.ts | 81 ++++++++ .../suggestion.service.spec.ts | 194 ++++++++++++++++++ .../suggestions-page.component.spec.ts | 112 +++++++++- 5 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.spec.ts create mode 100644 src/app/suggestion-notifications/reciter-suggestions/suggestion.service.spec.ts diff --git a/src/app/shared/mocks/reciter-suggestion.mock.ts b/src/app/shared/mocks/reciter-suggestion.mock.ts index aceca72fc8..b2dd0e7b12 100644 --- a/src/app/shared/mocks/reciter-suggestion.mock.ts +++ b/src/app/shared/mocks/reciter-suggestion.mock.ts @@ -6,7 +6,7 @@ import { Suggestion } from '../../core/suggestion-notifications/reciter-suggesti import { SUGGESTION } from '../../core/suggestion-notifications/reciter-suggestions/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', diff --git a/src/app/shared/mocks/suggestion.mock.ts b/src/app/shared/mocks/suggestion.mock.ts index 8b1ab7acd5..d4ada21bf8 100644 --- a/src/app/shared/mocks/suggestion.mock.ts +++ b/src/app/shared/mocks/suggestion.mock.ts @@ -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'), + notMine: observableOf(null), + notMineMultiple: observableOf({success: 1, fails: 0}), + approveAndImportMultiple: observableOf({success: 1, fails: 0}), + approveAndImport: observableOf({id: '1234'}), + isCollectionFixed: false, + translateSuggestionSource: 'testSource', + translateSuggestionType: 'testType', }); } diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.spec.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.spec.ts new file mode 100644 index 0000000000..544b53774e --- /dev/null +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.spec.ts @@ -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/reciter-suggestion.mock'; +import { Item } from '../../../core/shared/item.model'; + + +describe('SuggestionListElementComponent', () => { + let component: SuggestionListElementComponent; + let fixture: ComponentFixture; + 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.notMineClicked, 'emit'); + component.onNotMine('1234'); + expect(component.notMineClicked.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); + }); + }); +}); diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion.service.spec.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion.service.spec.ts new file mode 100644 index 0000000000..6abed7a1ce --- /dev/null +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion.service.spec.ts @@ -0,0 +1,194 @@ +import { SuggestionsService } from './suggestions.service'; +import { AuthService } from '../../core/auth/auth.service'; +import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service'; +import { + SuggestionsDataService +} from '../../core/suggestion-notifications/reciter-suggestions/suggestions-data.service'; +import { + SuggestionSourceDataService +} from '../../core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service'; +import { + SuggestionTargetDataService +} from '../../core/suggestion-notifications/reciter-suggestions/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/reciter-suggestion.mock'; +import { ResourceType } from '../../core/shared/resource-type'; + + +import { + SuggestionTarget +} from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; + +describe('SuggestionsService test', () => { + let scheduler: TestScheduler; + let service: SuggestionsService; + let authService: AuthService; + let researcherProfileService: ResearcherProfileDataService; + let suggestionsDataService: SuggestionsDataService; + let suggestionSourceDataService: SuggestionSourceDataService; + 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( + authService, + researcherProfileService, + suggestionsDataService, + suggestionSourceDataService, + suggestionTargetDataService, + translateService + ); + } + + beforeEach(() => { + scheduler = getTestScheduler(); + + + suggestionSourceDataService = jasmine.createSpyObj('suggestionSourcesDataService', { + getSources: observableOf(null), + }); + + 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'); + }); + + 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.notMine('1234'); + expect(service.deleteReviewedSuggestion).toHaveBeenCalledWith('1234'); + }); + + it('should delete suggestions', () => { + spyOn(service, 'notMine'); + service.notMineMultiple([mockSuggestionPublicationOne]); + expect(service.notMine).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('reciter.suggestion.source.' + suggestionTarget.source); + expect(result.type).toEqual('reciter.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('reciter.suggestion.type.source'); + }); + + it('should translate suggestion source', () => { + expect(service.translateSuggestionSource('source')).toEqual('reciter.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(); + }); + }); +}); diff --git a/src/app/suggestions-page/suggestions-page.component.spec.ts b/src/app/suggestions-page/suggestions-page.component.spec.ts index 61e66a99cf..0b0e235082 100644 --- a/src/app/suggestions-page/suggestions-page.component.spec.ts +++ b/src/app/suggestions-page/suggestions-page.component.spec.ts @@ -7,7 +7,10 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { of as observableOf } from 'rxjs'; import { SuggestionsPageComponent } from './suggestions-page.component'; -import { SuggestionListElementComponent } from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; +import { + SuggestionApproveAndImport, + SuggestionListElementComponent +} from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; import { SuggestionsService } from '../suggestion-notifications/reciter-suggestions/suggestions.service'; import { getMockSuggestionNotificationsStateService, getMockSuggestionsService } from '../shared/mocks/suggestion.mock'; import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model'; @@ -104,4 +107,111 @@ describe('SuggestionPageComponent', () => { expect(component.researcherName).toBe(mockSuggestionTargetsObjectOne.display); expect(component.updatePage).toHaveBeenCalled(); }); + + it('should update page on pagination change', () => { + spyOn(component, 'updatePage').and.stub(); + + scheduler.schedule(() => fixture.detectChanges()); + scheduler.flush(); + component.onPaginationChange(); + expect(component.updatePage).toHaveBeenCalled(); + }); + + it('should update suggestion on page update', (done) => { + spyOn(component.processing$, 'next'); + spyOn(component.suggestionsRD$, 'next'); + + scheduler.schedule(() => fixture.detectChanges()); + scheduler.flush(); + paginationService.getFindListOptions().subscribe(() => { + expect(component.processing$.next).toHaveBeenCalled(); + expect(mockSuggestionsService.getSuggestions).toHaveBeenCalled(); + expect(component.suggestionsRD$.next).toHaveBeenCalled(); + expect(mockSuggestionsService.clearSuggestionRequests).toHaveBeenCalled(); + done(); + }); + component.updatePage(); + }); + + it('should flag suggestion for deletion', () => { + spyOn(component, 'updatePage').and.stub(); + + scheduler.schedule(() => fixture.detectChanges()); + scheduler.flush(); + component.notMine('1'); + expect(mockSuggestionsService.notMine).toHaveBeenCalledWith('1'); + expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); + expect(component.updatePage).toHaveBeenCalled(); + }); + + it('should flag all suggestion for deletion', () => { + spyOn(component, 'updatePage').and.stub(); + + scheduler.schedule(() => fixture.detectChanges()); + scheduler.flush(); + component.notMineAllSelected(); + expect(mockSuggestionsService.notMineMultiple).toHaveBeenCalled(); + expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); + expect(component.updatePage).toHaveBeenCalled(); + }); + + it('should approve and import', () => { + spyOn(component, 'updatePage').and.stub(); + + scheduler.schedule(() => fixture.detectChanges()); + scheduler.flush(); + component.approveAndImport({collectionId: '1234'} as unknown as SuggestionApproveAndImport); + expect(mockSuggestionsService.approveAndImport).toHaveBeenCalled(); + expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); + expect(component.updatePage).toHaveBeenCalled(); + }); + + it('should approve and import multiple suggestions', () => { + spyOn(component, 'updatePage').and.stub(); + + scheduler.schedule(() => fixture.detectChanges()); + scheduler.flush(); + component.approveAndImportAllSelected({collectionId: '1234'} as unknown as SuggestionApproveAndImport); + expect(mockSuggestionsService.approveAndImportMultiple).toHaveBeenCalled(); + expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); + expect(component.updatePage).toHaveBeenCalled(); + }); + + it('should select and deselect suggestion', () => { + component.selectedSuggestions = {}; + component.onSelected(mockSuggestionPublicationOne, true); + expect(component.selectedSuggestions[mockSuggestionPublicationOne.id]).toBe(mockSuggestionPublicationOne); + component.onSelected(mockSuggestionPublicationOne, false); + expect(component.selectedSuggestions[mockSuggestionPublicationOne.id]).toBeUndefined(); + }); + + it('should toggle all suggestions', () => { + component.selectedSuggestions = {}; + component.onToggleSelectAll([mockSuggestionPublicationOne, mockSuggestionPublicationTwo]); + expect(component.selectedSuggestions[mockSuggestionPublicationOne.id]).toEqual(mockSuggestionPublicationOne); + expect(component.selectedSuggestions[mockSuggestionPublicationTwo.id]).toEqual(mockSuggestionPublicationTwo); + component.onToggleSelectAll([mockSuggestionPublicationOne, mockSuggestionPublicationTwo]); + expect(component.selectedSuggestions).toEqual({}); + }); + + it('should return all selected suggestions count', () => { + component.selectedSuggestions = {}; + component.onToggleSelectAll([mockSuggestionPublicationOne, mockSuggestionPublicationTwo]); + expect(component.getSelectedSuggestionsCount()).toEqual(2); + }); + + it('should check if all collection is fixed', () => { + component.isCollectionFixed([mockSuggestionPublicationOne, mockSuggestionPublicationTwo]); + expect(mockSuggestionsService.isCollectionFixed).toHaveBeenCalled(); + }); + + it('should translate suggestion source', () => { + component.translateSuggestionSource(); + expect(mockSuggestionsService.translateSuggestionSource).toHaveBeenCalled(); + }); + + it('should translate suggestion type', () => { + component.translateSuggestionType(); + expect(mockSuggestionsService.translateSuggestionType).toHaveBeenCalled(); + }); }); From 4dedda3f9f990d0f353d886d16afd83630239201 Mon Sep 17 00:00:00 2001 From: frabacche Date: Tue, 23 Jan 2024 14:30:07 +0100 Subject: [PATCH 38/94] CST-5249_suggestion refactor label name openaire --- src/assets/i18n/en.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 29fa6a0180..0e5ea85074 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3612,13 +3612,13 @@ "reciter.suggestion.suggestionFor": "Suggestion for", - "reciter.suggestion.source.oaire": "OpenAIRE Graph", + "reciter.suggestion.source.openaire": "OpenAIRE Graph", "reciter.suggestion.from.source": "from the ", "reciter.suggestion.totalScore": "Total Score", - "reciter.suggestion.type.oaire": "OpenAIRE", + "reciter.suggestion.type.openaire": "OpenAIRE", "register-email.title": "New user registration", From 2bb421c7cf06865a49773e8142cc6dd7f92ca42f Mon Sep 17 00:00:00 2001 From: frabacche Date: Tue, 23 Jan 2024 17:44:49 +0100 Subject: [PATCH 39/94] CST-5249_suggestion community first code review --- .../admin-notifications-routing-paths.ts | 2 +- .../admin-notifications-routing.module.ts | 4 +-- src/app/menu.resolver.ts | 4 +-- .../suggestion-actions.component.html | 7 ++-- .../suggestion-list-element.component.html | 4 ++- .../suggestion-targets.component.html | 4 +-- .../suggestion-targets.component.ts | 1 - .../suggestions-page.component.html | 6 ++-- src/assets/i18n/en.json5 | 4 --- src/themes/custom/eager-theme.module.ts | 33 ++++++++++--------- 10 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts b/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts index 870458fa9f..7ae141335f 100644 --- a/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts +++ b/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts @@ -2,7 +2,7 @@ import { URLCombiner } from '../../core/url-combiner/url-combiner'; import { getNotificationsModuleRoute } from '../admin-routing-paths'; export const QUALITY_ASSURANCE_EDIT_PATH = 'quality-assurance'; -export const NOTIFICATIONS_RECITER_SUGGESTION_PATH = 'suggestion-targets'; +export const PUBLICATION_CLAIMS_PATH = 'publication-claims'; export function getQualityAssuranceRoute(id: string) { return new URLCombiner(getNotificationsModuleRoute(), QUALITY_ASSURANCE_EDIT_PATH, id).toString(); diff --git a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts index 9692b65d92..782c592e5d 100644 --- a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts +++ b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts @@ -4,7 +4,7 @@ import { RouterModule } from '@angular/router'; import { AuthenticatedGuard } from '../../core/auth/authenticated.guard'; import { I18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service'; -import { NOTIFICATIONS_RECITER_SUGGESTION_PATH } from './admin-notifications-routing-paths'; +import { PUBLICATION_CLAIMS_PATH } from './admin-notifications-routing-paths'; import { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component'; import { AdminNotificationsSuggestionTargetsPageResolver } from './admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service'; import { QUALITY_ASSURANCE_EDIT_PATH } from './admin-notifications-routing-paths'; @@ -25,7 +25,7 @@ import { RouterModule.forChild([ { canActivate: [ AuthenticatedGuard ], - path: `${NOTIFICATIONS_RECITER_SUGGESTION_PATH}`, + path: `${PUBLICATION_CLAIMS_PATH}`, component: AdminNotificationsSuggestionTargetsPageComponent, pathMatch: 'full', resolve: { diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index 506f30d6a5..b5d8608d06 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -47,7 +47,7 @@ import { import { ExportBatchSelectorComponent } from './shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component'; -import { NOTIFICATIONS_RECITER_SUGGESTION_PATH } from './admin/admin-notifications/admin-notifications-routing-paths'; +import { PUBLICATION_CLAIMS_PATH } from './admin/admin-notifications/admin-notifications-routing-paths'; /** * Creates all of the app's menus @@ -568,7 +568,7 @@ export class MenuResolver implements Resolve { model: { type: MenuItemType.LINK, text: 'menu.section.notifications_reciter', - link: '/admin/notifications/' + NOTIFICATIONS_RECITER_SUGGESTION_PATH + link: '/admin/notifications/' + PUBLICATION_CLAIMS_PATH } as LinkMenuItemModel, }, /* Admin Search */ diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.html b/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.html index 7ec3e61395..4d9fdb8b8e 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.html +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.html @@ -21,8 +21,9 @@
-
diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html b/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html index f37d595c45..5054f8806a 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.html @@ -4,7 +4,8 @@
+ [checked]="isSelected" (change)="changeSelected($event)" + [attr.aria-label]="object.display"/>
@@ -19,6 +20,7 @@ diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.html b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.html index 280f574ec4..04e967c3d5 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.html +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.html @@ -1,7 +1,7 @@
- +

{{'reciter.suggestion.title'| translate}}

- {{targetElement.display}} + {{targetElement.display}}
diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts index e5524765da..cfa168e2b8 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts +++ b/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts @@ -136,7 +136,6 @@ export class SuggestionTargetsComponent implements OnInit { distinctUntilChanged(), take(1) ).subscribe((options: PaginationComponentOptions) => { - console.log('HELLO suggestion called!', options); this.suggestionTargetsStateService.dispatchRetrieveReciterSuggestionTargets( this.source, options.pageSize, diff --git a/src/app/suggestions-page/suggestions-page.component.html b/src/app/suggestions-page/suggestions-page.component.html index fb5f08b6a4..59b6f58c5f 100644 --- a/src/app/suggestions-page/suggestions-page.component.html +++ b/src/app/suggestions-page/suggestions-page.component.html @@ -4,12 +4,12 @@
-

+

{{ translateSuggestionType() | translate }} {{'reciter.suggestion.suggestionFor' | translate}} - {{researcherName}} + {{researcherName}} {{'reciter.suggestion.from.source' | translate}} {{ translateSuggestionSource() | translate }} -

+
diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index dea20aa1a5..0e5ea85074 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1774,10 +1774,6 @@ "form.repeatable.sort.tip": "Drop the item in the new position", - "form.number-picker.decrement": "Decrement {{field}}", - - "form.number-picker.increment": "Increment {{field}}", - "grant-deny-request-copy.deny": "Don't send copy", "grant-deny-request-copy.email.back": "Back", diff --git a/src/themes/custom/eager-theme.module.ts b/src/themes/custom/eager-theme.module.ts index 31047e239a..8889c0c3df 100644 --- a/src/themes/custom/eager-theme.module.ts +++ b/src/themes/custom/eager-theme.module.ts @@ -102,21 +102,24 @@ const DECLARATIONS = [ ]; @NgModule({ - imports: [ - CommonModule, - SharedModule, - RootModule, - NavbarModule, - SharedBrowseByModule, - ResultsBackButtonModule, - ItemPageModule, - ItemSharedModule, - DsoPageModule, - ], - declarations: DECLARATIONS, - providers: [ - ...ENTRY_COMPONENTS.map((component) => ({provide: component})) - ], + imports: [ + CommonModule, + SharedModule, + RootModule, + NavbarModule, + SharedBrowseByModule, + ResultsBackButtonModule, + ItemPageModule, + ItemSharedModule, + DsoPageModule, + ], + declarations: DECLARATIONS, + providers: [ + ...ENTRY_COMPONENTS.map((component) => ({ provide: component })) + ], + exports: [ + ItemSearchResultListElementComponent + ] }) /** * This module is included in the main bundle that gets downloaded at first page load. So it should From e77898eaacb3489f9bdd077a709c90ca66ca0dec Mon Sep 17 00:00:00 2001 From: Agustina Martinez Date: Tue, 23 Jan 2024 21:47:29 +0000 Subject: [PATCH 40/94] Update item-status.component.ts Fix initialOperations typo and return ops with register doi op --- .../edit-item-page/item-status/item-status.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index 8e04985c18..d3a5e033d7 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -117,7 +117,7 @@ export class ItemStatusComponent implements OnInit { * The value is supposed to be a href for the button */ const currentUrl = this.getCurrentUrl(item); - const inititalOperations: ItemOperation[] = [ + const initialOperations: ItemOperation[] = [ new ItemOperation('authorizations', `${currentUrl}/authorizations`, FeatureID.CanManagePolicies, true), new ItemOperation('mappedCollections', `${currentUrl}/mapper`, FeatureID.CanManageMappings, true), item.isWithdrawn @@ -130,7 +130,7 @@ export class ItemStatusComponent implements OnInit { new ItemOperation('delete', `${currentUrl}/delete`, FeatureID.CanDelete, true) ]; - this.operations$.next(inititalOperations); + this.operations$.next(initialOperations); /** * When the identifier data stream changes, determine whether the register DOI button should be shown or not. @@ -170,12 +170,12 @@ export class ItemStatusComponent implements OnInit { }), // Switch map pushes the register DOI operation onto a copy of the base array then returns to the pipe switchMap((showDoi: boolean) => { - const ops = [...inititalOperations]; + const ops = [...initialOperations]; if (showDoi) { const op = new ItemOperation('register-doi', `${currentUrl}/register-doi`, FeatureID.CanRegisterDOI, true); ops.splice(ops.length - 1, 0, op); // Add item before last } - return inititalOperations; + return ops; }), concatMap((op: ItemOperation) => { if (hasValue(op.featureID)) { From afa6559b19e410cab444e865b26300a9e338babc Mon Sep 17 00:00:00 2001 From: frabacche Date: Wed, 24 Jan 2024 18:32:21 +0100 Subject: [PATCH 41/94] CST-5249_suggestion refactor deleting 'reciter', docs --- ...ublication-claim-page-resolver.service.ts} | 6 +- ...ons-publication-claim-page.component.html} | 0 ...ons-publication-claim-page.component.scss} | 0 ...-publication-claim-page.component.spec.ts} | 2 +- ...ations-publication-claim-page.component.ts | 10 +++ .../admin-notifications-routing-paths.ts | 2 +- .../admin-notifications-routing.module.ts | 12 ++-- ...tions-suggestion-targets-page.component.ts | 10 --- .../admin-notifications.module.ts | 2 +- src/app/core/core.module.ts | 4 +- .../suggestion-objects.resource-type.ts | 2 +- .../suggestion-source-object.resource-type.ts | 2 +- .../models/suggestion-source.model.ts | 10 +-- .../suggestion-target-object.resource-type.ts | 2 +- .../models/suggestion-target.model.ts | 10 +-- .../models/suggestion.model.ts | 15 ++-- .../source/suggestion-source-data.service.ts | 31 ++++---- .../suggestions-source-data.service.spec.ts | 30 ++++---- .../suggestion-data.service.spec.ts | 24 +++---- .../suggestions-data.service.ts | 32 ++++----- .../target/suggestion-target-data.service.ts | 32 ++++----- .../suggestions-target-data.service.spec.ts | 36 +++++----- src/app/menu.resolver.ts | 4 +- .../notifications/notifications-effects.ts | 2 +- src/app/notifications/notifications.module.ts | 22 +++--- .../notifications/notifications.reducer.ts | 2 +- ...k.ts => publication-claim-targets.mock.ts} | 2 +- ...tion.mock.ts => publication-claim.mock.ts} | 4 +- src/app/shared/mocks/suggestion.mock.ts | 4 +- .../{reciter-suggestions => }/selectors.ts | 56 +++++++-------- .../suggestion-actions.component.html | 4 +- .../suggestion-actions.component.scss | 0 .../suggestion-actions.component.ts | 21 +++--- .../suggestion-evidences.component.html | 0 .../suggestion-evidences.component.scss | 0 .../suggestion-evidences.component.ts | 7 +- .../suggestion-list-element.component.html | 2 +- .../suggestion-list-element.component.scss | 0 .../suggestion-list-element.component.spec.ts | 10 +-- .../suggestion-list-element.component.ts | 20 ++++-- .../suggestion-targets.actions.ts | 4 +- .../suggestion-targets.component.html | 5 +- .../suggestion-targets.component.scss | 0 .../suggestion-targets.component.ts | 22 +++--- .../suggestion-targets.effects.ts | 6 +- .../suggestion-targets.reducer.ts | 2 +- .../suggestion-targets.state.service.ts | 70 +++++++++---------- .../suggestion.service.spec.ts | 32 ++++----- .../suggestions-notification.component.html | 0 .../suggestions-notification.component.scss | 0 .../suggestions-notification.component.ts | 11 +-- .../suggestions-popup.component.html | 0 .../suggestions-popup.component.scss | 0 .../suggestions-popup.component.spec.ts | 6 +- .../suggestions-popup.component.ts | 19 ++--- .../suggestions.service.ts | 47 +++++++------ .../suggestions-page-routing.module.ts | 11 +-- .../suggestions-page.component.html | 4 +- .../suggestions-page.component.spec.ts | 18 ++--- .../suggestions-page.component.ts | 18 ++--- .../suggestions-page.module.ts | 4 +- .../suggestions-page.resolver.ts | 4 +- src/assets/i18n/en.json5 | 68 +++++++++--------- 63 files changed, 409 insertions(+), 376 deletions(-) rename src/app/admin/admin-notifications/{admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service.ts => admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts} (78%) rename src/app/admin/admin-notifications/{admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.html => admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html} (100%) rename src/app/admin/admin-notifications/{admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.scss => admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.scss} (100%) rename src/app/admin/admin-notifications/{admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.spec.ts => admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts} (95%) create mode 100644 src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts delete mode 100644 src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.ts rename src/app/core/suggestion-notifications/{reciter-suggestions => }/models/suggestion-objects.resource-type.ts (76%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/models/suggestion-source-object.resource-type.ts (77%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/models/suggestion-source.model.ts (71%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/models/suggestion-target-object.resource-type.ts (77%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/models/suggestion-target.model.ts (75%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/models/suggestion.model.ts (74%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/source/suggestion-source-data.service.ts (80%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/source/suggestions-source-data.service.spec.ts (76%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/suggestion-data.service.spec.ts (87%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/suggestions-data.service.ts (87%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/target/suggestion-target-data.service.ts (83%) rename src/app/core/suggestion-notifications/{reciter-suggestions => }/target/suggestions-target-data.service.spec.ts (77%) rename src/app/shared/mocks/{reciter-suggestion-targets.mock.ts => publication-claim-targets.mock.ts} (96%) rename src/app/shared/mocks/{reciter-suggestion.mock.ts => publication-claim.mock.ts} (98%) rename src/app/suggestion-notifications/{reciter-suggestions => }/selectors.ts (50%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-actions/suggestion-actions.component.html (89%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-actions/suggestion-actions.component.scss (100%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-actions/suggestion-actions.component.ts (73%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.html (100%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.scss (100%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.ts (58%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-list-element/suggestion-list-element.component.html (95%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-list-element/suggestion-list-element.component.scss (100%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-list-element/suggestion-list-element.component.spec.ts (88%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-list-element/suggestion-list-element.component.ts (77%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-targets/suggestion-targets.actions.ts (96%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-targets/suggestion-targets.component.html (87%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-targets/suggestion-targets.component.scss (100%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-targets/suggestion-targets.component.ts (80%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-targets/suggestion-targets.effects.ts (91%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-targets/suggestion-targets.reducer.ts (95%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion-targets/suggestion-targets.state.service.ts (54%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestion.service.spec.ts (83%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestions-notification/suggestions-notification.component.html (100%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestions-notification/suggestions-notification.component.scss (100%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestions-notification/suggestions-notification.component.ts (75%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestions-popup/suggestions-popup.component.html (100%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestions-popup/suggestions-popup.component.scss (100%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestions-popup/suggestions-popup.component.spec.ts (92%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestions-popup/suggestions-popup.component.ts (75%) rename src/app/suggestion-notifications/{reciter-suggestions => }/suggestions.service.ts (84%) diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts similarity index 78% rename from src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service.ts rename to src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts index df1f4b81e6..add9a504dd 100644 --- a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service.ts +++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page-resolver.service.ts @@ -4,7 +4,7 @@ import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/r /** * Interface for the route parameters. */ -export interface AdminNotificationsSuggestionTargetsPageParams { +export interface AdminNotificationsPublicationClaimPageParams { pageId?: string; pageSize?: number; currentPage?: number; @@ -14,7 +14,7 @@ export interface AdminNotificationsSuggestionTargetsPageParams { * This class represents a resolver that retrieve the route data before the route is activated. */ @Injectable() -export class AdminNotificationsSuggestionTargetsPageResolver implements Resolve { +export class AdminNotificationsPublicationClaimPageResolver implements Resolve { /** * Method for resolving the parameters in the current route. @@ -22,7 +22,7 @@ export class AdminNotificationsSuggestionTargetsPageResolver implements Resolve< * @param {RouterStateSnapshot} state The current RouterStateSnapshot * @returns AdminNotificationsSuggestionTargetsPageParams Emits the route parameters */ - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): AdminNotificationsSuggestionTargetsPageParams { + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): AdminNotificationsPublicationClaimPageParams { return { pageId: route.queryParams.pageId, pageSize: parseInt(route.queryParams.pageSize, 10), diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.html b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html similarity index 100% rename from src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.html rename to src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.scss b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.scss similarity index 100% rename from src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.scss rename to src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.scss diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.spec.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts similarity index 95% rename from src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.spec.ts rename to src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts index f9e407f402..c0209da898 100644 --- a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.spec.ts +++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts @@ -1,6 +1,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-suggestion-targets-page.component'; +import { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-publication-claim-page.component'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts new file mode 100644 index 0000000000..10023ead93 --- /dev/null +++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts @@ -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 AdminNotificationsSuggestionTargetsPageComponent { + +} diff --git a/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts b/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts index 7ae141335f..f92a96d242 100644 --- a/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts +++ b/src/app/admin/admin-notifications/admin-notifications-routing-paths.ts @@ -2,7 +2,7 @@ import { URLCombiner } from '../../core/url-combiner/url-combiner'; import { getNotificationsModuleRoute } from '../admin-routing-paths'; export const QUALITY_ASSURANCE_EDIT_PATH = 'quality-assurance'; -export const PUBLICATION_CLAIMS_PATH = 'publication-claims'; +export const PUBLICATION_CLAIMS_PATH = 'publication-claim'; export function getQualityAssuranceRoute(id: string) { return new URLCombiner(getNotificationsModuleRoute(), QUALITY_ASSURANCE_EDIT_PATH, id).toString(); diff --git a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts index 782c592e5d..648bdc0a1f 100644 --- a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts +++ b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts @@ -5,8 +5,8 @@ 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 { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component'; -import { AdminNotificationsSuggestionTargetsPageResolver } from './admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page-resolver.service'; +import { AdminNotificationsSuggestionTargetsPageComponent } 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'; @@ -30,11 +30,11 @@ import { pathMatch: 'full', resolve: { breadcrumb: I18nBreadcrumbResolver, - reciterSuggestionTargetParams: AdminNotificationsSuggestionTargetsPageResolver + suggestionTargetParams: AdminNotificationsPublicationClaimPageResolver }, data: { - title: 'admin.notifications.recitersuggestion.page.title', - breadcrumbKey: 'admin.notifications.recitersuggestion', + title: 'admin.notifications.publicationclaim.page.title', + breadcrumbKey: 'admin.notifications.publicationclaim', showBreadcrumbsFluid: false } }, @@ -89,7 +89,7 @@ import { providers: [ I18nBreadcrumbResolver, I18nBreadcrumbsService, - AdminNotificationsSuggestionTargetsPageResolver, + AdminNotificationsPublicationClaimPageResolver, SourceDataResolver, AdminQualityAssuranceSourcePageResolver, AdminQualityAssuranceTopicsPageResolver, diff --git a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.ts b/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.ts deleted file mode 100644 index a9a77f5089..0000000000 --- a/src/app/admin/admin-notifications/admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from '@angular/core'; - -@Component({ - selector: 'ds-admin-notifications-reciter-page', - templateUrl: './admin-notifications-suggestion-targets-page.component.html', - styleUrls: ['./admin-notifications-suggestion-targets-page.component.scss'] -}) -export class AdminNotificationsSuggestionTargetsPageComponent { - -} diff --git a/src/app/admin/admin-notifications/admin-notifications.module.ts b/src/app/admin/admin-notifications/admin-notifications.module.ts index 93e184c942..3566bdd91a 100644 --- a/src/app/admin/admin-notifications/admin-notifications.module.ts +++ b/src/app/admin/admin-notifications/admin-notifications.module.ts @@ -3,7 +3,7 @@ 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 { AdminNotificationsSuggestionTargetsPageComponent } from './admin-notifications-suggestion-targets-page/admin-notifications-suggestion-targets-page.component'; +import { AdminNotificationsSuggestionTargetsPageComponent } 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'; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 987ab5e603..f151f10f66 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -185,8 +185,8 @@ import { FlatBrowseDefinition } from './shared/flat-browse-definition.model'; import { ValueListBrowseDefinition } from './shared/value-list-browse-definition.model'; import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition'; import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model'; -import { SuggestionTarget } from './suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; -import { SuggestionSource } from './suggestion-notifications/reciter-suggestions/models/suggestion-source.model'; +import { SuggestionTarget } from './suggestion-notifications/models/suggestion-target.model'; +import { SuggestionSource } from './suggestion-notifications/models/suggestion-source.model'; /** * When not in production, endpoint responses can be mocked for testing purposes diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type.ts b/src/app/core/suggestion-notifications/models/suggestion-objects.resource-type.ts similarity index 76% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type.ts rename to src/app/core/suggestion-notifications/models/suggestion-objects.resource-type.ts index 8f87027a8c..8f83d86376 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-objects.resource-type.ts +++ b/src/app/core/suggestion-notifications/models/suggestion-objects.resource-type.ts @@ -1,4 +1,4 @@ -import { ResourceType } from '../../../shared/resource-type'; +import { ResourceType } from '../../shared/resource-type'; /** * The resource type for the Suggestion object diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source-object.resource-type.ts b/src/app/core/suggestion-notifications/models/suggestion-source-object.resource-type.ts similarity index 77% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source-object.resource-type.ts rename to src/app/core/suggestion-notifications/models/suggestion-source-object.resource-type.ts index 2e26fe4301..e319ed5109 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source-object.resource-type.ts +++ b/src/app/core/suggestion-notifications/models/suggestion-source-object.resource-type.ts @@ -1,4 +1,4 @@ -import { ResourceType } from '../../../shared/resource-type'; +import { ResourceType } from '../../shared/resource-type'; /** * The resource type for the Suggestion Source object diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts b/src/app/core/suggestion-notifications/models/suggestion-source.model.ts similarity index 71% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts rename to src/app/core/suggestion-notifications/models/suggestion-source.model.ts index 007520800d..12d9d7e9d8 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-source.model.ts +++ b/src/app/core/suggestion-notifications/models/suggestion-source.model.ts @@ -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 diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target-object.resource-type.ts b/src/app/core/suggestion-notifications/models/suggestion-target-object.resource-type.ts similarity index 77% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target-object.resource-type.ts rename to src/app/core/suggestion-notifications/models/suggestion-target-object.resource-type.ts index 71dd41912a..81b1b5c261 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target-object.resource-type.ts +++ b/src/app/core/suggestion-notifications/models/suggestion-target-object.resource-type.ts @@ -1,4 +1,4 @@ -import { ResourceType } from '../../../shared/resource-type'; +import { ResourceType } from '../../shared/resource-type'; /** * The resource type for the Suggestion Target object diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts b/src/app/core/suggestion-notifications/models/suggestion-target.model.ts similarity index 75% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts rename to src/app/core/suggestion-notifications/models/suggestion-target.model.ts index 2afe170e77..99d9a8628a 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model.ts +++ b/src/app/core/suggestion-notifications/models/suggestion-target.model.ts @@ -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 diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion.model.ts b/src/app/core/suggestion-notifications/models/suggestion.model.ts similarity index 74% rename from src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion.model.ts rename to src/app/core/suggestion-notifications/models/suggestion.model.ts index c36d36794b..ad58b1cfe5 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/models/suggestion.model.ts +++ b/src/app/core/suggestion-notifications/models/suggestion.model.ts @@ -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; diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts b/src/app/core/suggestion-notifications/source/suggestion-source-data.service.ts similarity index 80% rename from src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts rename to src/app/core/suggestion-notifications/source/suggestion-source-data.service.ts index 15b067f2ed..f00a84c95b 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service.ts +++ b/src/app/core/suggestion-notifications/source/suggestion-source-data.service.ts @@ -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 { diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestions-source-data.service.spec.ts b/src/app/core/suggestion-notifications/source/suggestions-source-data.service.spec.ts similarity index 76% rename from src/app/core/suggestion-notifications/reciter-suggestions/source/suggestions-source-data.service.spec.ts rename to src/app/core/suggestion-notifications/source/suggestions-source-data.service.spec.ts index 5f8d51a641..28f34b863d 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/source/suggestions-source-data.service.spec.ts +++ b/src/app/core/suggestion-notifications/source/suggestions-source-data.service.spec.ts @@ -1,25 +1,25 @@ 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 { 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 { RestResponse } from '../../cache/response.models'; import { of as observableOf } from 'rxjs'; import { Store } from '@ngrx/store'; -import { CoreState } from '../../../core-state.model'; +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 { 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'; +} 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'; diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/suggestion-data.service.spec.ts b/src/app/core/suggestion-notifications/suggestion-data.service.spec.ts similarity index 87% rename from src/app/core/suggestion-notifications/reciter-suggestions/suggestion-data.service.spec.ts rename to src/app/core/suggestion-notifications/suggestion-data.service.spec.ts index 93b29eff06..a854514c89 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/suggestion-data.service.spec.ts +++ b/src/app/core/suggestion-notifications/suggestion-data.service.spec.ts @@ -1,25 +1,25 @@ 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 { 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 { 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 { 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 { 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'; +import { RequestParam } from '../cache/models/request-param.model'; describe('SuggestionDataService test', () => { let scheduler: TestScheduler; diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts b/src/app/core/suggestion-notifications/suggestions-data.service.ts similarity index 87% rename from src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts rename to src/app/core/suggestion-notifications/suggestions-data.service.ts index 58fb165031..72476f6a15 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/suggestions-data.service.ts +++ b/src/app/core/suggestion-notifications/suggestions-data.service.ts @@ -5,26 +5,26 @@ 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 { 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 { 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'; diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts b/src/app/core/suggestion-notifications/target/suggestion-target-data.service.ts similarity index 83% rename from src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts rename to src/app/core/suggestion-notifications/target/suggestion-target-data.service.ts index 5a5f91a87b..a2f1507b10 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service.ts +++ b/src/app/core/suggestion-notifications/target/suggestion-target-data.service.ts @@ -1,25 +1,25 @@ import { Injectable } from '@angular/core'; -import { dataService } from '../../../data/base/data-service.decorator'; +import { dataService } from '../../data/base/data-service.decorator'; -import { IdentifiableDataService } from '../../../data/base/identifiable-data.service'; +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 { 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 { RequestParam } from '../../../cache/models/request-param.model'; -import { SearchData, SearchDataImpl } from '../../../data/base/search-data'; -import { DefaultChangeAnalyzer } from '../../../data/default-change-analyzer.service'; +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() diff --git a/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestions-target-data.service.spec.ts b/src/app/core/suggestion-notifications/target/suggestions-target-data.service.spec.ts similarity index 77% rename from src/app/core/suggestion-notifications/reciter-suggestions/target/suggestions-target-data.service.spec.ts rename to src/app/core/suggestion-notifications/target/suggestions-target-data.service.spec.ts index ba5900b79f..9207603a5a 100644 --- a/src/app/core/suggestion-notifications/reciter-suggestions/target/suggestions-target-data.service.spec.ts +++ b/src/app/core/suggestion-notifications/target/suggestions-target-data.service.spec.ts @@ -1,30 +1,30 @@ 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 { 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 { RestResponse } from '../../cache/response.models'; import { of as observableOf } from 'rxjs'; import { Store } from '@ngrx/store'; -import { CoreState } from '../../../core-state.model'; +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 { 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 { 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 { 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'; +} 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; diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index b5d8608d06..fc6eb00195 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -561,13 +561,13 @@ export class MenuResolver implements Resolve { } as LinkMenuItemModel, }, { - id: 'notifications_reciter', + id: 'notifications_publication-claim', parentID: 'notifications', active: false, visible: authorized, model: { type: MenuItemType.LINK, - text: 'menu.section.notifications_reciter', + text: 'menu.section.notifications_publication-claim', link: '/admin/notifications/' + PUBLICATION_CLAIMS_PATH } as LinkMenuItemModel, }, diff --git a/src/app/notifications/notifications-effects.ts b/src/app/notifications/notifications-effects.ts index 9f19058aab..a73fd04fc0 100644 --- a/src/app/notifications/notifications-effects.ts +++ b/src/app/notifications/notifications-effects.ts @@ -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 '../suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects'; +import { SuggestionTargetsEffects } from '../suggestion-notifications/suggestion-targets/suggestion-targets.effects'; export const notificationsEffects = [ QualityAssuranceTopicsEffects, diff --git a/src/app/notifications/notifications.module.ts b/src/app/notifications/notifications.module.ts index 98832b86b7..cd8cb99811 100644 --- a/src/app/notifications/notifications.module.ts +++ b/src/app/notifications/notifications.module.ts @@ -26,29 +26,29 @@ import { QualityAssuranceSourceService } from './qa/source/quality-assurance-sou import { QualityAssuranceSourceDataService } from '../core/notifications/qa/source/quality-assurance-source-data.service'; -import { SuggestionTargetsComponent } from '../suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component'; -import { SuggestionActionsComponent } from '../suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component'; +import { SuggestionTargetsComponent } from '../suggestion-notifications/suggestion-targets/suggestion-targets.component'; +import { SuggestionActionsComponent } from '../suggestion-notifications/suggestion-actions/suggestion-actions.component'; import { SuggestionListElementComponent -} from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; +} from '../suggestion-notifications/suggestion-list-element/suggestion-list-element.component'; import { SuggestionEvidencesComponent -} from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component'; -import { SuggestionsPopupComponent } from '../suggestion-notifications/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 '../suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component'; -import { SuggestionsService } from '../suggestion-notifications/reciter-suggestions/suggestions.service'; -import { SuggestionsDataService } from '../core/suggestion-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/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service'; +} from '../core/suggestion-notifications/source/suggestion-source-data.service'; import { SuggestionTargetDataService -} from '../core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service'; +} from '../core/suggestion-notifications/target/suggestion-target-data.service'; import { SuggestionTargetsStateService -} from '../suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service'; +} from '../suggestion-notifications/suggestion-targets/suggestion-targets.state.service'; const MODULES = [ diff --git a/src/app/notifications/notifications.reducer.ts b/src/app/notifications/notifications.reducer.ts index ba7fcc6c58..cced6755fa 100644 --- a/src/app/notifications/notifications.reducer.ts +++ b/src/app/notifications/notifications.reducer.ts @@ -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 '../suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.reducer'; +import { SuggestionTargetsReducer, SuggestionTargetState } from '../suggestion-notifications/suggestion-targets/suggestion-targets.reducer'; /** * The OpenAIRE State diff --git a/src/app/shared/mocks/reciter-suggestion-targets.mock.ts b/src/app/shared/mocks/publication-claim-targets.mock.ts similarity index 96% rename from src/app/shared/mocks/reciter-suggestion-targets.mock.ts rename to src/app/shared/mocks/publication-claim-targets.mock.ts index 489bb7733d..1d7688c1e3 100644 --- a/src/app/shared/mocks/reciter-suggestion-targets.mock.ts +++ b/src/app/shared/mocks/publication-claim-targets.mock.ts @@ -1,5 +1,5 @@ import { ResourceType } from '../../core/shared/resource-type'; -import { SuggestionTarget } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; // REST Mock --------------------------------------------------------------------- // ------------------------------------------------------------------------------- diff --git a/src/app/shared/mocks/reciter-suggestion.mock.ts b/src/app/shared/mocks/publication-claim.mock.ts similarity index 98% rename from src/app/shared/mocks/reciter-suggestion.mock.ts rename to src/app/shared/mocks/publication-claim.mock.ts index b2dd0e7b12..143b54d8dd 100644 --- a/src/app/shared/mocks/reciter-suggestion.mock.ts +++ b/src/app/shared/mocks/publication-claim.mock.ts @@ -2,8 +2,8 @@ // REST Mock --------------------------------------------------------------------- // ------------------------------------------------------------------------------- -import { Suggestion } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; -import { SUGGESTION } from '../../core/suggestion-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: '24694773', diff --git a/src/app/shared/mocks/suggestion.mock.ts b/src/app/shared/mocks/suggestion.mock.ts index d4ada21bf8..ed7f9045d5 100644 --- a/src/app/shared/mocks/suggestion.mock.ts +++ b/src/app/shared/mocks/suggestion.mock.ts @@ -1349,8 +1349,8 @@ export function getMockSuggestionsService(): any { deleteReviewedSuggestion: jasmine.createSpy('deleteReviewedSuggestion'), retrieveCurrentUserSuggestions: jasmine.createSpy('retrieveCurrentUserSuggestions'), getTargetUuid: jasmine.createSpy('getTargetUuid'), - notMine: observableOf(null), - notMineMultiple: observableOf({success: 1, fails: 0}), + ignoreSuggestion: observableOf(null), + ignoreSuggestionMultiple: observableOf({success: 1, fails: 0}), approveAndImportMultiple: observableOf({success: 1, fails: 0}), approveAndImport: observableOf({id: '1234'}), isCollectionFixed: false, diff --git a/src/app/suggestion-notifications/reciter-suggestions/selectors.ts b/src/app/suggestion-notifications/selectors.ts similarity index 50% rename from src/app/suggestion-notifications/reciter-suggestions/selectors.ts rename to src/app/suggestion-notifications/selectors.ts index f031b71711..97ed3a8360 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/selectors.ts +++ b/src/app/suggestion-notifications/selectors.ts @@ -1,44 +1,44 @@ import {createFeatureSelector, createSelector, MemoizedSelector} from '@ngrx/store'; -import { suggestionNotificationsSelector, SuggestionNotificationsState } from '../../notifications/notifications.reducer'; -import { SuggestionTarget } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { suggestionNotificationsSelector, SuggestionNotificationsState } from '../notifications/notifications.reducer'; +import { SuggestionTarget } from '../core/suggestion-notifications/models/suggestion-target.model'; import { SuggestionTargetState } from './suggestion-targets/suggestion-targets.reducer'; -import {subStateSelector} from '../../submission/selectors'; +import {subStateSelector} from '../submission/selectors'; /** * Returns the Reciter Suggestion Target state. - * @function _getReciterSuggestionTargetState + * @function _getSuggestionTargetState * @param {AppState} state Top level state. * @return {SuggestionNotificationsState} */ -const _getReciterSuggestionTargetState = createFeatureSelector('suggestionNotifications'); +const _getSuggestionTargetState = createFeatureSelector('suggestionNotifications'); // Reciter Suggestion Targets // ---------------------------------------------------------------------------- /** - * Returns the Reciter Suggestion Targets State. - * @function reciterSuggestionTargetStateSelector + * Returns the Suggestion Targets State. + * @function suggestionTargetStateSelector * @return {SuggestionNotificationsState} */ -export function reciterSuggestionTargetStateSelector(): MemoizedSelector { +export function suggestionTargetStateSelector(): MemoizedSelector { return subStateSelector(suggestionNotificationsSelector, 'suggestionTarget'); } /** - * Returns the Reciter Suggestion Targets list. - * @function reciterSuggestionTargetObjectSelector + * Returns the Suggestion Targets list. + * @function suggestionTargetObjectSelector * @return {SuggestionTarget[]} */ -export function reciterSuggestionTargetObjectSelector(): MemoizedSelector { - return subStateSelector(reciterSuggestionTargetStateSelector(), 'targets'); +export function suggestionTargetObjectSelector(): MemoizedSelector { + return subStateSelector(suggestionTargetStateSelector(), 'targets'); } /** - * Returns true if the Reciter Suggestion Targets are loaded. - * @function isReciterSuggestionTargetLoadedSelector + * Returns true if the Suggestion Targets are loaded. + * @function isSuggestionTargetLoadedSelector * @return {boolean} */ -export const isReciterSuggestionTargetLoadedSelector = createSelector(_getReciterSuggestionTargetState, +export const isSuggestionTargetLoadedSelector = createSelector(_getSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.loaded ); @@ -47,51 +47,51 @@ export const isReciterSuggestionTargetLoadedSelector = createSelector(_getRecite * @function isDeduplicationSetsProcessingSelector * @return {boolean} */ -export const isReciterSuggestionTargetProcessingSelector = createSelector(_getReciterSuggestionTargetState, +export const isReciterSuggestionTargetProcessingSelector = createSelector(_getSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.processing ); /** * Returns the total available pages of Reciter Suggestion Targets. - * @function getreciterSuggestionTargetTotalPagesSelector + * @function getSuggestionTargetTotalPagesSelector * @return {number} */ -export const getReciterSuggestionTargetTotalPagesSelector = createSelector(_getReciterSuggestionTargetState, +export const getSuggestionTargetTotalPagesSelector = createSelector(_getSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.totalPages ); /** - * Returns the current page of Reciter Suggestion Targets. - * @function getreciterSuggestionTargetCurrentPageSelector + * Returns the current page of Suggestion Targets. + * @function getSuggestionTargetCurrentPageSelector * @return {number} */ -export const getReciterSuggestionTargetCurrentPageSelector = createSelector(_getReciterSuggestionTargetState, +export const getSuggestionTargetCurrentPageSelector = createSelector(_getSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.currentPage ); /** - * Returns the total number of Reciter Suggestion Targets. - * @function getreciterSuggestionTargetTotalsSelector + * Returns the total number of Suggestion Targets. + * @function getSuggestionTargetTotalsSelector * @return {number} */ -export const getReciterSuggestionTargetTotalsSelector = createSelector(_getReciterSuggestionTargetState, +export const getSuggestionTargetTotalsSelector = createSelector(_getSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.totalElements ); /** * Returns Suggestion Targets for the current user. - * @function getCurrentUserReciterSuggestionTargetSelector + * @function getCurrentUserSuggestionTargetSelector * @return {SuggestionTarget[]} */ -export const getCurrentUserSuggestionTargetsSelector = createSelector(_getReciterSuggestionTargetState, +export const getCurrentUserSuggestionTargetsSelector = createSelector(_getSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.currentUserTargets ); /** * Returns whether or not the user has consulted their suggestions - * @function getCurrentUserReciterSuggestionTargetSelector + * @function getCurrentUserSuggestionTargetSelector * @return {boolean} */ -export const getCurrentUserSuggestionTargetsVisitedSelector = createSelector(_getReciterSuggestionTargetState, +export const getCurrentUserSuggestionTargetsVisitedSelector = createSelector(_getSuggestionTargetState, (state: SuggestionNotificationsState) => state.suggestionTarget.currentUserTargetsVisited ); diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.html b/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.html similarity index 89% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.html rename to src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.html index 4d9fdb8b8e..24e970a818 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-actions/suggestion-actions.component.html +++ b/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.html @@ -19,8 +19,8 @@
- +
diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.scss b/src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.scss similarity index 100% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.scss rename to src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.scss diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.spec.ts b/src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.spec.ts similarity index 88% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.spec.ts rename to src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.spec.ts index 544b53774e..61d1a19996 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.spec.ts +++ b/src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.spec.ts @@ -5,8 +5,8 @@ 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/reciter-suggestion.mock'; -import { Item } from '../../../core/shared/item.model'; +import { mockSuggestionPublicationOne } from '../../shared/mocks/publication-claim.mock'; +import { Item } from '../../core/shared/item.model'; describe('SuggestionListElementComponent', () => { @@ -66,9 +66,9 @@ describe('SuggestionListElementComponent', () => { }); it('should emit for deletion', () => { - spyOn(component.notMineClicked, 'emit'); - component.onNotMine('1234'); - expect(component.notMineClicked.emit).toHaveBeenCalledWith('1234'); + spyOn(component.ignoreSuggestionClicked, 'emit'); + component.onIgnoreSuggestion('1234'); + expect(component.ignoreSuggestionClicked.emit).toHaveBeenCalledWith('1234'); }); it('should emit for approve and import', () => { diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts b/src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.ts similarity index 77% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts rename to src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.ts index a1ef454037..f569054bb0 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component.ts +++ b/src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.ts @@ -2,16 +2,22 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { fadeIn } from '../../../shared/animations/fade'; -import { Suggestion } from '../../../core/suggestion-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'; +/** + * 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 +39,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 +75,8 @@ export class SuggestionListElementComponent implements OnInit { /** * Delete the suggestion */ - onNotMine(suggestionId: string) { - this.notMineClicked.emit(suggestionId); + onIgnoreSuggestion(suggestionId: string) { + this.ignoreSuggestionClicked.emit(suggestionId); } /** diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.actions.ts similarity index 96% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts rename to src/app/suggestion-notifications/suggestion-targets/suggestion-targets.actions.ts index 4e5b88d65c..058fa36751 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.actions.ts +++ b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.actions.ts @@ -1,7 +1,7 @@ /* eslint-disable max-classes-per-file */ import { Action } from '@ngrx/store'; -import { type } from '../../../shared/ngrx/type'; -import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { type } from '../../shared/ngrx/type'; +import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; /** * For each action type in an action group, make a simple diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.html b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.html similarity index 87% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.html rename to src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.html index 04e967c3d5..3d98082ba6 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.html +++ b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.html @@ -33,8 +33,9 @@
diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.scss b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.scss similarity index 100% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.scss rename to src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.scss diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.ts similarity index 80% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts rename to src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.ts index cfa168e2b8..e469ac1386 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.component.ts +++ b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.ts @@ -4,13 +4,13 @@ import { Router } from '@angular/router'; import { Observable, Subscription } from 'rxjs'; import { distinctUntilChanged, take } from 'rxjs/operators'; -import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; -import { hasValue } from '../../../shared/empty.util'; -import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; +import { hasValue } from '../../shared/empty.util'; +import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; 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 { PaginationService } from '../../../core/pagination/pagination.service'; +import { PaginationService } from '../../core/pagination/pagination.service'; /** * Component to display the Suggestion Target list. @@ -69,8 +69,8 @@ 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(); } /** @@ -78,7 +78,7 @@ export class SuggestionTargetsComponent implements OnInit { */ ngAfterViewInit(): void { this.subs.push( - this.suggestionTargetsStateService.isReciterSuggestionTargetsLoaded().pipe( + this.suggestionTargetsStateService.isSuggestionTargetsLoaded().pipe( take(1) ).subscribe(() => { this.getSuggestionTargets(); @@ -93,7 +93,7 @@ export class SuggestionTargetsComponent implements OnInit { * 'true' if the targets are loading, 'false' otherwise. */ public isTargetsLoading(): Observable { - return this.suggestionTargetsStateService.isReciterSuggestionTargetsLoading(); + return this.suggestionTargetsStateService.isSuggestionTargetsLoading(); } /** @@ -103,7 +103,7 @@ export class SuggestionTargetsComponent implements OnInit { * 'true' if there are operations running on the targets (ex.: a REST call), 'false' otherwise. */ public isTargetsProcessing(): Observable { - return this.suggestionTargetsStateService.isReciterSuggestionTargetsProcessing(); + return this.suggestionTargetsStateService.isSuggestionTargetsProcessing(); } /** @@ -136,7 +136,7 @@ export class SuggestionTargetsComponent implements OnInit { distinctUntilChanged(), take(1) ).subscribe((options: PaginationComponentOptions) => { - this.suggestionTargetsStateService.dispatchRetrieveReciterSuggestionTargets( + this.suggestionTargetsStateService.dispatchRetrieveSuggestionTargets( this.source, options.pageSize, options.currentPage diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts similarity index 91% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts rename to src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts index 850c82972e..63964b7721 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.effects.ts +++ b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts @@ -14,10 +14,10 @@ import { 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'; -import { NotificationsService } from '../../../shared/notifications/notifications.service'; -import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; /** * Provides effect methods for the Suggestion Targets actions. diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.reducer.ts similarity index 95% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts rename to src/app/suggestion-notifications/suggestion-targets/suggestion-targets.reducer.ts index 2d315e6995..09ead019d1 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.reducer.ts +++ b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.reducer.ts @@ -1,5 +1,5 @@ import { SuggestionTargetActionTypes, SuggestionTargetsActions } from './suggestion-targets.actions'; -import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; /** * The interface representing the OpenAIRE suggestion targets state. diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.state.service.ts similarity index 54% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts rename to src/app/suggestion-notifications/suggestion-targets/suggestion-targets.state.service.ts index da347a1e8b..7e0e6ad836 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service.ts +++ b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.state.service.ts @@ -7,20 +7,20 @@ import { map } from 'rxjs/operators'; import { getCurrentUserSuggestionTargetsSelector, getCurrentUserSuggestionTargetsVisitedSelector, - getReciterSuggestionTargetCurrentPageSelector, - getReciterSuggestionTargetTotalsSelector, - isReciterSuggestionTargetLoadedSelector, + getSuggestionTargetCurrentPageSelector, + getSuggestionTargetTotalsSelector, + isSuggestionTargetLoadedSelector, isReciterSuggestionTargetProcessingSelector, - reciterSuggestionTargetObjectSelector + suggestionTargetObjectSelector } from '../selectors'; -import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; import { ClearSuggestionTargetsAction, MarkUserSuggestionsAsVisitedAction, RefreshUserSuggestionsAction, RetrieveTargetsBySourceAction } from './suggestion-targets.actions'; -import { SuggestionNotificationsState } from '../../../notifications/notifications.reducer'; +import { SuggestionNotificationsState } from '../../notifications/notifications.reducer'; /** * The service handling the Suggestion targets State. @@ -35,80 +35,80 @@ export class SuggestionTargetsStateService { constructor(private store: Store) { } /** - * Returns the list of Reciter Suggestion Targets from the state. + * Returns the list of Suggestion Targets from the state. * - * @return Observable - * The list of Reciter Suggestion Targets. + * @return Observable + * The list of Suggestion Targets. */ - public getReciterSuggestionTargets(): Observable { - return this.store.pipe(select(reciterSuggestionTargetObjectSelector())); + public getSuggestionTargets(): Observable { + 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 * 'true' if the targets are loading, 'false' otherwise. */ - public isReciterSuggestionTargetsLoading(): Observable { + public isSuggestionTargetsLoading(): Observable { 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 * 'true' if the targets are loaded, 'false' otherwise. */ - public isReciterSuggestionTargetsLoaded(): Observable { - return this.store.pipe(select(isReciterSuggestionTargetLoadedSelector)); + public isSuggestionTargetsLoaded(): Observable { + 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 * 'true' if there are operations running on the targets (ex.: a REST call), 'false' otherwise. */ - public isReciterSuggestionTargetsProcessing(): Observable { + public isSuggestionTargetsProcessing(): Observable { 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 - * The number of the Reciter Suggestion Targets pages. + * The number of the Suggestion Targets pages. */ - public getReciterSuggestionTargetsTotalPages(): Observable { - return this.store.pipe(select(getReciterSuggestionTargetTotalsSelector)); + public getSuggestionTargetsTotalPages(): Observable { + 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 - * The number of the current Reciter Suggestion Targets page. + * The number of the current Suggestion Targets page. */ - public getReciterSuggestionTargetsCurrentPage(): Observable { - return this.store.pipe(select(getReciterSuggestionTargetCurrentPageSelector)); + public getSuggestionTargetsCurrentPage(): Observable { + 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 - * The number of the Reciter Suggestion Targets. + * The number of the Suggestion Targets. */ - public getReciterSuggestionTargetsTotals(): Observable { - return this.store.pipe(select(getReciterSuggestionTargetTotalsSelector)); + public getSuggestionTargetsTotals(): Observable { + 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 +117,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 - * The Reciter Suggestion Targets object. + * The Suggestion Targets object. */ public getCurrentUserSuggestionTargets(): Observable { return this.store.pipe(select(getCurrentUserSuggestionTargetsSelector)); diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestion.service.spec.ts b/src/app/suggestion-notifications/suggestion.service.spec.ts similarity index 83% rename from src/app/suggestion-notifications/reciter-suggestions/suggestion.service.spec.ts rename to src/app/suggestion-notifications/suggestion.service.spec.ts index 6abed7a1ce..c7d0c7a4bf 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestion.service.spec.ts +++ b/src/app/suggestion-notifications/suggestion.service.spec.ts @@ -1,30 +1,30 @@ import { SuggestionsService } from './suggestions.service'; -import { AuthService } from '../../core/auth/auth.service'; -import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service'; +import { AuthService } from '../core/auth/auth.service'; +import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service'; import { SuggestionsDataService -} from '../../core/suggestion-notifications/reciter-suggestions/suggestions-data.service'; +} from '../core/suggestion-notifications/suggestions-data.service'; import { SuggestionSourceDataService -} from '../../core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service'; +} from '../core/suggestion-notifications/source/suggestion-source-data.service'; import { SuggestionTargetDataService -} from '../../core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service'; +} 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/reciter-suggestion.mock'; -import { ResourceType } from '../../core/shared/resource-type'; +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/reciter-suggestions/models/suggestion-target.model'; +} from '../core/suggestion-notifications/models/suggestion-target.model'; describe('SuggestionsService test', () => { let scheduler: TestScheduler; @@ -151,14 +151,14 @@ describe('SuggestionsService test', () => { it('should delete suggestion', () => { spyOn(service, 'deleteReviewedSuggestion').and.returnValue(createSuccessfulRemoteDataObject$({})); - service.notMine('1234'); + service.ignoreSuggestion('1234'); expect(service.deleteReviewedSuggestion).toHaveBeenCalledWith('1234'); }); it('should delete suggestions', () => { spyOn(service, 'notMine'); - service.notMineMultiple([mockSuggestionPublicationOne]); - expect(service.notMine).toHaveBeenCalledWith(mockSuggestionPublicationOne.id); + service.ignoreSuggestionMultiple([mockSuggestionPublicationOne]); + expect(service.ignoreSuggestion).toHaveBeenCalledWith(mockSuggestionPublicationOne.id); }); it('should get target Uuid', () => { diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.html b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.html similarity index 100% rename from src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.html rename to src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.html diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.scss b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.scss similarity index 100% rename from src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.scss rename to src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.scss diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts similarity index 75% rename from src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts rename to src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts index 23b34c4de9..dfa1e187e3 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions-notification/suggestions-notification.component.ts +++ b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts @@ -1,11 +1,14 @@ import { Component, OnInit } from '@angular/core'; -import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; import { TranslateService } from '@ngx-translate/core'; import { SuggestionTargetsStateService } from '../suggestion-targets/suggestion-targets.state.service'; -import { NotificationsService } from '../../../shared/notifications/notifications.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', @@ -22,13 +25,13 @@ export class SuggestionsNotificationComponent implements OnInit { constructor( private translateService: TranslateService, - private reciterSuggestionStateService: SuggestionTargetsStateService, + private suggestionTargetsStateService: SuggestionTargetsStateService, private notificationsService: NotificationsService, private suggestionsService: SuggestionsService ) { } ngOnInit() { - this.suggestionsRD$ = this.reciterSuggestionStateService.getCurrentUserSuggestionTargets(); + this.suggestionsRD$ = this.suggestionTargetsStateService.getCurrentUserSuggestionTargets(); } /** diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.html b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.html similarity index 100% rename from src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.html rename to src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.html diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.scss b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.scss similarity index 100% rename from src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.scss rename to src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.scss diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.spec.ts similarity index 92% rename from src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts rename to src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.spec.ts index aade2c78fa..8a5721968b 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.spec.ts +++ b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.spec.ts @@ -3,11 +3,11 @@ 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 { 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', () => { diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts similarity index 75% rename from src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts rename to src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts index 39be112bd2..2eb5cec6da 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions-popup/suggestions-popup.component.ts +++ b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts @@ -1,13 +1,16 @@ 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 { NotificationsService } from '../../shared/notifications/notifications.service'; import { SuggestionsService } from '../suggestions.service'; import { takeUntil } from 'rxjs/operators'; -import { SuggestionTarget } from '../../../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; -import { isNotEmpty } from '../../../shared/empty.util'; +import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; +import { isNotEmpty } from '../../shared/empty.util'; import { combineLatest, Subject } from 'rxjs'; +/** + * Show suggestions on a popover window, used on the homepage + */ @Component({ selector: 'ds-suggestions-popup', templateUrl: './suggestions-popup.component.html', @@ -21,7 +24,7 @@ export class SuggestionsPopupComponent implements OnInit, OnDestroy { constructor( private translateService: TranslateService, - private reciterSuggestionStateService: SuggestionTargetsStateService, + private suggestionTargetsStateService: SuggestionTargetsStateService, private notificationsService: NotificationsService, private suggestionsService: SuggestionsService ) { } @@ -33,14 +36,14 @@ export class SuggestionsPopupComponent implements OnInit, OnDestroy { public initializePopup() { const notifier = new Subject(); this.subscription = combineLatest([ - this.reciterSuggestionStateService.getCurrentUserSuggestionTargets(), - this.reciterSuggestionStateService.hasUserVisitedSuggestions() + this.suggestionTargetsStateService.getCurrentUserSuggestionTargets(), + this.suggestionTargetsStateService.hasUserVisitedSuggestions() ]).pipe(takeUntil(notifier)).subscribe(([suggestions, visited]) => { - this.reciterSuggestionStateService.dispatchRefreshUserSuggestionsAction(); + this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); if (isNotEmpty(suggestions)) { if (!visited) { suggestions.forEach((suggestionTarget: SuggestionTarget) => this.showNotificationForNewSuggestions(suggestionTarget)); - this.reciterSuggestionStateService.dispatchMarkUserSuggestionsAsVisitedAction(); + this.suggestionTargetsStateService.dispatchMarkUserSuggestionsAsVisitedAction(); notifier.next(null); notifier.complete(); } diff --git a/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts b/src/app/suggestion-notifications/suggestions.service.ts similarity index 84% rename from src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts rename to src/app/suggestion-notifications/suggestions.service.ts index b760ddf34d..d4e8825f63 100644 --- a/src/app/suggestion-notifications/reciter-suggestions/suggestions.service.ts +++ b/src/app/suggestion-notifications/suggestions.service.ts @@ -3,38 +3,41 @@ import { Injectable } from '@angular/core'; import { of, forkJoin, Observable } from 'rxjs'; import { catchError, map, mergeMap, take } from 'rxjs/operators'; -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/reciter-suggestions/models/suggestion-target.model'; -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 { AuthService } from '../core/auth/auth.service'; +import { hasValue, isNotEmpty } from '../shared/empty.util'; +import { ResearcherProfile } from '../core/profile/model/researcher-profile.model'; import { getAllSucceededRemoteDataPayload, getFinishedRemoteData, getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload -} from '../../core/shared/operators'; -import { Suggestion } from '../../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; -import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-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/layout-config.interfaces'; -import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service'; +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/layout-config.interfaces'; +import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service'; import { SuggestionSourceDataService -} from '../../core/suggestion-notifications/reciter-suggestions/source/suggestion-source-data.service'; +} from '../core/suggestion-notifications/source/suggestion-source-data.service'; import { SuggestionTargetDataService -} from '../../core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service'; +} from '../core/suggestion-notifications/target/suggestion-target-data.service'; import { SuggestionsDataService -} from '../../core/suggestion-notifications/reciter-suggestions/suggestions-data.service'; +} from '../core/suggestion-notifications/suggestions-data.service'; +/** + * useful for multiple approvals and ignores operation + * */ export interface SuggestionBulkResult { success: number; fails: number; @@ -199,7 +202,7 @@ export class SuggestionsService { * Perform the delete operation over a single suggestion. * @param suggestionId */ - public notMine(suggestionId): Observable> { + public ignoreSuggestion(suggestionId): Observable> { return this.deleteReviewedSuggestion(suggestionId).pipe( catchError((error) => of(null)) ); @@ -229,8 +232,8 @@ export class SuggestionsService { * Perform a bulk notMine operation. * @param suggestions the array containing the suggestions */ - public notMineMultiple(suggestions: Suggestion[]): Observable { - return forkJoin(suggestions.map((suggestion: Suggestion) => this.notMine(suggestion.id))) + public ignoreSuggestionMultiple(suggestions: Suggestion[]): Observable { + return forkJoin(suggestions.map((suggestion: Suggestion) => this.ignoreSuggestion(suggestion.id))) .pipe(map((results: RemoteData[]) => { return { success: results.filter((result) => result != null).length, diff --git a/src/app/suggestions-page/suggestions-page-routing.module.ts b/src/app/suggestions-page/suggestions-page-routing.module.ts index 05dc6321b7..0a7d9bd90c 100644 --- a/src/app/suggestions-page/suggestions-page-routing.module.ts +++ b/src/app/suggestions-page/suggestions-page-routing.module.ts @@ -4,7 +4,7 @@ import { RouterModule } from '@angular/router'; import { SuggestionsPageResolver } from './suggestions-page.resolver'; import { SuggestionsPageComponent } from './suggestions-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; -import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver'; @NgModule({ imports: [ @@ -13,11 +13,11 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso path: ':targetId', resolve: { suggestionTargets: SuggestionsPageResolver, - breadcrumb: I18nBreadcrumbResolver + breadcrumb: ItemBreadcrumbResolver//I18nBreadcrumbResolver }, data: { - title: 'admin.notifications.recitersuggestion.page.title', - breadcrumbKey: 'admin.notifications.recitersuggestion', + title: 'admin.notifications.publicationclaim.page.title', + breadcrumbKey: 'admin.notifications.publicationclaim', showBreadcrumbsFluid: false }, canActivate: [AuthenticatedGuard], @@ -27,7 +27,8 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso ]) ], providers: [ - SuggestionsPageResolver + SuggestionsPageResolver, + ItemBreadcrumbResolver ] }) export class SuggestionsPageRoutingModule { diff --git a/src/app/suggestions-page/suggestions-page.component.html b/src/app/suggestions-page/suggestions-page.component.html index 59b6f58c5f..f8b14f6728 100644 --- a/src/app/suggestions-page/suggestions-page.component.html +++ b/src/app/suggestions-page/suggestions-page.component.html @@ -19,7 +19,7 @@ [isBulk]="true" [isCollectionFixed]="isCollectionFixed(suggestionsRD.page)" (approveAndImport)="approveAndImportAllSelected($event)" - (notMineClicked)="notMineAllSelected()"> + (ignoreSuggestionClicked)="ignoreSuggestionAllSelected()">
@@ -35,7 +35,7 @@ [object]="object" [isSelected]="selectedSuggestions[object.id]" [isCollectionFixed]="isCollectionFixed([object])" - (notMineClicked)="notMine($event)" + (ignoreSuggestionClicked)="ignoreSuggestion($event)" (selected)="onSelected(object, $event)" (approveAndImport)="approveAndImport($event)"> diff --git a/src/app/suggestions-page/suggestions-page.component.spec.ts b/src/app/suggestions-page/suggestions-page.component.spec.ts index 0b0e235082..78cf087362 100644 --- a/src/app/suggestions-page/suggestions-page.component.spec.ts +++ b/src/app/suggestions-page/suggestions-page.component.spec.ts @@ -10,23 +10,23 @@ import { SuggestionsPageComponent } from './suggestions-page.component'; import { SuggestionApproveAndImport, SuggestionListElementComponent -} from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; -import { SuggestionsService } from '../suggestion-notifications/reciter-suggestions/suggestions.service'; +} from '../suggestion-notifications/suggestion-list-element/suggestion-list-element.component'; +import { SuggestionsService } from '../suggestion-notifications/suggestions.service'; import { getMockSuggestionNotificationsStateService, getMockSuggestionsService } from '../shared/mocks/suggestion.mock'; import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model'; -import { Suggestion } from '../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; -import { mockSuggestionPublicationOne, mockSuggestionPublicationTwo } from '../shared/mocks/reciter-suggestion.mock'; -import { SuggestionEvidencesComponent } from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-evidences/suggestion-evidences.component'; +import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model'; +import { mockSuggestionPublicationOne, mockSuggestionPublicationTwo } from '../shared/mocks/publication-claim.mock'; +import { SuggestionEvidencesComponent } from '../suggestion-notifications/suggestion-list-element/suggestion-evidences/suggestion-evidences.component'; import { ObjectKeysPipe } from '../shared/utils/object-keys-pipe'; import { VarDirective } from '../shared/utils/var.directive'; import { ActivatedRoute, Router } from '@angular/router'; import { RouterStub } from '../shared/testing/router.stub'; -import { mockSuggestionTargetsObjectOne } from '../shared/mocks/reciter-suggestion-targets.mock'; +import { mockSuggestionTargetsObjectOne } from '../shared/mocks/publication-claim-targets.mock'; import { AuthService } from '../core/auth/auth.service'; import { NotificationsService } from '../shared/notifications/notifications.service'; import { NotificationsServiceStub } from '../shared/testing/notifications-service.stub'; import { getMockTranslateService } from '../shared/mocks/translate.service.mock'; -import { SuggestionTargetsStateService } from '../suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service'; +import { SuggestionTargetsStateService } from '../suggestion-notifications/suggestion-targets/suggestion-targets.state.service'; import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; import { createSuccessfulRemoteDataObject } from '../shared/remote-data.utils'; import { PageInfo } from '../core/shared/page-info.model'; @@ -138,7 +138,7 @@ describe('SuggestionPageComponent', () => { scheduler.schedule(() => fixture.detectChanges()); scheduler.flush(); - component.notMine('1'); + component.ignoreSuggestion('1'); expect(mockSuggestionsService.notMine).toHaveBeenCalledWith('1'); expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); expect(component.updatePage).toHaveBeenCalled(); @@ -149,7 +149,7 @@ describe('SuggestionPageComponent', () => { scheduler.schedule(() => fixture.detectChanges()); scheduler.flush(); - component.notMineAllSelected(); + component.ignoreSuggestionAllSelected(); expect(mockSuggestionsService.notMineMultiple).toHaveBeenCalled(); expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); expect(component.updatePage).toHaveBeenCalled(); diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts index b4ca0108d5..4883ed8406 100644 --- a/src/app/suggestions-page/suggestions-page.component.ts +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -9,14 +9,14 @@ import { SortDirection, SortOptions, } from '../core/cache/models/sort-options.m import { PaginatedList } from '../core/data/paginated-list.model'; import { RemoteData } from '../core/data/remote-data'; import { getFirstSucceededRemoteDataPayload } from '../core/shared/operators'; -import { SuggestionBulkResult, SuggestionsService } from '../suggestion-notifications/reciter-suggestions/suggestions.service'; +import { SuggestionBulkResult, SuggestionsService } from '../suggestion-notifications/suggestions.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { Suggestion } from '../core/suggestion-notifications/reciter-suggestions/models/suggestion.model'; -import { SuggestionTarget } from '../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model'; +import { SuggestionTarget } from '../core/suggestion-notifications/models/suggestion-target.model'; import { AuthService } from '../core/auth/auth.service'; -import { SuggestionApproveAndImport } from '../suggestion-notifications/reciter-suggestions/suggestion-list-element/suggestion-list-element.component'; +import { SuggestionApproveAndImport } from '../suggestion-notifications/suggestion-list-element/suggestion-list-element.component'; import { NotificationsService } from '../shared/notifications/notifications.service'; -import { SuggestionTargetsStateService } from '../suggestion-notifications/reciter-suggestions/suggestion-targets/suggestion-targets.state.service'; +import { SuggestionTargetsStateService } from '../suggestion-notifications/suggestion-targets/suggestion-targets.state.service'; import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; import { PaginationService } from '../core/pagination/pagination.service'; import { WorkspaceItem } from '../core/submission/models/workspaceitem.model'; @@ -155,8 +155,8 @@ export class SuggestionsPageComponent implements OnInit { * Used to delete a suggestion. * @suggestionId */ - notMine(suggestionId) { - this.suggestionService.notMine(suggestionId).subscribe((res) => { + ignoreSuggestion(suggestionId) { + this.suggestionService.ignoreSuggestion(suggestionId).subscribe((res) => { this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); this.updatePage(); }); @@ -165,10 +165,10 @@ export class SuggestionsPageComponent implements OnInit { /** * Used to delete all selected suggestions. */ - notMineAllSelected() { + ignoreSuggestionAllSelected() { this.isBulkOperationPending = true; this.suggestionService - .notMineMultiple(Object.values(this.selectedSuggestions)) + .ignoreSuggestionMultiple(Object.values(this.selectedSuggestions)) .subscribe((results: SuggestionBulkResult) => { this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); this.updatePage(); diff --git a/src/app/suggestions-page/suggestions-page.module.ts b/src/app/suggestions-page/suggestions-page.module.ts index f5397cbdce..e25e483e39 100644 --- a/src/app/suggestions-page/suggestions-page.module.ts +++ b/src/app/suggestions-page/suggestions-page.module.ts @@ -4,8 +4,8 @@ import { CommonModule } from '@angular/common'; import { SuggestionsPageComponent } from './suggestions-page.component'; import { SharedModule } from '../shared/shared.module'; import { SuggestionsPageRoutingModule } from './suggestions-page-routing.module'; -import { SuggestionsService } from '../suggestion-notifications/reciter-suggestions/suggestions.service'; -import { SuggestionsDataService } from '../core/suggestion-notifications/reciter-suggestions/suggestions-data.service'; +import { SuggestionsService } from '../suggestion-notifications/suggestions.service'; +import { SuggestionsDataService } from '../core/suggestion-notifications/suggestions-data.service'; import { NotificationsModule } from '../notifications/notifications.module'; @NgModule({ diff --git a/src/app/suggestions-page/suggestions-page.resolver.ts b/src/app/suggestions-page/suggestions-page.resolver.ts index 6d05c0bc84..dde5b847f5 100644 --- a/src/app/suggestions-page/suggestions-page.resolver.ts +++ b/src/app/suggestions-page/suggestions-page.resolver.ts @@ -6,10 +6,10 @@ import { find } from 'rxjs/operators'; import { RemoteData } from '../core/data/remote-data'; import { hasValue } from '../shared/empty.util'; -import { SuggestionTarget } from '../core/suggestion-notifications/reciter-suggestions/models/suggestion-target.model'; +import { SuggestionTarget } from '../core/suggestion-notifications/models/suggestion-target.model'; import { SuggestionTargetDataService -} from '../core/suggestion-notifications/reciter-suggestions/target/suggestion-target-data.service'; +} from '../core/suggestion-notifications/target/suggestion-target-data.service'; /** * This class represents a resolver that requests a specific collection before the route is activated diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0e5ea85074..989d2ea9fb 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -24,9 +24,9 @@ "404.page-not-found": "page not found", - "admin.notifications.recitersuggestion.breadcrumbs": "Suggestions", + "admin.notifications.publicationclaim.breadcrumbs": "Suggestions", - "admin.notifications.recitersuggestion.page.title": "Suggestions", + "admin.notifications.publicationclaim.page.title": "Suggestions", "error-page.description.401": "unauthorized", @@ -2950,7 +2950,7 @@ "menu.section.quality-assurance": "Quality Assurance", - "menu.section.notifications_reciter": "Publication Claim", + "menu.section.notifications_publication-claim": "Publication Claim", "menu.section.pin": "Pin sidebar", @@ -3562,63 +3562,67 @@ "media-viewer.playlist": "Playlist", - "reciter.suggestion.loading": "Loading ...", + "suggestion.loading": "Loading ...", - "reciter.suggestion.title": "Suggestions", + "suggestion.title": "Publication Claims", - "reciter.suggestion.targets.description": "Below you can see all the suggestions ", + "suggestion.title.breadcrumbs": "Publication Claims", - "reciter.suggestion.targets": "Current Suggestions", + "suggestion.targets.description": "Below you can see all the suggestions ", - "reciter.suggestion.table.name": "Researcher Name", + "suggestion.targets": "Current Suggestions", - "reciter.suggestion.table.actions": "Actions", + "suggestion.table.name": "Researcher Name", - "reciter.suggestion.button.review": "Review {{ total }} suggestion(s)", + "suggestion.table.actions": "Actions", - "reciter.suggestion.noTargets": "No target found.", + "suggestion.button.review": "Review {{ total }} suggestion(s)", - "reciter.suggestion.target.error.service.retrieve": "An error occurred while loading the Suggestion targets", + "suggestion.button.review.title": "Review {{ total }} suggestion(s) for ", - "reciter.suggestion.evidence.type": "Type", + "suggestion.noTargets": "No target found.", - "reciter.suggestion.evidence.score": "Score", + "suggestion.target.error.service.retrieve": "An error occurred while loading the Suggestion targets", - "reciter.suggestion.evidence.notes": "Notes", + "suggestion.evidence.type": "Type", - "reciter.suggestion.approveAndImport": "Approve & import", + "suggestion.evidence.score": "Score", - "reciter.suggestion.approveAndImport.success": "The suggestion has been imported successfully. View.", + "suggestion.evidence.notes": "Notes", - "reciter.suggestion.approveAndImport.bulk": "Approve & import Selected", + "suggestion.approveAndImport": "Approve & import", - "reciter.suggestion.approveAndImport.bulk.success": "{{ count }} suggestions have been imported successfully ", + "suggestion.approveAndImport.success": "The suggestion has been imported successfully. View.", - "reciter.suggestion.approveAndImport.bulk.error": "{{ count }} suggestions haven't been imported due to unexpected server errors", + "suggestion.approveAndImport.bulk": "Approve & import Selected", - "reciter.suggestion.notMine": "Not mine", + ".suggestion.approveAndImport.bulk.success": "{{ count }} suggestions have been imported successfully ", - "reciter.suggestion.notMine.success": "The suggestion has been discarded", + "suggestion.approveAndImport.bulk.error": "{{ count }} suggestions haven't been imported due to unexpected server errors", - "reciter.suggestion.notMine.bulk": "Not mine Selected", + "suggestion.ignoreSuggestion": "Ignore Suggestion", - "reciter.suggestion.notMine.bulk.success": "{{ count }} suggestions have been discarded ", + "suggestion.ignoreSuggestion.success": "The suggestion has been discarded", - "reciter.suggestion.notMine.bulk.error": "{{ count }} suggestions haven't been discarded due to unexpected server errors", + "suggestion.ignoreSuggestion.bulk": "Ignore Suggestion Selected", - "reciter.suggestion.seeEvidence": "See evidence", + "suggestion.ignoreSuggestion.bulk.success": "{{ count }} suggestions have been discarded ", - "reciter.suggestion.hideEvidence": "Hide evidence", + "suggestion.ignoreSuggestion.bulk.error": "{{ count }} suggestions haven't been discarded due to unexpected server errors", - "reciter.suggestion.suggestionFor": "Suggestion for", + "suggestion.seeEvidence": "See evidence", - "reciter.suggestion.source.openaire": "OpenAIRE Graph", + "suggestion.hideEvidence": "Hide evidence", - "reciter.suggestion.from.source": "from the ", + "suggestion.suggestionFor": "Suggestion for", - "reciter.suggestion.totalScore": "Total Score", + "suggestion.source.openaire": "OpenAIRE Graph", - "reciter.suggestion.type.openaire": "OpenAIRE", + "suggestion.from.source": "from the ", + + "suggestion.totalScore": "Total Score", + + "suggestion.type.openaire": "OpenAIRE", "register-email.title": "New user registration", From 66cd035f87a2f777fe3d5d1e9ac52e254f6ee159 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 25 Jan 2024 11:50:02 +0100 Subject: [PATCH 42/94] refactor, fix tests --- ...ions-publication-claim-page.component.html | 2 +- ...s-publication-claim-page.component.spec.ts | 14 +- ...ations-publication-claim-page.component.ts | 2 +- .../admin-notifications-routing.module.ts | 4 +- .../admin-notifications.module.ts | 4 +- src/app/core/data/base/base-data.service.ts | 8 +- src/app/core/data/base/create-data.ts | 2 +- src/app/core/data/base/find-all-data.ts | 2 +- src/app/core/data/base/patch-data.ts | 2 +- src/app/core/data/base/put-data.ts | 2 +- src/app/core/data/base/search-data.ts | 2 +- src/app/core/data/href-only-data.service.ts | 4 +- ...data.service.ts => update-data-service.ts} | 385 +++--------------- src/app/core/data/update-data.service.ts | 13 - .../core/data/version-data.service.spec.ts | 2 +- .../core/eperson/eperson-data.service.spec.ts | 2 +- .../workflowitem-data.service.spec.ts | 2 +- .../workspaceitem-data.service.spec.ts | 2 +- .../suggestions-data.service.ts | 28 +- .../dso-edit-metadata.component.ts | 2 +- .../themed-dso-edit-metadata.component.ts | 2 +- src/app/menu.resolver.ts | 2 +- src/app/notifications/notifications.module.ts | 4 +- .../eperson-group-list.component.ts | 2 +- .../mydspace-actions-service.factory.ts | 2 +- .../mydspace-actions/mydspace-actions.ts | 2 +- .../suggestion-actions.component.html | 4 +- .../publication-claim.component.html} | 16 +- .../publication-claim.component.scss} | 0 .../publication-claim.component.ts} | 22 +- .../suggestion.service.spec.ts | 2 +- .../suggestions-popup.component.ts | 4 +- .../suggestions.service.ts | 4 +- .../suggestions-page.component.spec.ts | 5 +- .../suggestions-page.component.ts | 14 +- src/assets/i18n/en.json5 | 4 +- src/config/app-config.interface.ts | 4 +- src/config/default-app-config.ts | 4 +- src/config/layout-config.interfaces.ts | 46 --- src/config/suggestion-config.interfaces.ts | 6 + 40 files changed, 143 insertions(+), 490 deletions(-) rename src/app/core/data/{data.service.ts => update-data-service.ts} (52%) delete mode 100644 src/app/core/data/update-data.service.ts rename src/app/suggestion-notifications/suggestion-targets/{suggestion-targets.component.html => publication-claim/publication-claim.component.html} (72%) rename src/app/suggestion-notifications/suggestion-targets/{suggestion-targets.component.scss => publication-claim/publication-claim.component.scss} (100%) rename src/app/suggestion-notifications/suggestion-targets/{suggestion-targets.component.ts => publication-claim/publication-claim.component.ts} (83%) delete mode 100644 src/config/layout-config.interfaces.ts create mode 100644 src/config/suggestion-config.interfaces.ts diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html index 965b9e08c9..b04e7132f1 100644 --- a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html +++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.html @@ -1 +1 @@ - + diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts index c0209da898..c5406023ce 100644 --- a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts +++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.spec.ts @@ -1,13 +1,13 @@ 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 { CommonModule } from '@angular/common'; import { TranslateModule } from '@ngx-translate/core'; -describe('AdminNotificationsSuggestionTargetsPageComponent', () => { - let component: AdminNotificationsSuggestionTargetsPageComponent; - let fixture: ComponentFixture; +describe('AdminNotificationsPublicationClaimPageComponent', () => { + let component: AdminNotificationsPublicationClaimPageComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -16,10 +16,10 @@ describe('AdminNotificationsSuggestionTargetsPageComponent', () => { TranslateModule.forRoot() ], declarations: [ - AdminNotificationsSuggestionTargetsPageComponent + AdminNotificationsPublicationClaimPageComponent ], providers: [ - AdminNotificationsSuggestionTargetsPageComponent + AdminNotificationsPublicationClaimPageComponent ], schemas: [NO_ERRORS_SCHEMA] }) @@ -27,7 +27,7 @@ describe('AdminNotificationsSuggestionTargetsPageComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(AdminNotificationsSuggestionTargetsPageComponent); + fixture = TestBed.createComponent(AdminNotificationsPublicationClaimPageComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts index 10023ead93..2256a1bc36 100644 --- a/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts +++ b/src/app/admin/admin-notifications/admin-notifications-publication-claim-page/admin-notifications-publication-claim-page.component.ts @@ -5,6 +5,6 @@ import { Component } from '@angular/core'; templateUrl: './admin-notifications-publication-claim-page.component.html', styleUrls: ['./admin-notifications-publication-claim-page.component.scss'] }) -export class AdminNotificationsSuggestionTargetsPageComponent { +export class AdminNotificationsPublicationClaimPageComponent { } diff --git a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts index 648bdc0a1f..07a98aa080 100644 --- a/src/app/admin/admin-notifications/admin-notifications-routing.module.ts +++ b/src/app/admin/admin-notifications/admin-notifications-routing.module.ts @@ -5,7 +5,7 @@ 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 { 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 { QUALITY_ASSURANCE_EDIT_PATH } from './admin-notifications-routing-paths'; import { AdminQualityAssuranceTopicsPageComponent } from './admin-quality-assurance-topics-page/admin-quality-assurance-topics-page.component'; @@ -26,7 +26,7 @@ import { { canActivate: [ AuthenticatedGuard ], path: `${PUBLICATION_CLAIMS_PATH}`, - component: AdminNotificationsSuggestionTargetsPageComponent, + component: AdminNotificationsPublicationClaimPageComponent, pathMatch: 'full', resolve: { breadcrumb: I18nBreadcrumbResolver, diff --git a/src/app/admin/admin-notifications/admin-notifications.module.ts b/src/app/admin/admin-notifications/admin-notifications.module.ts index 3566bdd91a..d9efb4c288 100644 --- a/src/app/admin/admin-notifications/admin-notifications.module.ts +++ b/src/app/admin/admin-notifications/admin-notifications.module.ts @@ -3,7 +3,7 @@ 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 { 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 { 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'; @@ -18,7 +18,7 @@ import { NotificationsModule } from '../../notifications/notifications.module'; NotificationsModule ], declarations: [ - AdminNotificationsSuggestionTargetsPageComponent, + AdminNotificationsPublicationClaimPageComponent, AdminQualityAssuranceTopicsPageComponent, AdminQualityAssuranceEventsPageComponent, AdminQualityAssuranceSourcePageComponent diff --git a/src/app/core/data/base/base-data.service.ts b/src/app/core/data/base/base-data.service.ts index c7cd5b0a70..84b1686024 100644 --- a/src/app/core/data/base/base-data.service.ts +++ b/src/app/core/data/base/base-data.service.ts @@ -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 implements CreateData, SearchData { @@ -385,7 +385,7 @@ export class BaseDataService 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 diff --git a/src/app/core/data/base/create-data.ts b/src/app/core/data/base/create-data.ts index 3ffcd9adf2..d2e009f669 100644 --- a/src/app/core/data/base/create-data.ts +++ b/src/app/core/data/base/create-data.ts @@ -37,7 +37,7 @@ export interface CreateData { } /** - * 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. diff --git a/src/app/core/data/base/find-all-data.ts b/src/app/core/data/base/find-all-data.ts index 57884e537e..bc0c1fb613 100644 --- a/src/app/core/data/base/find-all-data.ts +++ b/src/app/core/data/base/find-all-data.ts @@ -42,7 +42,7 @@ export interface FindAllData { } /** - * 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. diff --git a/src/app/core/data/base/patch-data.ts b/src/app/core/data/base/patch-data.ts index e30c394a34..1f93671458 100644 --- a/src/app/core/data/base/patch-data.ts +++ b/src/app/core/data/base/patch-data.ts @@ -54,7 +54,7 @@ export interface PatchData { } /** - * 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. diff --git a/src/app/core/data/base/put-data.ts b/src/app/core/data/base/put-data.ts index bd2a8d2929..66ae73405e 100644 --- a/src/app/core/data/base/put-data.ts +++ b/src/app/core/data/base/put-data.ts @@ -31,7 +31,7 @@ export interface PutData { } /** - * 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. diff --git a/src/app/core/data/base/search-data.ts b/src/app/core/data/base/search-data.ts index 536d6d6e25..ff0b492945 100644 --- a/src/app/core/data/base/search-data.ts +++ b/src/app/core/data/base/search-data.ts @@ -51,7 +51,7 @@ export interface SearchData { } /** - * 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. diff --git a/src/app/core/data/href-only-data.service.ts b/src/app/core/data/href-only-data.service.ts index 0a765de101..8393d71460 100644 --- a/src/app/core/data/href-only-data.service.ts +++ b/src/app/core/data/href-only-data.service.ts @@ -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 diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/update-data-service.ts similarity index 52% rename from src/app/core/data/data.service.ts rename to src/app/core/data/update-data-service.ts index 43b60f874d..994cd0ee87 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/update-data-service.ts @@ -42,45 +42,60 @@ import { } from './request.models'; import { RequestService } from './request.service'; import { RestRequestMethod } from './rest-request-method'; -import { UpdateDataService } from './update-data.service'; import { GenericConstructor } from '../shared/generic-constructor'; import { NoContent } from '../shared/NoContent.model'; import { CacheableObject } from '../cache/cacheable-object.model'; import { CoreState } from '../core-state.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 implements UpdateDataService { - protected abstract requestService: RequestService; - protected abstract rdbService: RemoteDataBuildService; +export interface UpdateDataService { + patch(dso: T, operations: Operation[]): Observable>; + update(object: T): Observable>; + 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 extends BaseDataService implements UpdateDataService, FindAllData, SearchData, CreateData { protected abstract store: Store; - protected abstract linkPath: string; - protected abstract halService: HALEndpointService; - protected abstract objectCache: ObjectCacheService; - protected abstract notificationsService: NotificationsService; protected abstract http: HttpClient; protected abstract comparator: ChangeAnalyzer; - /** - * Allows subclasses to reset the response cache time. - */ - protected responseMsToLive: number; + private findAllData: FindAllDataImpl; + private searchData: SearchDataImpl; + private createData: CreateData; - /** - * Get the endpoint for browsing - * @param options The [[FindListOptions]] object - * @param linkPath The link path for the object - * @returns {Observable} - */ - getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable { - return this.getEndpoint(); + + constructor( + protected linkPath: string, + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + 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 { - return this.halService.getEndpoint(this.linkPath); - } /** * Create the HREF with given options object @@ -92,16 +107,7 @@ export abstract class DataService implements UpdateDa * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved */ public getFindAllHref(options: FindListOptions = {}, linkPath?: string, ...linksToFollow: FollowLinkConfig[]): Observable { - let endpoint$: Observable; - 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))); + return this.findAllData.getFindAllHref(options, linkPath, ...linksToFollow) } /** @@ -114,149 +120,7 @@ export abstract class DataService implements UpdateDa * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved */ public getSearchByHref(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig[]): Observable { - let result$: Observable; - 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} - * 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[]): 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} - * Return an observable that emits created HREF - */ - buildHrefWithParams(href: string, params: RequestParam[], ...linksToFollow: FollowLinkConfig[]): 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[]) { - linksToFollow.forEach((linkToFollow: FollowLinkConfig) => { - 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[]): string[] { - let nestEmbed = embedString; - linksToFollow.forEach((linkToFollow: FollowLinkConfig) => { - 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; + return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow) } /** @@ -274,7 +138,7 @@ export abstract class DataService implements UpdateDa * Return an observable that emits object list */ findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { - 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 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 - * @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, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable> { - 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(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) => 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 - * @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, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { - 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(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>) => 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 - * @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, 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 { - 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 * @@ -464,9 +216,7 @@ export abstract class DataService implements UpdateDa * Return an observable that emits response from the server */ searchBy(searchMethod: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { - const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow); - - return this.findAllByHref(hrefObs, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); + return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow) } /** @@ -548,38 +298,7 @@ export abstract class DataService implements UpdateDa * Array with additional params to combine with query string */ create(object: T, ...params: RequestParam[]): Observable> { - const requestId = this.requestService.generateRequestId(); - 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(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) => rd.isLoading, true) - ).subscribe((rd: RemoteData) => { - if (rd.hasFailed) { - this.notificationsService.error('Server Error:', rd.errorMessage, new NotificationOptions(-1)); - } - }); - - return result$; + return this.createData.create(object, ...params) } /** @@ -732,16 +451,4 @@ export abstract class DataService implements UpdateDa commitUpdates(method?: RestRequestMethod) { 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; - } } diff --git a/src/app/core/data/update-data.service.ts b/src/app/core/data/update-data.service.ts deleted file mode 100644 index 9f707a82da..0000000000 --- a/src/app/core/data/update-data.service.ts +++ /dev/null @@ -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 { - patch(dso: T, operations: Operation[]): Observable>; - update(object: T): Observable>; - commitUpdates(method?: RestRequestMethod); -} diff --git a/src/app/core/data/version-data.service.spec.ts b/src/app/core/data/version-data.service.spec.ts index dd3f9eec94..0579b998b8 100644 --- a/src/app/core/data/version-data.service.spec.ts +++ b/src/app/core/data/version-data.service.spec.ts @@ -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(); diff --git a/src/app/core/eperson/eperson-data.service.spec.ts b/src/app/core/eperson/eperson-data.service.spec.ts index c1bc3563a3..cbddf1e6c3 100644 --- a/src/app/core/eperson/eperson-data.service.spec.ts +++ b/src/app/core/eperson/eperson-data.service.spec.ts @@ -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); }); }); diff --git a/src/app/core/submission/workflowitem-data.service.spec.ts b/src/app/core/submission/workflowitem-data.service.spec.ts index 3f6ec54fda..64ffbe5718 100644 --- a/src/app/core/submission/workflowitem-data.service.spec.ts +++ b/src/app/core/submission/workflowitem-data.service.spec.ts @@ -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(); diff --git a/src/app/core/submission/workspaceitem-data.service.spec.ts b/src/app/core/submission/workspaceitem-data.service.spec.ts index 34548c1e53..25a849baa2 100644 --- a/src/app/core/submission/workspaceitem-data.service.spec.ts +++ b/src/app/core/submission/workspaceitem-data.service.spec.ts @@ -141,7 +141,7 @@ 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(); const searchUrl = service.getIDHref('item', [new RequestParam('uuid', encodeURIComponent('1234-1234'))]); diff --git a/src/app/core/suggestion-notifications/suggestions-data.service.ts b/src/app/core/suggestion-notifications/suggestions-data.service.ts index 72476f6a15..945cc8b083 100644 --- a/src/app/core/suggestion-notifications/suggestions-data.service.ts +++ b/src/app/core/suggestion-notifications/suggestions-data.service.ts @@ -11,7 +11,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv 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 { 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'; @@ -31,13 +31,9 @@ import { SuggestionTargetDataService } from './target/suggestion-target-data.ser /* 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 { - /** - * The REST endpoint. - */ - protected linkPath = 'suggestions'; +export class SuggestionDataServiceImpl extends UpdateDataServiceImpl { /** * Initialize service variables @@ -49,6 +45,7 @@ export class SuggestionDataServiceImpl extends DataService { * @param {NotificationsService} notificationsService * @param {HttpClient} http * @param {ChangeAnalyzer} comparator + * @param responseMsToLive */ constructor( protected requestService: RequestService, @@ -58,8 +55,10 @@ export class SuggestionDataServiceImpl extends DataService { protected halService: HALEndpointService, protected notificationsService: NotificationsService, protected http: HttpClient, - protected comparator: ChangeAnalyzer) { - super(); + protected comparator: ChangeAnalyzer, + protected responseMsToLive: number, + ) { + super('suggestions', requestService, rdbService, objectCache, halService, notificationsService ,responseMsToLive); } } @@ -73,20 +72,22 @@ export class SuggestionsDataService { 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 @@ -98,6 +99,7 @@ export class SuggestionsDataService { * @param {DefaultChangeAnalyzer} comparatorSuggestions * @param {DefaultChangeAnalyzer} comparatorSources * @param {DefaultChangeAnalyzer} comparatorTargets + * @param responseMsToLive */ constructor( protected requestService: RequestService, @@ -110,7 +112,7 @@ export class SuggestionsDataService { protected comparatorSources: DefaultChangeAnalyzer, protected comparatorTargets: DefaultChangeAnalyzer, ) { - 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); } diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts index d44817be84..ad3d6680a1 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts @@ -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', diff --git a/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts b/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts index ba21907c99..51eec3ac23 100644 --- a/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts +++ b/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts @@ -2,7 +2,7 @@ import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { DsoEditMetadataComponent } from './dso-edit-metadata.component'; import { Component, Input } from '@angular/core'; 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({ selector: 'ds-themed-dso-edit-metadata', diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index fc6eb00195..f2a7e2dbe1 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -541,7 +541,7 @@ export class MenuResolver implements Resolve { { id: 'notifications', active: false, - visible: authorized && canSeeQA, + visible: authorized && true, model: { type: MenuItemType.TEXT, text: 'menu.section.notifications' diff --git a/src/app/notifications/notifications.module.ts b/src/app/notifications/notifications.module.ts index cd8cb99811..00c7582b2f 100644 --- a/src/app/notifications/notifications.module.ts +++ b/src/app/notifications/notifications.module.ts @@ -26,7 +26,7 @@ import { QualityAssuranceSourceService } from './qa/source/quality-assurance-sou import { QualityAssuranceSourceDataService } 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 { SuggestionListElementComponent @@ -65,7 +65,7 @@ const COMPONENTS = [ QualityAssuranceTopicsComponent, QualityAssuranceEventsComponent, QualityAssuranceSourceComponent, - SuggestionTargetsComponent, + PublicationClaimComponent, SuggestionActionsComponent, SuggestionListElementComponent, SuggestionEvidencesComponent, diff --git a/src/app/shared/eperson-group-list/eperson-group-list.component.ts b/src/app/shared/eperson-group-list/eperson-group-list.component.ts index 7cad7a9783..1a08740a94 100644 --- a/src/app/shared/eperson-group-list/eperson-group-list.component.ts +++ b/src/app/shared/eperson-group-list/eperson-group-list.component.ts @@ -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 diff --git a/src/app/shared/mydspace-actions/mydspace-actions-service.factory.ts b/src/app/shared/mydspace-actions/mydspace-actions-service.factory.ts index 6408dddac6..987ca1411e 100644 --- a/src/app/shared/mydspace-actions/mydspace-actions-service.factory.ts +++ b/src/app/shared/mydspace-actions/mydspace-actions-service.factory.ts @@ -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> { public getConstructor(type: ResourceType): TService { diff --git a/src/app/shared/mydspace-actions/mydspace-actions.ts b/src/app/shared/mydspace-actions/mydspace-actions.ts index 931929ccce..184f059bd8 100644 --- a/src/app/shared/mydspace-actions/mydspace-actions.ts +++ b/src/app/shared/mydspace-actions/mydspace-actions.ts @@ -47,7 +47,7 @@ export abstract class MyDSpaceActionsComponent(false); /** - * Instance of DataService related to mydspace object + * Instance of UpdateDataServiceImpl related to mydspace object */ protected objectDataService: TService; diff --git a/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.html b/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.html index 24e970a818..2a46191dee 100644 --- a/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.html +++ b/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.html @@ -23,7 +23,7 @@ {{ ignoreSuggestionLabel() | translate}}
diff --git a/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.html b/src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.html similarity index 72% rename from src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.html rename to src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.html index 3d98082ba6..45068cd12c 100644 --- a/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.html +++ b/src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.html @@ -1,9 +1,9 @@
-

{{'reciter.suggestion.title'| translate}}

+

{{'suggestion.title'| translate}}

- + - +
- - + + @@ -33,9 +33,9 @@
diff --git a/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.scss b/src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.scss similarity index 100% rename from src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.scss rename to src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.scss diff --git a/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.ts b/src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.ts similarity index 83% rename from src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.ts rename to src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.ts index e469ac1386..aef25f4075 100644 --- a/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.component.ts +++ b/src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.ts @@ -4,23 +4,23 @@ import { Router } from '@angular/router'; import { Observable, Subscription } from 'rxjs'; import { distinctUntilChanged, take } from 'rxjs/operators'; -import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; -import { hasValue } from '../../shared/empty.util'; -import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { SuggestionTargetsStateService } from './suggestion-targets.state.service'; -import { getSuggestionPageRoute } from '../../suggestions-page/suggestions-page-routing-paths'; -import { SuggestionsService } from '../suggestions.service'; -import { PaginationService } from '../../core/pagination/pagination.service'; +import { SuggestionTarget } from '../../../core/suggestion-notifications/models/suggestion-target.model'; +import { hasValue } from '../../../shared/empty.util'; +import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; +import { SuggestionTargetsStateService } from '../suggestion-targets.state.service'; +import { getSuggestionPageRoute } from '../../../suggestions-page/suggestions-page-routing-paths'; +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 diff --git a/src/app/suggestion-notifications/suggestion.service.spec.ts b/src/app/suggestion-notifications/suggestion.service.spec.ts index c7d0c7a4bf..e6ca83d5fe 100644 --- a/src/app/suggestion-notifications/suggestion.service.spec.ts +++ b/src/app/suggestion-notifications/suggestion.service.spec.ts @@ -156,7 +156,7 @@ describe('SuggestionsService test', () => { }); it('should delete suggestions', () => { - spyOn(service, 'notMine'); + spyOn(service, 'ignoreSuggestion'); service.ignoreSuggestionMultiple([mockSuggestionPublicationOne]); expect(service.ignoreSuggestion).toHaveBeenCalledWith(mockSuggestionPublicationOne.id); }); diff --git a/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts index 2eb5cec6da..762f80085a 100644 --- a/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts +++ b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts @@ -18,7 +18,7 @@ import { combineLatest, Subject } from 'rxjs'; }) export class SuggestionsPopupComponent implements OnInit, OnDestroy { - labelPrefix = 'mydspace.'; + labelPrefix = 'notification.'; subscription; @@ -57,7 +57,7 @@ export class SuggestionsPopupComponent implements OnInit, OnDestroy { * @private */ 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.notificationsService.success('', content, {timeOut:0}, true); } diff --git a/src/app/suggestion-notifications/suggestions.service.ts b/src/app/suggestion-notifications/suggestions.service.ts index d4e8825f63..47d4124a47 100644 --- a/src/app/suggestion-notifications/suggestions.service.ts +++ b/src/app/suggestion-notifications/suggestions.service.ts @@ -23,7 +23,7 @@ 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/layout-config.interfaces'; +import {SuggestionConfig} from '../../config/suggestion-config.interfaces'; import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service'; import { 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 */ public ignoreSuggestionMultiple(suggestions: Suggestion[]): Observable { diff --git a/src/app/suggestions-page/suggestions-page.component.spec.ts b/src/app/suggestions-page/suggestions-page.component.spec.ts index 78cf087362..2f6cfb7278 100644 --- a/src/app/suggestions-page/suggestions-page.component.spec.ts +++ b/src/app/suggestions-page/suggestions-page.component.spec.ts @@ -41,7 +41,6 @@ describe('SuggestionPageComponent', () => { let scheduler: TestScheduler; const mockSuggestionsService = getMockSuggestionsService(); const mockSuggestionsTargetStateService = getMockSuggestionNotificationsStateService(); - const suggestionTargetsList: PaginatedList = buildPaginatedList(new PageInfo(), [mockSuggestionPublicationOne, mockSuggestionPublicationTwo]); const router = new RouterStub(); const routeStub = { data: observableOf({ @@ -139,7 +138,7 @@ describe('SuggestionPageComponent', () => { scheduler.schedule(() => fixture.detectChanges()); scheduler.flush(); component.ignoreSuggestion('1'); - expect(mockSuggestionsService.notMine).toHaveBeenCalledWith('1'); + expect(mockSuggestionsService.ignoreSuggestion).toHaveBeenCalledWith('1'); expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); expect(component.updatePage).toHaveBeenCalled(); }); @@ -150,7 +149,7 @@ describe('SuggestionPageComponent', () => { scheduler.schedule(() => fixture.detectChanges()); scheduler.flush(); component.ignoreSuggestionAllSelected(); - expect(mockSuggestionsService.notMineMultiple).toHaveBeenCalled(); + expect(mockSuggestionsService.ignoreSuggestionMultiple).toHaveBeenCalled(); expect(mockSuggestionsTargetStateService.dispatchRefreshUserSuggestionsAction).toHaveBeenCalled(); expect(component.updatePage).toHaveBeenCalled(); }); diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts index 4883ed8406..09051441f4 100644 --- a/src/app/suggestions-page/suggestions-page.component.ts +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -28,6 +28,11 @@ import {redirectOn4xx} from '../core/shared/authorized.operators'; templateUrl: './suggestions-page.component.html', 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 { /** @@ -139,15 +144,6 @@ export class SuggestionsPageComponent implements OnInit { this.processing$.next(false); this.suggestionsRD$.next(results); 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']); - // } }); } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 989d2ea9fb..006fbdb5d4 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3084,9 +3084,9 @@ "mydspace.import": "Import", - "mydspace.notification.suggestion": "We found {{count}} publications
in the {{source}} that seems to be related to your profile.
Please review the suggestions", + "notification.suggestion": "We found {{count}} publications
in the {{source}} that seems to be related to your profile.
Please review the suggestions", - "mydspace.notification.suggestion.page": "We found {{count}} {{type}} in the {{source}} that seems to be related to your profile. Please review the suggestions.", + "notification.suggestion.page": "We found {{count}} {{type}} in the {{source}} that seems to be related to your profile. Please review the suggestions.", "nav.browse.header": "All of DSpace", diff --git a/src/config/app-config.interface.ts b/src/config/app-config.interface.ts index f89623e018..6c4b99cb0f 100644 --- a/src/config/app-config.interface.ts +++ b/src/config/app-config.interface.ts @@ -14,7 +14,7 @@ import { AuthConfig } from './auth-config.interfaces'; import { UIServerConfig } from './ui-server-config.interface'; import { MediaViewerConfig } from './media-viewer-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 { ActuatorsConfig } from './actuators.config'; import { InfoConfig } from './info-config.interface'; @@ -23,7 +23,7 @@ import { HomeConfig } from './homepage-config.interface'; import { MarkdownConfig } from './markdown-config.interface'; import { FilterVocabularyConfig } from './filter-vocabulary-config'; import { DiscoverySortConfig } from './discovery-sort.config'; -import {QualityAssuranceConfig} from './quality-assurance.config'; +import { QualityAssuranceConfig } from './quality-assurance.config'; interface AppConfig extends Config { ui: UIServerConfig; diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index 902ab68dc3..be9306c893 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -14,7 +14,7 @@ import { ServerConfig } from './server-config.interface'; import { SubmissionConfig } from './submission-config.interface'; import { ThemeConfig } from './theme.config'; 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 { ActuatorsConfig } from './actuators.config'; import { InfoConfig } from './info-config.interface'; @@ -304,6 +304,8 @@ export class DefaultAppConfig implements AppConfig { // source: 'suggestionSource', // 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 diff --git a/src/config/layout-config.interfaces.ts b/src/config/layout-config.interfaces.ts deleted file mode 100644 index 0b15a06aa9..0000000000 --- a/src/config/layout-config.interfaces.ts +++ /dev/null @@ -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; -} diff --git a/src/config/suggestion-config.interfaces.ts b/src/config/suggestion-config.interfaces.ts new file mode 100644 index 0000000000..e99ffa25f7 --- /dev/null +++ b/src/config/suggestion-config.interfaces.ts @@ -0,0 +1,6 @@ +import { Config } from "./config.interface"; + +export interface SuggestionConfig extends Config { + source: string; + collectionId: string; +} From d4bf3a519a352ef67dce884120a4c3f3efcb2289 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 25 Jan 2024 13:22:17 +0100 Subject: [PATCH 43/94] refactor, clean up --- src/app/core/data/update-data-service.ts | 210 ++++-------------- .../suggestions-data.service.ts | 3 +- .../dso-edit-metadata.component.ts | 2 +- .../themed-dso-edit-metadata.component.ts | 2 +- src/app/menu.resolver.ts | 2 +- .../suggestion-actions.component.ts | 4 +- .../suggestion-targets.effects.ts | 4 +- .../suggestion.service.spec.ts | 8 +- .../suggestions.service.ts | 13 +- .../suggestions-page.component.spec.ts | 3 - .../suggestions-page.component.ts | 12 +- src/config/suggestion-config.interfaces.ts | 2 +- 12 files changed, 72 insertions(+), 193 deletions(-) diff --git a/src/app/core/data/update-data-service.ts b/src/app/core/data/update-data-service.ts index 994cd0ee87..9a31067f88 100644 --- a/src/app/core/data/update-data-service.ts +++ b/src/app/core/data/update-data-service.ts @@ -1,84 +1,85 @@ import { HttpClient } from '@angular/common/http'; import { Store } from '@ngrx/store'; import { Operation } from 'fast-json-patch'; -import { AsyncSubject, combineLatest, from as observableFrom, Observable, of as observableOf } from 'rxjs'; +import { AsyncSubject, from as observableFrom, Observable } from 'rxjs'; import { - distinctUntilChanged, - filter, find, map, mergeMap, - skipWhile, switchMap, take, - takeWhile, - tap, toArray } from 'rxjs/operators'; -import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util'; -import { NotificationOptions } from '../../shared/notifications/models/notification-options.model'; +import { hasValue } from '../../shared/empty.util'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; -import { getClassForType } from '../cache/builders/build-decorators'; 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 { DSpaceSerializer } from '../dspace-rest/dspace.serializer'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { getFirstCompletedRemoteData, getFirstSucceededRemoteData, getRemoteDataPayload } from '../shared/operators'; -import { URLCombiner } from '../url-combiner/url-combiner'; import { ChangeAnalyzer } from './change-analyzer'; import { PaginatedList } from './paginated-list.model'; import { RemoteData } from './remote-data'; import { - CreateRequest, DeleteByIDRequest, - DeleteRequest, - GetRequest, - PatchRequest, - PostRequest, - PutRequest + PostRequest } from './request.models'; import { RequestService } from './request.service'; import { RestRequestMethod } from './rest-request-method'; -import { GenericConstructor } from '../shared/generic-constructor'; import { NoContent } from '../shared/NoContent.model'; import { CacheableObject } from '../cache/cacheable-object.model'; import { CoreState } from '../core-state.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"; +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'; + +/** + * Interface to list the methods used by the injected service in components + */ export interface UpdateDataService { patch(dso: T, operations: Operation[]): Observable>; update(object: T): Observable>; - 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; + * searchData: SearchData; + * createData: CreateData; + * patchData: PatchData; + * putData: PutData; + * deleteData: DeleteData; * * 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 + * 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 abstract class UpdateDataServiceImpl extends BaseDataService implements UpdateDataService, FindAllData, SearchData, CreateData { +export abstract class UpdateDataServiceImpl extends IdentifiableDataService implements FindAllData, SearchData, CreateData, PatchData, PutData, DeleteData { protected abstract store: Store; protected abstract http: HttpClient; - protected abstract comparator: ChangeAnalyzer; private findAllData: FindAllDataImpl; private searchData: SearchDataImpl; - private createData: CreateData; + private createData: CreateDataImpl; + private patchData: PatchDataImpl; + private putData: PutDataImpl; + private deleteData: DeleteDataImpl; constructor( @@ -88,12 +89,16 @@ export abstract class UpdateDataServiceImpl extends B protected objectCache: ObjectCacheService, protected halService: HALEndpointService, protected notificationsService: NotificationsService, + protected comparator: ChangeAnalyzer, 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); } @@ -107,7 +112,7 @@ export abstract class UpdateDataServiceImpl extends B * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved */ public getFindAllHref(options: FindListOptions = {}, linkPath?: string, ...linksToFollow: FollowLinkConfig[]): Observable { - return this.findAllData.getFindAllHref(options, linkPath, ...linksToFollow) + return this.findAllData.getFindAllHref(options, linkPath, ...linksToFollow); } /** @@ -120,7 +125,7 @@ export abstract class UpdateDataServiceImpl extends B * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved */ public getSearchByHref(searchMethod: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig[]): Observable { - return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow) + return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow); } /** @@ -138,27 +143,7 @@ export abstract class UpdateDataServiceImpl extends B * Return an observable that emits object list */ findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { - return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow) - } - - /** - * Create the HREF for a specific object based on its identifier; with possible embed query params based on linksToFollow - * @param endpoint The base endpoint for the type of object - * @param resourceID The identifier for the object - * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved - */ - getIDHref(endpoint, resourceID, ...linksToFollow: FollowLinkConfig[]): string { - return this.buildHrefFromFindOptions(endpoint + '/' + resourceID, {}, [], ...linksToFollow); - } - - /** - * Create an observable for the HREF of a specific object based on its identifier - * @param resourceID The identifier for the object - * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved - */ - getIDHrefObs(resourceID: string, ...linksToFollow: FollowLinkConfig[]): Observable { - return this.getEndpoint().pipe( - map((endpoint: string) => this.getIDHref(endpoint, resourceID, ...linksToFollow))); + return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } /** @@ -177,29 +162,6 @@ export abstract class UpdateDataServiceImpl extends B return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } - /** - * An operator that will call the given function if the incoming RemoteData is stale and - * shouldReRequest is true - * - * @param shouldReRequest Whether or not to call the re-request function if the RemoteData is stale - * @param requestFn The function to call if the RemoteData is stale and shouldReRequest is - * true - */ - protected reRequestStaleRemoteData(shouldReRequest: boolean, requestFn: () => Observable>) { - return (source: Observable>): Observable> => { - if (shouldReRequest === true) { - return source.pipe( - tap((remoteData: RemoteData) => { - if (hasValue(remoteData) && remoteData.isStale) { - requestFn(); - } - }) - ); - } else { - return source; - } - }; - } /** * Make a new FindListRequest with given search method @@ -216,7 +178,7 @@ export abstract class UpdateDataServiceImpl extends B * Return an observable that emits response from the server */ searchBy(searchMethod: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { - return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow) + return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } /** @@ -225,30 +187,11 @@ export abstract class UpdateDataServiceImpl extends B * @param {Operation[]} operations The patch operations to be performed */ patch(object: T, operations: Operation[]): Observable> { - const requestId = this.requestService.generateRequestId(); - - const hrefObs = this.halService.getEndpoint(this.linkPath).pipe( - map((endpoint: string) => this.getIDHref(endpoint, object.uuid))); - - hrefObs.pipe( - find((href: string) => hasValue(href)), - ).subscribe((href: string) => { - const request = new PatchRequest(requestId, href, operations); - if (hasValue(this.responseMsToLive)) { - request.responseMsToLive = this.responseMsToLive; - } - this.requestService.send(request); - }); - - return this.rdbService.buildFromRequestUUID(requestId); + return this.patchData.patch(object, operations); } createPatchFromCache(object: T): Observable { - const oldVersion$ = this.findByHref(object._links.self.href, true, false); - return oldVersion$.pipe( - getFirstSucceededRemoteData(), - getRemoteDataPayload(), - map((oldVersion: T) => this.comparator.diff(oldVersion, object))); + return this.patchData.createPatchFromCache(object); } /** @@ -257,17 +200,7 @@ export abstract class UpdateDataServiceImpl extends B * @param object The object to send a put request for. */ put(object: T): Observable> { - const requestId = this.requestService.generateRequestId(); - const serializedObject = new DSpaceSerializer(object.constructor as GenericConstructor<{}>).serialize(object); - const request = new PutRequest(requestId, object._links.self.href, serializedObject); - - if (hasValue(this.responseMsToLive)) { - request.responseMsToLive = this.responseMsToLive; - } - - this.requestService.send(request); - - return this.rdbService.buildFromRequestUUID(requestId); + return this.putData.put(object); } /** @@ -276,16 +209,7 @@ export abstract class UpdateDataServiceImpl extends B * @param {DSpaceObject} object The given object */ update(object: T): Observable> { - return this.createPatchFromCache(object) - .pipe( - mergeMap((operations: Operation[]) => { - if (isNotEmpty(operations)) { - this.objectCache.addPatch(object._links.self.href, operations); - } - return this.findByHref(object._links.self.href, true, true); - } - ) - ); + return this.patchData.update(object); } /** @@ -298,7 +222,7 @@ export abstract class UpdateDataServiceImpl extends B * Array with additional params to combine with query string */ create(object: T, ...params: RequestParam[]): Observable> { - return this.createData.create(object, ...params) + return this.createData.create(object, ...params); } /** @@ -390,9 +314,7 @@ export abstract class UpdateDataServiceImpl extends B * errorMessage, timeCompleted, etc */ delete(objectId: string, copyVirtualMetadata?: string[]): Observable> { - return this.getIDHrefObs(objectId).pipe( - switchMap((href: string) => this.deleteByHref(href, copyVirtualMetadata)) - ); + return this.deleteData.delete(objectId, copyVirtualMetadata); } /** @@ -405,43 +327,7 @@ export abstract class UpdateDataServiceImpl extends B * Only emits once all request related to the DSO has been invalidated. */ deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable> { - const requestId = this.requestService.generateRequestId(); - - if (copyVirtualMetadata) { - copyVirtualMetadata.forEach((id) => - href += (href.includes('?') ? '&' : '?') - + 'copyVirtualMetadata=' - + id - ); - } - - const request = new DeleteRequest(requestId, href); - if (hasValue(this.responseMsToLive)) { - request.responseMsToLive = this.responseMsToLive; - } - this.requestService.send(request); - - const response$ = this.rdbService.buildFromRequestUUID(requestId); - - const invalidated$ = new AsyncSubject(); - response$.pipe( - getFirstCompletedRemoteData(), - switchMap((rd: RemoteData) => { - if (rd.hasSucceeded) { - return this.invalidateByHref(href); - } else { - return [true]; - } - }) - ).subscribe(() => { - invalidated$.next(true); - invalidated$.complete(); - }); - - return combineLatest([response$, invalidated$]).pipe( - filter(([_, invalidated]) => invalidated), - map(([response, _]) => response), - ); + return this.deleteData.deleteByHref(href, copyVirtualMetadata); } /** @@ -449,6 +335,6 @@ export abstract class UpdateDataServiceImpl extends B * @param method The RestRequestMethod for which de server sync buffer should be committed */ commitUpdates(method?: RestRequestMethod) { - this.requestService.commit(method); + this.patchData.commitUpdates(method); } } diff --git a/src/app/core/suggestion-notifications/suggestions-data.service.ts b/src/app/core/suggestion-notifications/suggestions-data.service.ts index 945cc8b083..2078fdb75e 100644 --- a/src/app/core/suggestion-notifications/suggestions-data.service.ts +++ b/src/app/core/suggestion-notifications/suggestions-data.service.ts @@ -58,7 +58,7 @@ export class SuggestionDataServiceImpl extends UpdateDataServiceImpl protected comparator: ChangeAnalyzer, protected responseMsToLive: number, ) { - super('suggestions', requestService, rdbService, objectCache, halService, notificationsService ,responseMsToLive); + super('suggestions', requestService, rdbService, objectCache, halService, notificationsService, comparator ,responseMsToLive); } } @@ -99,7 +99,6 @@ export class SuggestionsDataService { * @param {DefaultChangeAnalyzer} comparatorSuggestions * @param {DefaultChangeAnalyzer} comparatorSources * @param {DefaultChangeAnalyzer} comparatorTargets - * @param responseMsToLive */ constructor( protected requestService: RequestService, diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts index ad3d6680a1..22d23e8402 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts @@ -21,7 +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"; +import { UpdateDataService } from '../../core/data/update-data-service'; @Component({ selector: 'ds-dso-edit-metadata', diff --git a/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts b/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts index 51eec3ac23..5d7ccdd87d 100644 --- a/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts +++ b/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts @@ -2,7 +2,7 @@ import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { DsoEditMetadataComponent } from './dso-edit-metadata.component'; import { Component, Input } from '@angular/core'; 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({ selector: 'ds-themed-dso-edit-metadata', diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts index f2a7e2dbe1..fc6eb00195 100644 --- a/src/app/menu.resolver.ts +++ b/src/app/menu.resolver.ts @@ -541,7 +541,7 @@ export class MenuResolver implements Resolve { { id: 'notifications', active: false, - visible: authorized && true, + visible: authorized && canSeeQA, model: { type: MenuItemType.TEXT, text: 'menu.section.notifications' diff --git a/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.ts b/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.ts index 5a4c6ebe3f..f6e2452738 100644 --- a/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.ts +++ b/src/app/suggestion-notifications/suggestion-actions/suggestion-actions.component.ts @@ -86,10 +86,10 @@ export class SuggestionActionsComponent { } ignoreSuggestionLabel(): string { - return this.isBulk ? 'reciter.suggestion.ignoreSuggestion.bulk' : 'reciter.suggestion.ignoreSuggestion' ; + 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'; } } diff --git a/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts index 63964b7721..7a431bf4c2 100644 --- a/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts +++ b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts @@ -55,7 +55,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 +64,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) => { diff --git a/src/app/suggestion-notifications/suggestion.service.spec.ts b/src/app/suggestion-notifications/suggestion.service.spec.ts index e6ca83d5fe..ad2cb8fcc4 100644 --- a/src/app/suggestion-notifications/suggestion.service.spec.ts +++ b/src/app/suggestion-notifications/suggestion.service.spec.ts @@ -169,18 +169,18 @@ describe('SuggestionsService test', () => { it('should get suggestion interpolation', () => { const result = service.getNotificationSuggestionInterpolation(suggestionTarget as SuggestionTarget); expect(result.count).toEqual(suggestionTarget.total); - expect(result.source).toEqual('reciter.suggestion.source.' + suggestionTarget.source); - expect(result.type).toEqual('reciter.suggestion.type.' + suggestionTarget.source); + 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('reciter.suggestion.type.source'); + expect(service.translateSuggestionType('source')).toEqual('suggestion.type.source'); }); it('should translate suggestion source', () => { - expect(service.translateSuggestionSource('source')).toEqual('reciter.suggestion.source.source'); + expect(service.translateSuggestionSource('source')).toEqual('suggestion.source.source'); }); it('should resolve collection id', () => { diff --git a/src/app/suggestion-notifications/suggestions.service.ts b/src/app/suggestion-notifications/suggestions.service.ts index 47d4124a47..599215d529 100644 --- a/src/app/suggestion-notifications/suggestions.service.ts +++ b/src/app/suggestion-notifications/suggestions.service.ts @@ -51,17 +51,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 ) { @@ -194,7 +191,7 @@ export class SuggestionsService { return workspaceitemService.importExternalSourceEntry(suggestion.externalSourceUri, resolvedCollectionId) .pipe( getFirstSucceededRemoteDataPayload(), - catchError((error) => of(null)) + catchError(() => of(null)) ); } @@ -204,7 +201,7 @@ export class SuggestionsService { */ public ignoreSuggestion(suggestionId): Observable> { return this.deleteReviewedSuggestion(suggestionId).pipe( - catchError((error) => of(null)) + catchError(() => of(null)) ); } @@ -268,11 +265,11 @@ export class SuggestionsService { } 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; } /** diff --git a/src/app/suggestions-page/suggestions-page.component.spec.ts b/src/app/suggestions-page/suggestions-page.component.spec.ts index 2f6cfb7278..6abd28386f 100644 --- a/src/app/suggestions-page/suggestions-page.component.spec.ts +++ b/src/app/suggestions-page/suggestions-page.component.spec.ts @@ -13,8 +13,6 @@ import { } from '../suggestion-notifications/suggestion-list-element/suggestion-list-element.component'; import { SuggestionsService } from '../suggestion-notifications/suggestions.service'; import { getMockSuggestionNotificationsStateService, getMockSuggestionsService } from '../shared/mocks/suggestion.mock'; -import { buildPaginatedList, PaginatedList } from '../core/data/paginated-list.model'; -import { Suggestion } from '../core/suggestion-notifications/models/suggestion.model'; import { mockSuggestionPublicationOne, mockSuggestionPublicationTwo } from '../shared/mocks/publication-claim.mock'; import { SuggestionEvidencesComponent } from '../suggestion-notifications/suggestion-list-element/suggestion-evidences/suggestion-evidences.component'; import { ObjectKeysPipe } from '../shared/utils/object-keys-pipe'; @@ -29,7 +27,6 @@ import { getMockTranslateService } from '../shared/mocks/translate.service.mock' import { SuggestionTargetsStateService } from '../suggestion-notifications/suggestion-targets/suggestion-targets.state.service'; import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service'; import { createSuccessfulRemoteDataObject } from '../shared/remote-data.utils'; -import { PageInfo } from '../core/shared/page-info.model'; import { TestScheduler } from 'rxjs/testing'; import { getTestScheduler } from 'jasmine-marbles'; import { PaginationServiceStub } from '../shared/testing/pagination-service.stub'; diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts index 09051441f4..48245b60b3 100644 --- a/src/app/suggestions-page/suggestions-page.component.ts +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -152,7 +152,7 @@ export class SuggestionsPageComponent implements OnInit { * @suggestionId */ ignoreSuggestion(suggestionId) { - this.suggestionService.ignoreSuggestion(suggestionId).subscribe((res) => { + this.suggestionService.ignoreSuggestion(suggestionId).subscribe(() => { this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); this.updatePage(); }); @@ -172,12 +172,12 @@ export class SuggestionsPageComponent implements OnInit { this.selectedSuggestions = {}; if (results.success > 0) { this.notificationService.success( - this.translateService.get('reciter.suggestion.notMine.bulk.success', + this.translateService.get('suggestion.notMine.bulk.success', {count: results.success})); } if (results.fails > 0) { this.notificationService.error( - this.translateService.get('reciter.suggestion.notMine.bulk.error', + this.translateService.get('suggestion.notMine.bulk.error', {count: results.fails})); } }); @@ -190,7 +190,7 @@ export class SuggestionsPageComponent implements OnInit { approveAndImport(event: SuggestionApproveAndImport) { this.suggestionService.approveAndImport(this.workspaceItemService, event.suggestion, event.collectionId) .subscribe((workspaceitem: WorkspaceItem) => { - const content = this.translateService.instant('reciter.suggestion.approveAndImport.success', { workspaceItemId: workspaceitem.id }); + const content = this.translateService.instant('suggestion.approveAndImport.success', { workspaceItemId: workspaceitem.id }); this.notificationService.success('', content, {timeOut:0}, true); this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); this.updatePage(); @@ -212,12 +212,12 @@ export class SuggestionsPageComponent implements OnInit { this.selectedSuggestions = {}; if (results.success > 0) { this.notificationService.success( - this.translateService.get('reciter.suggestion.approveAndImport.bulk.success', + this.translateService.get('suggestion.approveAndImport.bulk.success', {count: results.success})); } if (results.fails > 0) { this.notificationService.error( - this.translateService.get('reciter.suggestion.approveAndImport.bulk.error', + this.translateService.get('suggestion.approveAndImport.bulk.error', {count: results.fails})); } }); diff --git a/src/config/suggestion-config.interfaces.ts b/src/config/suggestion-config.interfaces.ts index e99ffa25f7..afd3a38c58 100644 --- a/src/config/suggestion-config.interfaces.ts +++ b/src/config/suggestion-config.interfaces.ts @@ -1,4 +1,4 @@ -import { Config } from "./config.interface"; +import { Config } from './config.interface'; export interface SuggestionConfig extends Config { source: string; From 4cca9015a1fab16d78ad689c5024b7782a2af7ad Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 25 Jan 2024 13:23:01 +0100 Subject: [PATCH 44/94] remove unused imports --- .../suggestion-targets/suggestion-targets.effects.ts | 1 - src/app/suggestion-notifications/suggestions.service.ts | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts index 7a431bf4c2..1358e6f8b5 100644 --- a/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts +++ b/src/app/suggestion-notifications/suggestion-targets/suggestion-targets.effects.ts @@ -9,7 +9,6 @@ import { of } from 'rxjs'; import { AddTargetAction, AddUserSuggestionsAction, - RefreshUserSuggestionsAction, RetrieveAllTargetsErrorAction, RetrieveTargetsBySourceAction, SuggestionTargetActionTypes, diff --git a/src/app/suggestion-notifications/suggestions.service.ts b/src/app/suggestion-notifications/suggestions.service.ts index 599215d529..bf21b5ae2f 100644 --- a/src/app/suggestion-notifications/suggestions.service.ts +++ b/src/app/suggestion-notifications/suggestions.service.ts @@ -7,7 +7,6 @@ import { SortDirection, SortOptions } from '../core/cache/models/sort-options.mo 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 { AuthService } from '../core/auth/auth.service'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { ResearcherProfile } from '../core/profile/model/researcher-profile.model'; import { @@ -25,9 +24,6 @@ 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 { - SuggestionSourceDataService -} from '../core/suggestion-notifications/source/suggestion-source-data.service'; import { SuggestionTargetDataService } from '../core/suggestion-notifications/target/suggestion-target-data.service'; From e77343604e2288c0310c7c1565fce47e781cf11a Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 25 Jan 2024 13:50:23 +0100 Subject: [PATCH 45/94] refactor and add tests --- src/app/core/data/update-data-service.spec.ts | 144 ++++++++++++++++++ src/app/core/data/update-data-service.ts | 25 +-- .../suggestion.service.spec.ts | 15 +- 3 files changed, 147 insertions(+), 37 deletions(-) create mode 100644 src/app/core/data/update-data-service.spec.ts diff --git a/src/app/core/data/update-data-service.spec.ts b/src/app/core/data/update-data-service.spec.ts new file mode 100644 index 0000000000..6912527b91 --- /dev/null +++ b/src/app/core/data/update-data-service.spec.ts @@ -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; + 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); + }); + +}); diff --git a/src/app/core/data/update-data-service.ts b/src/app/core/data/update-data-service.ts index 9a31067f88..715b2ee413 100644 --- a/src/app/core/data/update-data-service.ts +++ b/src/app/core/data/update-data-service.ts @@ -1,5 +1,3 @@ -import { HttpClient } from '@angular/common/http'; -import { Store } from '@ngrx/store'; import { Operation } from 'fast-json-patch'; import { AsyncSubject, from as observableFrom, Observable } from 'rxjs'; import { @@ -29,7 +27,6 @@ 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 { CoreState } from '../core-state.model'; import { FindListOptions } from './find-list-options.model'; import { FindAllData, FindAllDataImpl } from './base/find-all-data'; import { SearchData, SearchDataImpl } from './base/search-data'; @@ -70,10 +67,7 @@ export interface UpdateDataService { * invalidateByHref - invalidate the href making all requests as stale */ -export abstract class UpdateDataServiceImpl extends IdentifiableDataService implements FindAllData, SearchData, CreateData, PatchData, PutData, DeleteData { - protected abstract store: Store; - protected abstract http: HttpClient; - +export class UpdateDataServiceImpl extends IdentifiableDataService implements FindAllData, SearchData, CreateData, PatchData, PutData, DeleteData { private findAllData: FindAllDataImpl; private searchData: SearchDataImpl; private createData: CreateDataImpl; @@ -146,23 +140,6 @@ export abstract class UpdateDataServiceImpl extends I return this.findAllData.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); } - /** - * Returns an observable of {@link RemoteData} of an object, based on its ID, with a list of - * {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object - * @param id ID of object we want to retrieve - * @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 - */ - findById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable> { - const href$ = this.getIDHrefObs(encodeURIComponent(id), ...linksToFollow); - return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow); - } - - /** * Make a new FindListRequest with given search method * diff --git a/src/app/suggestion-notifications/suggestion.service.spec.ts b/src/app/suggestion-notifications/suggestion.service.spec.ts index ad2cb8fcc4..acb0bb3be2 100644 --- a/src/app/suggestion-notifications/suggestion.service.spec.ts +++ b/src/app/suggestion-notifications/suggestion.service.spec.ts @@ -1,12 +1,10 @@ import { SuggestionsService } from './suggestions.service'; -import { AuthService } from '../core/auth/auth.service'; import { ResearcherProfileDataService } from '../core/profile/researcher-profile-data.service'; import { SuggestionsDataService } from '../core/suggestion-notifications/suggestions-data.service'; -import { - SuggestionSourceDataService -} from '../core/suggestion-notifications/source/suggestion-source-data.service'; + + import { SuggestionTargetDataService } from '../core/suggestion-notifications/target/suggestion-target-data.service'; @@ -29,10 +27,8 @@ import { describe('SuggestionsService test', () => { let scheduler: TestScheduler; let service: SuggestionsService; - let authService: AuthService; let researcherProfileService: ResearcherProfileDataService; let suggestionsDataService: SuggestionsDataService; - let suggestionSourceDataService: SuggestionSourceDataService; let suggestionTargetDataService: SuggestionTargetDataService; let translateService: any = { instant: (str) => str, @@ -53,10 +49,8 @@ describe('SuggestionsService test', () => { function initTestService() { return new SuggestionsService( - authService, researcherProfileService, suggestionsDataService, - suggestionSourceDataService, suggestionTargetDataService, translateService ); @@ -65,11 +59,6 @@ describe('SuggestionsService test', () => { beforeEach(() => { scheduler = getTestScheduler(); - - suggestionSourceDataService = jasmine.createSpyObj('suggestionSourcesDataService', { - getSources: observableOf(null), - }); - researcherProfileService = jasmine.createSpyObj('researcherProfileService', { findById: createSuccessfulRemoteDataObject$(mockResercherProfile as ResearcherProfile), findRelatedItemId: observableOf('1234'), From 604d35590221847519a69f1ca646cc53cfe33e06 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 25 Jan 2024 13:59:17 +0100 Subject: [PATCH 46/94] update labels --- .../suggestion-evidences.component.html | 6 +++--- .../suggestion-list-element.component.html | 2 +- src/app/suggestions-page/suggestions-page.component.html | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/app/suggestion-notifications/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.html b/src/app/suggestion-notifications/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.html index 5ad4f0a978..e23c244eb4 100644 --- a/src/app/suggestion-notifications/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.html +++ b/src/app/suggestion-notifications/suggestion-list-element/suggestion-evidences/suggestion-evidences.component.html @@ -3,9 +3,9 @@
{{'reciter.suggestion.table.name' | translate}}{{'reciter.suggestion.table.actions' | translate}}{{'suggestion.table.name' | translate}}{{'suggestion.table.actions' | translate}}
- - - + + + diff --git a/src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.html b/src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.html index 3d95aed8a3..ef27876f2c 100644 --- a/src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.html +++ b/src/app/suggestion-notifications/suggestion-list-element/suggestion-list-element.component.html @@ -11,7 +11,7 @@
-
{{'reciter.suggestion.totalScore' | translate}}
+
{{'suggestion.totalScore' | translate}}
{{ object.score }}
diff --git a/src/app/suggestions-page/suggestions-page.component.html b/src/app/suggestions-page/suggestions-page.component.html index f8b14f6728..89c676ab71 100644 --- a/src/app/suggestions-page/suggestions-page.component.html +++ b/src/app/suggestions-page/suggestions-page.component.html @@ -6,9 +6,9 @@

{{ translateSuggestionType() | translate }} - {{'reciter.suggestion.suggestionFor' | translate}} + {{'suggestion.suggestionFor' | translate}} {{researcherName}} - {{'reciter.suggestion.from.source' | translate}} {{ translateSuggestionSource() | translate }} + {{'suggestion.from.source' | translate}} {{ translateSuggestionSource() | translate }}

@@ -33,7 +33,7 @@
  • Date: Thu, 25 Jan 2024 16:42:36 +0100 Subject: [PATCH 47/94] added german translations of feedback page translation keys --- src/assets/i18n/de.json5 | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index c185a13432..1f93726e05 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -2193,6 +2193,44 @@ // "info.privacy.title": "Privacy Statement", "info.privacy.title": "Datenschutzerklärung", + // "info.feedback.breadcrumbs": "Feedback", + "info.feedback.breadcrumbs": "Feedback", + + // "info.feedback.head": "Feedback", + "info.feedback.head": "Feedback", + + // "info.feedback.title": "Feedback", + "info.feedback.title": "Feedback", + + // "info.feedback.info": "Thanks for sharing your feedback about the DSpace system. Your comments are appreciated!", + "info.feedback.info": "Vielen Dank für Ihre Rückmeldung. Wir werden uns in Kürze bei Ihnen melden, sofern Sie eine Rückmeldung gewünscht haben.", + + // "info.feedback.email_help": "This address will be used to follow up on your feedback.", + "info.feedback.email_help": "Sollten wir Rückfragen zu Ihrem Feedback haben, dann kontaktieren wir Sie unter dieser E-Mailadresse.", + + // "info.feedback.send": "Send Feedback", + "info.feedback.send": "Feedback senden", + + // "info.feedback.comments": "Comments", + "info.feedback.comments": "Ihr Kommentar", + + // "info.feedback.email-label": "Your Email", + "info.feedback.email-label": "Ihre E-Mailadresse", + + // "info.feedback.create.success": "Feedback Sent Successfully!", + "info.feedback.create.success": "Ihr Feedback wurde erfolgreich versendet.", + + // "info.feedback.error.email.required": "A valid email address is required", + "info.feedback.error.email.required": "Bitte geben Sie eine gültige E-Mailadresse ein!", + + // "info.feedback.error.message.required": "A comment is required", + "info.feedback.error.message.required": "Bitte geben Sie einen Kommentar ein!", + + // "info.feedback.page-label": "Page", + "info.feedback.page-label": "URL der Seite", + + // "info.feedback.page_help": "The page related to your feedback", + "info.feedback.page_help": "Dies ist die URL der Seite, auf die sich Ihr Feedback bezieht.", // "item.alerts.private": "This item is private", From e03b492dbd2a60b3512361c08f343c73392561cc Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Thu, 25 Jan 2024 16:51:25 +0100 Subject: [PATCH 48/94] minor improvement of German translation --- src/assets/i18n/de.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index 1f93726e05..6c11866891 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -2203,7 +2203,7 @@ "info.feedback.title": "Feedback", // "info.feedback.info": "Thanks for sharing your feedback about the DSpace system. Your comments are appreciated!", - "info.feedback.info": "Vielen Dank für Ihre Rückmeldung. Wir werden uns in Kürze bei Ihnen melden, sofern Sie eine Rückmeldung gewünscht haben.", + "info.feedback.info": "Wir freuen uns auf Ihr Feedback. Sofern Sie eine Rückmeldung wünschen, werden wir uns in Kürze bei Ihnen melden.", // "info.feedback.email_help": "This address will be used to follow up on your feedback.", "info.feedback.email_help": "Sollten wir Rückfragen zu Ihrem Feedback haben, dann kontaktieren wir Sie unter dieser E-Mailadresse.", @@ -2218,7 +2218,7 @@ "info.feedback.email-label": "Ihre E-Mailadresse", // "info.feedback.create.success": "Feedback Sent Successfully!", - "info.feedback.create.success": "Ihr Feedback wurde erfolgreich versendet.", + "info.feedback.create.success": "Vielen Dank für Ihr Feedback! Ihr Feedback wurde erfolgreich versendet.", // "info.feedback.error.email.required": "A valid email address is required", "info.feedback.error.email.required": "Bitte geben Sie eine gültige E-Mailadresse ein!", From 80174817fd3de270cdb1d9c090c29db371b4ba87 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 25 Jan 2024 19:32:12 +0100 Subject: [PATCH 49/94] add breadcrumbs, adapt tests, fix issue on page loading --- ...lication-claim-breadcrumb.resolver.spec.ts | 31 +++++++++++++ .../publication-claim-breadcrumb.resolver.ts | 24 ++++++++++ ...blication-claim-breadcrumb.service.spec.ts | 46 +++++++++++++++++++ .../publication-claim-breadcrumb.service.ts | 42 +++++++++++++++++ .../cache/builders/build-decorators.spec.ts | 8 +++- .../publication-claim.component.html | 2 +- .../publication-claim.component.ts | 9 +--- .../suggestions-page-routing.module.ts | 6 +-- 8 files changed, 155 insertions(+), 13 deletions(-) create mode 100644 src/app/core/breadcrumbs/publication-claim-breadcrumb.resolver.spec.ts create mode 100644 src/app/core/breadcrumbs/publication-claim-breadcrumb.resolver.ts create mode 100644 src/app/core/breadcrumbs/publication-claim-breadcrumb.service.spec.ts create mode 100644 src/app/core/breadcrumbs/publication-claim-breadcrumb.service.ts diff --git a/src/app/core/breadcrumbs/publication-claim-breadcrumb.resolver.spec.ts b/src/app/core/breadcrumbs/publication-claim-breadcrumb.resolver.spec.ts new file mode 100644 index 0000000000..b6f4142469 --- /dev/null +++ b/src/app/core/breadcrumbs/publication-claim-breadcrumb.resolver.spec.ts @@ -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); + }); + }); +}); diff --git a/src/app/core/breadcrumbs/publication-claim-breadcrumb.resolver.ts b/src/app/core/breadcrumbs/publication-claim-breadcrumb.resolver.ts new file mode 100644 index 0000000000..713500d6a7 --- /dev/null +++ b/src/app/core/breadcrumbs/publication-claim-breadcrumb.resolver.ts @@ -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> { + 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 { + const targetId = route.paramMap.get('targetId').split(':')[1]; + return { provider: this.breadcrumbService, key: targetId }; + } +} diff --git a/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.spec.ts b/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.spec.ts new file mode 100644 index 0000000000..61fb1d82bf --- /dev/null +++ b/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.spec.ts @@ -0,0 +1,46 @@ +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'; + +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 exampleKey; + + const ADMIN_PUBLICATION_CLAIMS_PATH = 'admin/notifications/publication-claim'; + const ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY = 'admin.notifications.publicationclaim.page.title'; + + function init() { + exampleKey = '6bee076d-4f2a-4555-a475-04a267769b2a'; + } + + beforeEach(waitForAsync(() => { + init(); + TestBed.configureTestingModule({}).compileComponents(); + })); + + beforeEach(() => { + service = new PublicationClaimBreadcrumbService(dataService,dsoNameService,translateService); + }); + + 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)] + }); + }); + }); +}); diff --git a/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.ts b/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.ts new file mode 100644 index 0000000000..64284ee510 --- /dev/null +++ b/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.ts @@ -0,0 +1,42 @@ +import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model'; +import { BreadcrumbsProviderService } from './breadcrumbsProviderService'; +import { 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'; + + + +/** + * Service to calculate Publication claims breadcrumbs + */ +@Injectable({ + providedIn: 'root' +}) +export class PublicationClaimBreadcrumbService implements BreadcrumbsProviderService { + 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) { + } + + + /** + * Method to calculate the breadcrumbs + * @param key The key used to resolve the breadcrumb + */ + getBreadcrumbs(key: string): Observable { + return this.dataService.findById(key).pipe( + getFirstCompletedRemoteData(), + map((item) => { + return [new Breadcrumb(this.tranlsateService.instant(this.ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY), this.ADMIN_PUBLICATION_CLAIMS_PATH), + new Breadcrumb(this.dsoNameService.getName(item.payload), undefined)]; + }) + ); + } +} diff --git a/src/app/core/cache/builders/build-decorators.spec.ts b/src/app/core/cache/builders/build-decorators.spec.ts index 90d4b4fef8..e4baaa4a5a 100644 --- a/src/app/core/cache/builders/build-decorators.spec.ts +++ b/src/app/core/cache/builders/build-decorators.spec.ts @@ -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 { dataService, getLinkDefinition, link } from './build-decorators'; +import { dataService, getDataServiceFor, getLinkDefinition, link } from './build-decorators'; class TestHALResource implements HALResource { _links: { @@ -51,6 +51,12 @@ describe('build decorators', () => { 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); + }); }); }); }); diff --git a/src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.html b/src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.html index 45068cd12c..f2ab1e5b65 100644 --- a/src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.html +++ b/src/app/suggestion-notifications/suggestion-targets/publication-claim/publication-claim.component.html @@ -31,7 +31,7 @@
  • {{'reciter.suggestion.evidence.score' | translate}}{{'reciter.suggestion.evidence.type' | translate}}{{'reciter.suggestion.evidence.notes' | translate}}{{'suggestion.evidence.score' | translate}}{{'suggestion.evidence.type' | translate}}{{'suggestion.evidence.notes' | translate}}
    - + ({{ getSelectedSuggestionsCount() }})
    +
    {{ 'suggestion.count.missing' | translate }}
    diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts index 48245b60b3..d986366f1c 100644 --- a/src/app/suggestions-page/suggestions-page.component.ts +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -172,12 +172,12 @@ export class SuggestionsPageComponent implements OnInit { this.selectedSuggestions = {}; if (results.success > 0) { this.notificationService.success( - this.translateService.get('suggestion.notMine.bulk.success', + this.translateService.get('suggestion.ignoreSuggestion.bulk.success', {count: results.success})); } if (results.fails > 0) { this.notificationService.error( - this.translateService.get('suggestion.notMine.bulk.error', + this.translateService.get('suggestion.ignoreSuggestion.bulk.error', {count: results.fails})); } }); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 006fbdb5d4..fd4e50610c 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3596,7 +3596,7 @@ "suggestion.approveAndImport.bulk": "Approve & import Selected", - ".suggestion.approveAndImport.bulk.success": "{{ count }} suggestions have been imported successfully ", + "suggestion.approveAndImport.bulk.success": "{{ count }} suggestions have been imported successfully ", "suggestion.approveAndImport.bulk.error": "{{ count }} suggestions haven't been imported due to unexpected server errors", @@ -3620,6 +3620,8 @@ "suggestion.from.source": "from the ", + "suggestion.count.missing": "You have no publication claims left", + "suggestion.totalScore": "Total Score", "suggestion.type.openaire": "OpenAIRE", diff --git a/src/config/default-app-config.ts b/src/config/default-app-config.ts index be9306c893..3b3eb00ddb 100644 --- a/src/config/default-app-config.ts +++ b/src/config/default-app-config.ts @@ -304,8 +304,9 @@ export class DefaultAppConfig implements AppConfig { // source: 'suggestionSource', // 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 + // This is used as a default fallback in case there aren't collections where to import the suggestion + // If not mapped the user will be allowed to import the suggestions only in the provided options, shown clicking the button "Approve and import" + // If not mapped and no options available for import the user won't be able to import the suggestions. ]; // Theme Config From b8555f53c3125b6e9a61da116a41e43b9896a471 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Fri, 26 Jan 2024 11:53:56 +0100 Subject: [PATCH 51/94] add commented example --- config/config.example.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/config/config.example.yml b/config/config.example.yml index 69a9ffd320..4b9c1a27ac 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -75,7 +75,7 @@ cache: anonymousCache: # Maximum number of pages to cache. Default is zero (0) which means anonymous user cache is disabled. # As all pages are cached in server memory, increasing this value will increase memory needs. - # Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory. + # Individual cached pages are usually small (<100KB), so a value of max=1000 would only require ~100MB of memory. max: 0 # Amount of time after which cached pages are considered stale (in ms). After becoming stale, the cached # copy is automatically refreshed on the next request. @@ -382,7 +382,13 @@ vocabularies: vocabulary: 'srsc' enabled: true -# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. +# Default collection/community sorting order at Advanced search, Create/update community and collection when there are not a query. comcolSelectionSort: sortField: 'dc.title' sortDirection: 'ASC' + +# Example of fallback collection for suggestions import +# suggestion: + # - collectionId: 8f7df5ca-f9c2-47a4-81ec-8a6393d6e5af + # source: "openaire" + From 6497a156aa1888da2de48dae235e67ae93d49fe9 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 28 Dec 2023 20:42:21 +0100 Subject: [PATCH 52/94] Destroy dynamically generated components in onDestroy & replace deprecated createComponent --- ...in-search-result-grid-element.component.ts | 8 ++-- ...t-admin-workflow-grid-element.component.ts | 37 ++++++++-------- ...t-admin-workflow-grid-element.component.ts | 42 ++++++++++++------- src/app/shared/context-help.directive.ts | 21 +++++----- ...anced-workflow-actions-loader.component.ts | 24 ++++++++--- 5 files changed, 78 insertions(+), 54 deletions(-) diff --git a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts index 481145ab8b..36707187dd 100644 --- a/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts +++ b/src/app/admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.ts @@ -61,10 +61,10 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE ], }, ); - (this.compRef.instance as any).object = this.object; - (this.compRef.instance as any).index = this.index; - (this.compRef.instance as any).linkType = this.linkType; - (this.compRef.instance as any).listID = this.listID; + this.compRef.setInput('object',this.object); + this.compRef.setInput('index', this.index); + this.compRef.setInput('linkType', this.linkType); + this.compRef.setInput('listID', this.listID); } ngOnDestroy(): void { diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts index 0c4527070d..4c25a9f998 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workflow-item/workflow-item-search-result-admin-workflow-grid-element.component.ts @@ -77,28 +77,27 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S this.dso = this.linkService.resolveLink(this.dso, followLink('item')); this.item$ = (this.dso.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload()); this.item$.pipe(take(1)).subscribe((item: Item) => { - const component: GenericConstructor = this.getComponent(item); + const component: GenericConstructor = this.getComponent(item); - const viewContainerRef = this.listableObjectDirective.viewContainerRef; - viewContainerRef.clear(); + const viewContainerRef = this.listableObjectDirective.viewContainerRef; + viewContainerRef.clear(); - this.compRef = viewContainerRef.createComponent( - component, { - index: 0, - injector: undefined, - projectableNodes: [ - [this.badges.nativeElement], - [this.buttons.nativeElement], - ], - }, - ); - (this.compRef.instance as any).object = item; - (this.compRef.instance as any).index = this.index; - (this.compRef.instance as any).linkType = this.linkType; - (this.compRef.instance as any).listID = this.listID; + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + injector: undefined, + projectableNodes: [ + [this.badges.nativeElement], + [this.buttons.nativeElement], + ], + }, + ); + this.compRef.setInput('object', item); + this.compRef.setInput('index', this.index); + this.compRef.setInput('linkType', this.linkType); + this.compRef.setInput('listID', this.listID); this.compRef.changeDetectorRef.detectChanges(); - } - ); + }); } ngOnDestroy(): void { diff --git a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts index d6f39e79fe..9404b02a2a 100644 --- a/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts +++ b/src/app/admin/admin-workflow-page/admin-workflow-search-results/admin-workflow-search-result-grid-element/workspace-item/workspace-item-search-result-admin-workflow-grid-element.component.ts @@ -1,4 +1,4 @@ -import { Component, ComponentFactoryResolver, ElementRef, ViewChild, OnInit } from '@angular/core'; +import { Component, ElementRef, ViewChild, OnInit, OnDestroy, ComponentRef } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; import { map, mergeMap, take, tap } from 'rxjs/operators'; @@ -37,6 +37,7 @@ import { SupervisionOrder } from '../../../../../core/supervision-order/models/s import { PaginatedList } from '../../../../../core/data/paginated-list.model'; import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service'; import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service'; +import { hasValue } from '../../../../../shared/empty.util'; @listableObjectComponent(WorkspaceItemSearchResult, ViewMode.GridElement, Context.AdminWorkflowSearch) @Component({ @@ -47,7 +48,7 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service /** * The component for displaying a grid element for an workflow item on the admin workflow search page */ -export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent implements OnInit { +export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent implements OnDestroy, OnInit { /** * The item linked to the workspace item @@ -79,9 +80,13 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends */ @ViewChild('buttons', { static: true }) buttons: ElementRef; + /** + * The reference to the dynamic component + */ + protected compRef: ComponentRef; + constructor( public dsoNameService: DSONameService, - private componentFactoryResolver: ComponentFactoryResolver, private linkService: LinkService, protected truncatableService: TruncatableService, private themeService: ThemeService, @@ -100,24 +105,24 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends this.dso = this.linkService.resolveLink(this.dso, followLink('item')); this.item$ = (this.dso.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload()); this.item$.pipe(take(1)).subscribe((item: Item) => { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item)); + const component: GenericConstructor = this.getComponent(item); const viewContainerRef = this.listableObjectDirective.viewContainerRef; viewContainerRef.clear(); - const componentRef = viewContainerRef.createComponent( - componentFactory, - 0, - undefined, - [ + this.compRef = viewContainerRef.createComponent( + component, { + index: 0, + projectableNodes: [ [this.badges.nativeElement], [this.buttons.nativeElement] - ]); - (componentRef.instance as any).object = item; - (componentRef.instance as any).index = this.index; - (componentRef.instance as any).linkType = this.linkType; - (componentRef.instance as any).listID = this.listID; - componentRef.changeDetectorRef.detectChanges(); + ], + }); + this.compRef.setInput('object', item); + this.compRef.setInput('index', this.index); + this.compRef.setInput('linkType', this.linkType); + this.compRef.setInput('listID', this.listID); + this.compRef.changeDetectorRef.detectChanges(); } ); @@ -130,6 +135,13 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends }); } + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } + } + /** * Fetch the component depending on the item's entity type, view mode and context * @returns {GenericConstructor} diff --git a/src/app/shared/context-help.directive.ts b/src/app/shared/context-help.directive.ts index 41d6daec21..52a822de2c 100644 --- a/src/app/shared/context-help.directive.ts +++ b/src/app/shared/context-help.directive.ts @@ -1,5 +1,4 @@ import { - ComponentFactoryResolver, ComponentRef, Directive, Input, @@ -12,6 +11,7 @@ import { PlacementArray } from '@ng-bootstrap/ng-bootstrap/util/positioning'; import { ContextHelpWrapperComponent } from './context-help-wrapper/context-help-wrapper.component'; import { PlacementDir } from './context-help-wrapper/placement-dir.model'; import { ContextHelpService } from './context-help.service'; +import { hasValue } from './empty.util'; export interface ContextHelpDirectiveInput { content: string; @@ -43,7 +43,6 @@ export class ContextHelpDirective implements OnChanges, OnDestroy { constructor( private templateRef: TemplateRef, private viewContainerRef: ViewContainerRef, - private componentFactoryResolver: ComponentFactoryResolver, private contextHelpService: ContextHelpService ) {} @@ -53,19 +52,21 @@ export class ContextHelpDirective implements OnChanges, OnDestroy { this.contextHelpService.add({id: this.dsContextHelp.id, isTooltipVisible: false}); if (this.wrapper === undefined) { - const factory - = this.componentFactoryResolver.resolveComponentFactory(ContextHelpWrapperComponent); - this.wrapper = this.viewContainerRef.createComponent(factory); + this.wrapper = this.viewContainerRef.createComponent(ContextHelpWrapperComponent); } - this.wrapper.instance.templateRef = this.templateRef; - this.wrapper.instance.content = this.dsContextHelp.content; - this.wrapper.instance.id = this.dsContextHelp.id; - this.wrapper.instance.tooltipPlacement = this.dsContextHelp.tooltipPlacement; - this.wrapper.instance.iconPlacement = this.dsContextHelp.iconPlacement; + this.wrapper.setInput('templateRef', this.templateRef); + this.wrapper.setInput('content', this.dsContextHelp.content); + this.wrapper.setInput('id', this.dsContextHelp.id); + this.wrapper.setInput('tooltipPlacement', this.dsContextHelp.tooltipPlacement); + this.wrapper.setInput('iconPlacement', this.dsContextHelp.iconPlacement); } ngOnDestroy() { this.clearMostRecentId(); + if (hasValue(this.wrapper)) { + this.wrapper.destroy(); + this.wrapper = undefined; + } } private clearMostRecentId(): void { diff --git a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts index 32f14c015d..db47c6638f 100644 --- a/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts +++ b/src/app/workflowitems-edit-page/advanced-workflow-action/advanced-workflow-actions-loader/advanced-workflow-actions-loader.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, ViewChild, ComponentFactoryResolver, OnInit } from '@angular/core'; +import { Component, Input, ViewChild, OnInit, ComponentRef, OnDestroy } from '@angular/core'; import { hasValue } from '../../../shared/empty.util'; import { getAdvancedComponentByWorkflowTaskOption @@ -15,7 +15,7 @@ import { PAGE_NOT_FOUND_PATH } from '../../../app-routing-paths'; templateUrl: './advanced-workflow-actions-loader.component.html', styleUrls: ['./advanced-workflow-actions-loader.component.scss'], }) -export class AdvancedWorkflowActionsLoaderComponent implements OnInit { +export class AdvancedWorkflowActionsLoaderComponent implements OnDestroy, OnInit { /** * The name of the type to render @@ -28,8 +28,12 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { */ @ViewChild(AdvancedWorkflowActionsDirective, { static: true }) claimedTaskActionsDirective: AdvancedWorkflowActionsDirective; + /** + * The reference to the dynamic component + */ + protected compRef: ComponentRef; + constructor( - private componentFactoryResolver: ComponentFactoryResolver, private router: Router, ) { } @@ -40,16 +44,24 @@ export class AdvancedWorkflowActionsLoaderComponent implements OnInit { ngOnInit(): void { const comp = this.getComponentByWorkflowTaskOption(this.type); if (hasValue(comp)) { - const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp); - const viewContainerRef = this.claimedTaskActionsDirective.viewContainerRef; viewContainerRef.clear(); - viewContainerRef.createComponent(componentFactory); + this.compRef = viewContainerRef.createComponent(comp); } else { void this.router.navigate([PAGE_NOT_FOUND_PATH]); } } + /** + * Destroy the dynamically created component + */ + ngOnDestroy(): void { + if (hasValue(this.compRef)) { + this.compRef.destroy(); + this.compRef = undefined; + } + } + getComponentByWorkflowTaskOption(type: string): any { return getAdvancedComponentByWorkflowTaskOption(type); } From 2da613bff155035ae3c384924029820e7e1c88bc Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Thu, 1 Feb 2024 11:31:26 +0100 Subject: [PATCH 53/94] clean up --- ...rvice.spec.ts => update-data.service.spec.ts} | 2 +- ...te-data-service.ts => update-data.service.ts} | 0 .../suggestions-data.service.ts | 2 +- .../dso-edit-metadata.component.ts | 2 +- .../themed-dso-edit-metadata.component.ts | 2 +- .../suggestions-notification.component.html | 2 +- .../suggestions-notification.component.ts | 2 -- .../suggestions.service.ts | 4 +++- .../suggestions-page.component.html | 1 - .../suggestions-page.component.ts | 5 ++++- src/assets/i18n/en.json5 | 16 +++++++++------- 11 files changed, 21 insertions(+), 17 deletions(-) rename src/app/core/data/{update-data-service.spec.ts => update-data.service.spec.ts} (98%) rename src/app/core/data/{update-data-service.ts => update-data.service.ts} (100%) diff --git a/src/app/core/data/update-data-service.spec.ts b/src/app/core/data/update-data.service.spec.ts similarity index 98% rename from src/app/core/data/update-data-service.spec.ts rename to src/app/core/data/update-data.service.spec.ts index 6912527b91..426fa87eb6 100644 --- a/src/app/core/data/update-data-service.spec.ts +++ b/src/app/core/data/update-data.service.spec.ts @@ -14,7 +14,7 @@ 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 { 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'; diff --git a/src/app/core/data/update-data-service.ts b/src/app/core/data/update-data.service.ts similarity index 100% rename from src/app/core/data/update-data-service.ts rename to src/app/core/data/update-data.service.ts diff --git a/src/app/core/suggestion-notifications/suggestions-data.service.ts b/src/app/core/suggestion-notifications/suggestions-data.service.ts index 2078fdb75e..8956cbd208 100644 --- a/src/app/core/suggestion-notifications/suggestions-data.service.ts +++ b/src/app/core/suggestion-notifications/suggestions-data.service.ts @@ -11,7 +11,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv 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 { 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'; diff --git a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts index 22d23e8402..60b1dffa45 100644 --- a/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts +++ b/src/app/dso-shared/dso-edit-metadata/dso-edit-metadata.component.ts @@ -21,7 +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'; +import { UpdateDataService } from '../../core/data/update-data.service'; @Component({ selector: 'ds-dso-edit-metadata', diff --git a/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts b/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts index 5d7ccdd87d..ba21907c99 100644 --- a/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts +++ b/src/app/dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component.ts @@ -2,7 +2,7 @@ import { ThemedComponent } from '../../shared/theme-support/themed.component'; import { DsoEditMetadataComponent } from './dso-edit-metadata.component'; import { Component, Input } from '@angular/core'; 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({ selector: 'ds-themed-dso-edit-metadata', diff --git a/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.html b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.html index 40156b368f..3eac8d2099 100644 --- a/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.html +++ b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.html @@ -1,7 +1,7 @@
    -
    +
    diff --git a/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts index dfa1e187e3..e2afe8f07c 100644 --- a/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts +++ b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts @@ -16,8 +16,6 @@ import { Observable } from 'rxjs'; }) export class SuggestionsNotificationComponent implements OnInit { - labelPrefix = 'mydspace.'; - /** * The user suggestion targets. */ diff --git a/src/app/suggestion-notifications/suggestions.service.ts b/src/app/suggestion-notifications/suggestions.service.ts index bf21b5ae2f..565f8f94db 100644 --- a/src/app/suggestion-notifications/suggestions.service.ts +++ b/src/app/suggestion-notifications/suggestions.service.ts @@ -30,6 +30,7 @@ import { import { SuggestionsDataService } from '../core/suggestion-notifications/suggestions-data.service'; +import { getSuggestionPageRoute } from '../suggestions-page/suggestions-page-routing-paths'; /** * useful for multiple approvals and ignores operation @@ -256,7 +257,8 @@ 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) }; } diff --git a/src/app/suggestions-page/suggestions-page.component.html b/src/app/suggestions-page/suggestions-page.component.html index 5865b613ac..4e367b638e 100644 --- a/src/app/suggestions-page/suggestions-page.component.html +++ b/src/app/suggestions-page/suggestions-page.component.html @@ -5,7 +5,6 @@

    - {{ translateSuggestionType() | translate }} {{'suggestion.suggestionFor' | translate}} {{researcherName}} {{'suggestion.from.source' | translate}} {{ translateSuggestionSource() | translate }} diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts index d986366f1c..ec560dc3ec 100644 --- a/src/app/suggestions-page/suggestions-page.component.ts +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -22,6 +22,9 @@ import { PaginationService } from '../core/pagination/pagination.service'; import { WorkspaceItem } from '../core/submission/models/workspaceitem.model'; import {FindListOptions} from '../core/data/find-list-options.model'; import {redirectOn4xx} from '../core/shared/authorized.operators'; +import { + getWorkflowItemEditRoute +} from '../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; @Component({ selector: 'ds-suggestion-page', @@ -190,7 +193,7 @@ export class SuggestionsPageComponent implements OnInit { approveAndImport(event: SuggestionApproveAndImport) { this.suggestionService.approveAndImport(this.workspaceItemService, event.suggestion, event.collectionId) .subscribe((workspaceitem: WorkspaceItem) => { - const content = this.translateService.instant('suggestion.approveAndImport.success', { workspaceItemId: workspaceitem.id }); + const content = this.translateService.instant('suggestion.approveAndImport.success', { url: getWorkflowItemEditRoute(workspaceitem.id) }); this.notificationService.success('', content, {timeOut:0}, true); this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); this.updatePage(); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index fd4e50610c..bb82085a4a 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1772,6 +1772,10 @@ "form.create": "Create", + "form.number-picker.decrement": "Decrement {{field}}", + + "form.number-picker.increment": "Increment {{field}}", + "form.repeatable.sort.tip": "Drop the item in the new position", "grant-deny-request-copy.deny": "Don't send copy", @@ -3084,9 +3088,7 @@ "mydspace.import": "Import", - "notification.suggestion": "We found {{count}} publications
    in the {{source}} that seems to be related to your profile.
    Please review the suggestions", - - "notification.suggestion.page": "We found {{count}} {{type}} in the {{source}} that seems to be related to your profile. Please review the suggestions.", + "notification.suggestion": "We found {{count}} publications
    in the {{source}} that seems to be related to your profile.
    Please review the suggestions", "nav.browse.header": "All of DSpace", @@ -3564,9 +3566,9 @@ "suggestion.loading": "Loading ...", - "suggestion.title": "Publication Claims", + "suggestion.title": "Publication Claim", - "suggestion.title.breadcrumbs": "Publication Claims", + "suggestion.title.breadcrumbs": "Publication Claim", "suggestion.targets.description": "Below you can see all the suggestions ", @@ -3592,7 +3594,7 @@ "suggestion.approveAndImport": "Approve & import", - "suggestion.approveAndImport.success": "The suggestion has been imported successfully. View.", + "suggestion.approveAndImport.success": "The suggestion has been imported successfully. View.", "suggestion.approveAndImport.bulk": "Approve & import Selected", @@ -3614,7 +3616,7 @@ "suggestion.hideEvidence": "Hide evidence", - "suggestion.suggestionFor": "Suggestion for", + "suggestion.suggestionFor": "Suggestions for", "suggestion.source.openaire": "OpenAIRE Graph", From 2c2f59c7ef39a89aed02711f14098a0e4bb533dd Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Feb 2024 14:27:33 -0600 Subject: [PATCH 54/94] Update cli.yml to reference dspacenet in docker-compose-rest. Remove unnecessary network from cli.assetstore.yml --- docker/cli.assetstore.yml | 5 ----- docker/cli.yml | 15 ++++++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docker/cli.assetstore.yml b/docker/cli.assetstore.yml index 40e4974c7c..31bc53f64d 100644 --- a/docker/cli.assetstore.yml +++ b/docker/cli.assetstore.yml @@ -14,13 +14,8 @@ # Therefore, it should be kept in sync with that file version: "3.7" -networks: - dspacenet: - services: dspace-cli: - networks: - dspacenet: {} environment: # This assetstore zip is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data - LOADASSETS=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/assetstore.tar.gz diff --git a/docker/cli.yml b/docker/cli.yml index 223ec356b9..c2664fdf3e 100644 --- a/docker/cli.yml +++ b/docker/cli.yml @@ -13,7 +13,12 @@ # # Therefore, it should be kept in sync with that file version: "3.7" - +networks: + # Default to using network named 'dspacenet' from docker-compose-rest.yml. + # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") + default: + name: ${COMPOSE_PROJECT_NAME}_dspacenet + external: true services: dspace-cli: image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}" @@ -30,16 +35,12 @@ services: # solr.server: Ensure we are using the 'dspacesolr' image for Solr solr__P__server: http://dspacesolr:8983/solr volumes: - - "assetstore:/dspace/assetstore" + # Keep DSpace assetstore directory between reboots + - assetstore:/dspace/assetstore entrypoint: /dspace/bin/dspace command: help - networks: - - dspacenet tty: true stdin_open: true volumes: assetstore: - -networks: - dspacenet: From 22e87a5f5a875b41e38c1ef834b9c7a9b8cea458 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 2 Feb 2024 17:40:32 +0100 Subject: [PATCH 55/94] 111639: Added scope @Input() and made the search facets & search results use that scope --- .../shared/search/search-filter.service.ts | 1 + .../search-facet-filter-wrapper.component.ts | 9 ++++- .../search-facet-filter.component.spec.ts | 2 ++ .../search-facet-filter.component.ts | 12 +++++-- .../search-filter.component.html | 1 + .../search-filter/search-filter.component.ts | 10 +++++- .../search-hierarchy-filter.component.spec.ts | 4 ++- .../search-hierarchy-filter.component.ts | 6 ++-- .../search-range-filter.component.spec.ts | 2 ++ .../search-range-filter.component.ts | 4 ++- .../search-filters.component.html | 2 +- .../shared/search/search.component.spec.ts | 26 ++------------ src/app/shared/search/search.component.ts | 31 +++++++++++------ .../shared/search/themed-search.component.ts | 34 +++++++++++++++++-- 14 files changed, 97 insertions(+), 47 deletions(-) diff --git a/src/app/core/shared/search/search-filter.service.ts b/src/app/core/shared/search/search-filter.service.ts index 80ba200d38..bf232cb141 100644 --- a/src/app/core/shared/search/search-filter.service.ts +++ b/src/app/core/shared/search/search-filter.service.ts @@ -27,6 +27,7 @@ const filterStateSelector = (state: SearchFiltersState) => state.searchFilter; export const FILTER_CONFIG: InjectionToken = new InjectionToken('filterConfig'); export const IN_PLACE_SEARCH: InjectionToken = new InjectionToken('inPlaceSearch'); export const REFRESH_FILTER: InjectionToken> = new InjectionToken('refreshFilters'); +export const SCOPE: InjectionToken = new InjectionToken('scope'); /** * Service that performs all actions that have to do with search filters and facets diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts index 0aa131d428..f635c027b5 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component.ts @@ -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; + /** + * 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 }); diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts index 92d2e5265b..fc348722e5 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts @@ -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(false) }, + { provide: SCOPE, useValue: undefined }, { provide: SearchFilterService, useValue: { getSelectedValuesForFilter: () => observableOf(selectedValues), diff --git a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index 2b2eb9b11a..994b488d9c 100644 --- a/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -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) { + @Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject, + @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( diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.html b/src/app/shared/search/search-filters/search-filter/search-filter.component.html index a6fb0021b7..0eb826130d 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.html +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.html @@ -18,6 +18,7 @@ (@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)" class="search-filter-wrapper" [ngClass]="{ 'closed' : closed, 'notab': notab }"> diff --git a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts index d1d3bd729d..67e8906bb5 100644 --- a/src/app/shared/search/search-filters/search-filter/search-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-filter.component.ts @@ -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; + /** + * 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) => { diff --git a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts index e6c74d8047..4469a124ce 100644 --- a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.spec.ts @@ -14,6 +14,7 @@ import { CommonModule } from '@angular/common'; import { SearchService } from '../../../../../core/shared/search/search.service'; import { FILTER_CONFIG, + SCOPE, IN_PLACE_SEARCH, SearchFilterService, REFRESH_FILTER @@ -75,7 +76,8 @@ describe('SearchHierarchyFilterComponent', () => { { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: IN_PLACE_SEARCH, useValue: false }, { provide: FILTER_CONFIG, useValue: Object.assign(new SearchFilterConfig(), { name: testSearchFilter }) }, - { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false)} + { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false)}, + { provide: SCOPE, useValue: undefined }, ], schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); diff --git a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.ts index f9b3f2bff9..d53fa37cf4 100644 --- a/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component.ts @@ -9,6 +9,7 @@ import { import { SearchService } from '../../../../../core/shared/search/search.service'; import { FILTER_CONFIG, + SCOPE, IN_PLACE_SEARCH, SearchFilterService, REFRESH_FILTER } from '../../../../../core/shared/search/search-filter.service'; @@ -49,9 +50,10 @@ export class SearchHierarchyFilterComponent extends SearchFacetFilterComponent i @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 + @Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject, + @Inject(SCOPE) public scope: string, ) { - super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters); + super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters, scope); } vocabularyExists$: Observable; diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts index 3a146f5059..03f47a7569 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.spec.ts @@ -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 @@ -105,6 +106,7 @@ describe('SearchRangeFilterComponent', () => { { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }, { provide: IN_PLACE_SEARCH, useValue: false }, { provide: REFRESH_FILTER, useValue: new BehaviorSubject(false) }, + { provide: SCOPE, useValue: undefined }, { provide: SearchFilterService, useValue: { getSelectedValuesForFilter: () => selectedValues, diff --git a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts index 938f67412e..cdbf384fe9 100644 --- a/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts +++ b/src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.ts @@ -9,6 +9,7 @@ import { facetLoad, SearchFacetFilterComponent } from '../search-facet-filter/se import { SearchFilterConfig } from '../../../models/search-filter-config.model'; import { FILTER_CONFIG, + SCOPE, IN_PLACE_SEARCH, REFRESH_FILTER, SearchFilterService @@ -83,8 +84,9 @@ export class SearchRangeFilterComponent extends SearchFacetFilterComponent imple @Inject(FILTER_CONFIG) public filterConfig: SearchFilterConfig, @Inject(PLATFORM_ID) private platformId: any, @Inject(REFRESH_FILTER) public refreshFilters: BehaviorSubject, + @Inject(SCOPE) public scope: string, private route: RouteService) { - super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters); + super(searchService, filterService, rdbs, router, searchConfigService, inPlaceSearch, filterConfig, refreshFilters, scope); } diff --git a/src/app/shared/search/search-filters/search-filters.component.html b/src/app/shared/search/search-filters/search-filters.component.html index e392cd2663..c006d80c44 100644 --- a/src/app/shared/search/search-filters/search-filters.component.html +++ b/src/app/shared/search/search-filters/search-filters.component.html @@ -1,7 +1,7 @@

    {{"search.filters.head" | translate}}

    - +
    {{"search.filters.reset" | translate}} diff --git a/src/app/shared/search/search.component.spec.ts b/src/app/shared/search/search.component.spec.ts index d0d9bdda86..9d069636a9 100644 --- a/src/app/shared/search/search.component.spec.ts +++ b/src/app/shared/search/search.component.spec.ts @@ -36,8 +36,6 @@ import { getCollectionPageRoute } from '../../collection-page/collection-page-ro let comp: SearchComponent; let fixture: ComponentFixture; -let searchServiceObject: SearchService; -let searchConfigurationServiceObject: SearchConfigurationService; const store: Store = jasmine.createSpyObj('store', { /* eslint-disable no-empty,@typescript-eslint/no-empty-function */ dispatch: {}, @@ -93,7 +91,6 @@ const mockDso2 = Object.assign(new Item(), { } } }); -const sort: SortOptions = new SortOptions('score', SortDirection.DESC); const mockSearchResults: SearchObjects = Object.assign(new SearchObjects(), { page: [mockDso, mockDso2] }); @@ -106,23 +103,13 @@ const searchServiceStub = jasmine.createSpyObj('SearchService', { getSearchConfigurationFor: createSuccessfulRemoteDataObject$(searchConfig), trackSearch: {}, }) as SearchService; -const configurationParam = 'default'; const queryParam = 'test query'; const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f'; -const fixedFilter = 'fixed filter'; const defaultSearchOptions = new PaginatedSearchOptions({ pagination }); const paginatedSearchOptions$ = new BehaviorSubject(defaultSearchOptions); -const paginatedSearchOptions = new PaginatedSearchOptions({ - configuration: configurationParam, - query: queryParam, - scope: scopeParam, - fixedFilter: fixedFilter, - pagination, - sort -}); const activatedRouteStub = { snapshot: { queryParamMap: new Map([ @@ -155,14 +142,11 @@ const filtersConfigRD = createSuccessfulRemoteDataObject([mockFilterConfig, mock const filtersConfigRD$ = observableOf(filtersConfigRD); const routeServiceStub = { - getRouteParameterValue: () => { - return observableOf(''); - }, getQueryParameterValue: () => { - return observableOf(''); + return observableOf(null); }, getQueryParamsWithPrefix: () => { - return observableOf(''); + return observableOf(null); }, setParameter: () => { return; @@ -252,16 +236,10 @@ describe('SearchComponent', () => { comp.paginationId = paginationId; spyOn((comp as any), 'getSearchOptions').and.returnValue(paginatedSearchOptions$.asObservable()); - - searchServiceObject = TestBed.inject(SearchService); - searchConfigurationServiceObject = TestBed.inject(SEARCH_CONFIG_SERVICE); - }); afterEach(() => { comp = null; - searchServiceObject = null; - searchConfigurationServiceObject = null; }); it('should init search parameters properly and call retrieveSearchResults', fakeAsync(() => { diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 61f3a119c8..8e120940ec 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, OnInit, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, OnInit, Output, OnDestroy } from '@angular/core'; import { NavigationStart, Router } from '@angular/router'; import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; @@ -11,7 +11,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { pushInOut } from '../animations/push'; import { HostWindowService } from '../host-window.service'; import { SidebarService } from '../sidebar/sidebar.service'; -import { hasValue, hasValueOperator, isNotEmpty } from '../empty.util'; +import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../empty.util'; import { RouteService } from '../../core/services/route.service'; import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component'; import { PaginatedSearchOptions } from './models/paginated-search-options.model'; @@ -34,7 +34,7 @@ import { CollectionElementLinkType } from '../object-collection/collection-eleme import { environment } from 'src/environments/environment'; import { SubmissionObject } from '../../core/submission/models/submission-object.model'; import { SearchFilterConfig } from './models/search-filter-config.model'; -import { WorkspaceItem } from '../..//core/submission/models/workspaceitem.model'; +import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model'; import { ITEM_MODULE_PATH } from '../../item-page/item-page-routing-paths'; import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths'; import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths'; @@ -50,7 +50,7 @@ import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routi /** * This component renders a sidebar, a search input bar and the search results. */ -export class SearchComponent implements OnInit { +export class SearchComponent implements OnDestroy, OnInit { /** * The list of available configuration options @@ -166,6 +166,11 @@ export class SearchComponent implements OnInit { */ @Input() query: string; + /** + * The fallback scope when no scope is defined in the url, if this is also undefined no scope will be set + */ + @Input() scope: string; + /** * The current configuration used during the search */ @@ -179,7 +184,7 @@ export class SearchComponent implements OnInit { /** * The current sort options used */ - currentScope$: BehaviorSubject = new BehaviorSubject(''); + currentScope$: Observable; /** * The current sort options used @@ -299,6 +304,10 @@ export class SearchComponent implements OnInit { this.routeService.setParameter('fixedFilterQuery', this.fixedFilterQuery); } + this.currentScope$ = this.routeService.getQueryParameterValue('scope').pipe( + map((routeValue: string) => hasValue(routeValue) ? routeValue : this.scope), + ); + this.isSidebarCollapsed$ = this.isSidebarCollapsed(); this.searchLink = this.getSearchLink(); this.currentContext$.next(this.context); @@ -321,13 +330,13 @@ export class SearchComponent implements OnInit { ); const searchOptions$: Observable = this.getSearchOptions().pipe(distinctUntilChanged()); - this.subs.push(combineLatest([configuration$, searchSortOptions$, searchOptions$, sortOption$]).pipe( - filter(([configuration, searchSortOptions, searchOptions, sortOption]: [string, SortOptions[], PaginatedSearchOptions, SortOptions]) => { + this.subs.push(combineLatest([configuration$, searchSortOptions$, searchOptions$, sortOption$, this.currentScope$]).pipe( + filter(([configuration, searchSortOptions, searchOptions, sortOption, scope]: [string, SortOptions[], PaginatedSearchOptions, SortOptions, string]) => { // filter for search options related to instanced paginated id return searchOptions.pagination.id === this.paginationId; }), debounceTime(100) - ).subscribe(([configuration, searchSortOptions, searchOptions, sortOption]: [string, SortOptions[], PaginatedSearchOptions, SortOptions]) => { + ).subscribe(([configuration, searchSortOptions, searchOptions, sortOption, scope]: [string, SortOptions[], PaginatedSearchOptions, SortOptions, string]) => { // Build the PaginatedSearchOptions object const combinedOptions = Object.assign({}, searchOptions, { @@ -337,6 +346,9 @@ export class SearchComponent implements OnInit { if (combinedOptions.query === '') { combinedOptions.query = this.query; } + if (isEmpty(combinedOptions.scope)) { + combinedOptions.scope = scope; + } const newSearchOptions = new PaginatedSearchOptions(combinedOptions); // check if search options are changed // if so retrieve new related results otherwise skip it @@ -344,13 +356,12 @@ export class SearchComponent implements OnInit { // Initialize variables this.currentConfiguration$.next(configuration); this.currentSortOptions$.next(newSearchOptions.sort); - this.currentScope$.next(newSearchOptions.scope); this.sortOptionsList$.next(searchSortOptions); this.searchOptions$.next(newSearchOptions); this.initialized$.next(true); // retrieve results this.retrieveSearchResults(newSearchOptions); - this.retrieveFilters(searchOptions); + this.retrieveFilters(newSearchOptions); } })); diff --git a/src/app/shared/search/themed-search.component.ts b/src/app/shared/search/themed-search.component.ts index fe531e4f0f..38df90dd8a 100644 --- a/src/app/shared/search/themed-search.component.ts +++ b/src/app/shared/search/themed-search.component.ts @@ -15,11 +15,37 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode */ @Component({ selector: 'ds-themed-search', - styleUrls: [], templateUrl: '../theme-support/themed.component.html', }) export class ThemedSearchComponent extends ThemedComponent { - protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = ['configurationList', 'context', 'configuration', 'fixedFilterQuery', 'useCachedVersionIfAvailable', 'inPlaceSearch', 'linkType', 'paginationId', 'searchEnabled', 'sideBarWidth', 'searchFormPlaceholder', 'selectable', 'selectionConfig', 'showCsvExport', 'showSidebar', 'showThumbnails', 'showViewModes', 'useUniquePageId', 'viewModeList', 'showScopeSelector', 'resultFound', 'deselectObject', 'selectObject', 'trackStatistics', 'query']; + protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = [ + 'configurationList', + 'context', + 'configuration', + 'fixedFilterQuery', + 'useCachedVersionIfAvailable', + 'inPlaceSearch', + 'linkType', + 'paginationId', + 'searchEnabled', + 'sideBarWidth', + 'searchFormPlaceholder', + 'selectable', + 'selectionConfig', + 'showCsvExport', + 'showSidebar', + 'showThumbnails', + 'showViewModes', + 'useUniquePageId', + 'viewModeList', + 'showScopeSelector', + 'trackStatistics', + 'query', + 'scope', + 'resultFound', + 'deselectObject', + 'selectObject', + ]; @Input() configurationList: SearchConfigurationOption[]; @@ -51,7 +77,7 @@ export class ThemedSearchComponent extends ThemedComponent { @Input() showSidebar: boolean; - @Input() showThumbnails; + @Input() showThumbnails: boolean; @Input() showViewModes: boolean; @@ -65,6 +91,8 @@ export class ThemedSearchComponent extends ThemedComponent { @Input() query: string; + @Input() scope: string; + @Output() resultFound: EventEmitter> = new EventEmitter(); @Output() deselectObject: EventEmitter = new EventEmitter(); From 93b22cba1f1b541d86f12b3bce79c8db6fd83bb9 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sat, 3 Feb 2024 13:39:31 +0100 Subject: [PATCH 56/94] 111639: Fixed search settings not using the scope --- .../search-settings.component.spec.ts | 41 ++----------------- .../search-settings.component.ts | 11 ++--- src/app/shared/search/search.component.ts | 5 +-- src/assets/i18n/en.json5 | 12 ++++++ 4 files changed, 21 insertions(+), 48 deletions(-) diff --git a/src/app/shared/search/search-settings/search-settings.component.spec.ts b/src/app/shared/search/search-settings/search-settings.component.spec.ts index d0b51f04b1..bdcdb6f171 100644 --- a/src/app/shared/search/search-settings/search-settings.component.spec.ts +++ b/src/app/shared/search/search-settings/search-settings.component.spec.ts @@ -1,4 +1,3 @@ -import { SearchService } from '../../../core/shared/search/search.service'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { SearchSettingsComponent } from './search-settings.component'; import { of as observableOf } from 'rxjs'; @@ -6,15 +5,11 @@ import { PaginationComponentOptions } from '../../pagination/pagination-componen import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; import { TranslateModule } from '@ngx-translate/core'; import { RouterTestingModule } from '@angular/router/testing'; -import { ActivatedRoute } from '@angular/router'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { EnumKeysPipe } from '../../utils/enum-keys-pipe'; import { By } from '@angular/platform-browser'; -import { SearchFilterService } from '../../../core/shared/search/search-filter.service'; import { VarDirective } from '../../utils/var.directive'; import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; -import { SidebarService } from '../../sidebar/sidebar.service'; -import { SidebarServiceStub } from '../../testing/sidebar-service.stub'; import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationServiceStub } from '../../testing/pagination-service.stub'; @@ -22,32 +17,23 @@ describe('SearchSettingsComponent', () => { let comp: SearchSettingsComponent; let fixture: ComponentFixture; - let searchServiceObject: SearchService; let pagination: PaginationComponentOptions; let sort: SortOptions; - let mockResults; - let searchServiceStub; let queryParam; let scopeParam; let paginatedSearchOptions; - let paginationService; + let paginationService: PaginationServiceStub; - let activatedRouteStub; - beforeEach(waitForAsync(() => { + beforeEach(waitForAsync(async () => { pagination = new PaginationComponentOptions(); pagination.id = 'search-results-pagination'; pagination.currentPage = 1; pagination.pageSize = 10; sort = new SortOptions('score', SortDirection.DESC); - mockResults = ['test', 'data']; - searchServiceStub = { - searchOptions: { pagination: pagination, sort: sort }, - search: () => mockResults, - }; queryParam = 'test query'; scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f'; @@ -58,30 +44,12 @@ describe('SearchSettingsComponent', () => { sort, }; - activatedRouteStub = { - queryParams: observableOf({ - query: queryParam, - scope: scopeParam, - }), - }; - paginationService = new PaginationServiceStub(pagination, sort); - TestBed.configureTestingModule({ + await TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], declarations: [SearchSettingsComponent, EnumKeysPipe, VarDirective], providers: [ - { provide: SearchService, useValue: searchServiceStub }, - - { provide: ActivatedRoute, useValue: activatedRouteStub }, - { - provide: SidebarService, - useValue: SidebarServiceStub, - }, - { - provide: SearchFilterService, - useValue: {}, - }, { provide: PaginationService, useValue: paginationService, @@ -111,10 +79,7 @@ describe('SearchSettingsComponent', () => { // SearchPageComponent test instance fixture.detectChanges(); - searchServiceObject = (comp as any).service; spyOn(comp, 'reloadOrder'); - spyOn(searchServiceObject, 'search').and.callThrough(); - }); it('it should show the order settings with the respective selectable options', () => { diff --git a/src/app/shared/search/search-settings/search-settings.component.ts b/src/app/shared/search/search-settings/search-settings.component.ts index 0efd38b5b2..23c9e78c3a 100644 --- a/src/app/shared/search/search-settings/search-settings.component.ts +++ b/src/app/shared/search/search-settings/search-settings.component.ts @@ -1,7 +1,5 @@ import { Component, Inject, Input } from '@angular/core'; -import { SearchService } from '../../../core/shared/search/search.service'; import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model'; -import { ActivatedRoute, Router } from '@angular/router'; import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service'; import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-page.component'; import { PaginationService } from '../../../core/pagination/pagination.service'; @@ -26,11 +24,10 @@ export class SearchSettingsComponent { */ @Input() sortOptionsList: SortOptions[]; - constructor(private service: SearchService, - private route: ActivatedRoute, - private router: Router, - private paginationService: PaginationService, - @Inject(SEARCH_CONFIG_SERVICE) public searchConfigurationService: SearchConfigurationService) { + constructor( + protected paginationService: PaginationService, + @Inject(SEARCH_CONFIG_SERVICE) public searchConfigurationService: SearchConfigurationService, + ) { } /** diff --git a/src/app/shared/search/search.component.ts b/src/app/shared/search/search.component.ts index 8e120940ec..fc07893d72 100644 --- a/src/app/shared/search/search.component.ts +++ b/src/app/shared/search/search.component.ts @@ -315,9 +315,8 @@ export class SearchComponent implements OnDestroy, OnInit { // Determinate PaginatedSearchOptions and listen to any update on it const configuration$: Observable = this.searchConfigService .getCurrentConfiguration(this.configuration).pipe(distinctUntilChanged()); - const searchSortOptions$: Observable = configuration$.pipe( - switchMap((configuration: string) => this.searchConfigService - .getConfigurationSearchConfig(configuration)), + const searchSortOptions$: Observable = combineLatest([configuration$, this.currentScope$]).pipe( + switchMap(([configuration, scope]: [string, string]) => this.searchConfigService.getConfigurationSearchConfig(configuration, scope)), map((searchConfig: SearchConfig) => this.searchConfigService.getConfigurationSortOptions(searchConfig)), distinctUntilChanged() ); diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 6c91bae4c1..19c297acd3 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3888,6 +3888,18 @@ "sorting.lastModified.DESC": "Last modified Descending", + "sorting.person.familyName.ASC": "Surname Ascending", + + "sorting.person.familyName.DESC": "Surname Descending", + + "sorting.person.givenName.ASC": "Name Ascending", + + "sorting.person.givenName.DESC": "Name Descending", + + "sorting.person.birthDate.ASC": "Birth Date Ascending", + + "sorting.person.birthDate.DESC": "Birth Date Descending", + "statistics.title": "Statistics", "statistics.header": "Statistics for {{ scope }}", From 239f082cce32edce7719ba3eab8f71e06e3955d0 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Fri, 2 Feb 2024 09:35:27 +0100 Subject: [PATCH 57/94] Moved the context @Input() to the loaders (since they are not always required for all the loaders) & added AbstractComponentLoaderComponent documentation --- .../browse-by-switcher.component.ts | 7 +++- .../abstract-component-loader.component.ts | 40 +++++++++++-------- ...etadata-representation-loader.component.ts | 9 ++++- .../claimed-task-actions-loader.component.ts | 2 - ...table-object-component-loader.component.ts | 11 +++-- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts index 2ebea9a262..99ad720dd3 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts @@ -4,6 +4,7 @@ import { GenericConstructor } from '../../core/shared/generic-constructor'; import { AbstractComponentLoaderComponent } from '../../shared/abstract-component-loader/abstract-component-loader.component'; import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { BrowseByDataType } from './browse-by-data-type'; +import { Context } from '../../core/shared/context.model'; @Component({ selector: 'ds-browse-by-switcher', @@ -11,15 +12,17 @@ import { BrowseByDataType } from './browse-by-data-type'; }) export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent { + @Input() context: Context; + @Input() browseByType: BrowseByDataType; protected inputNamesDependentForComponent: (keyof this & string)[] = [ - ...this.inputNamesDependentForComponent, + 'context', 'browseByType', ]; protected inputNames: (keyof this & string)[] = [ - ...this.inputNames, + 'context', 'browseByType', ]; diff --git a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts index 888edffe67..2ea7c50d75 100644 --- a/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts +++ b/src/app/shared/abstract-component-loader/abstract-component-loader.component.ts @@ -1,22 +1,27 @@ -import { Component, ComponentRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core'; -import { Context } from '../../core/shared/context.model'; +import { Component, ComponentRef, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild, ViewContainerRef } from '@angular/core'; import { ThemeService } from '../theme-support/theme.service'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { hasValue, isNotEmpty } from '../empty.util'; import { Subscription } from 'rxjs'; import { DynamicComponentLoaderDirective } from './dynamic-component-loader.directive'; +/** + * To create a new loader component you will need to: + *
      + *
    • Create a new LoaderComponent component extending this component
    • + *
    • Point the templateUrl to this component's templateUrl
    • + *
    • Add all the @Input()/@Output() names that the dynamically generated components should inherit from the loader to the inputNames/outputNames lists
    • + *
    • Create a decorator file containing the new decorator function, a map containing all the collected {@link Component}s and a function to retrieve the {@link Component}
    • + *
    • Call the function to retrieve the correct {@link Component} in getComponent()
    • + *
    • Add all the @Input()s you had to used in getComponent() in the inputNamesDependentForComponent array
    • + *
    + */ @Component({ selector: 'ds-abstract-component-loader', templateUrl: './abstract-component-loader.component.html', }) export abstract class AbstractComponentLoaderComponent implements OnInit, OnChanges, OnDestroy { - /** - * The optional context - */ - @Input() context: Context; - /** * Directive to determine where the dynamic child component is located */ @@ -33,20 +38,21 @@ export abstract class AbstractComponentLoaderComponent implements OnInit, OnC protected subs: Subscription[] = []; /** - * The @{@link Input}() that are used to find the matching component using {@link getComponent}. When the value of - * one of these @{@link Input}() change this loader needs to retrieve the best matching component again using the + * The @Input() that are used to find the matching component using {@link getComponent}. When the value of + * one of these @Input() change this loader needs to retrieve the best matching component again using the * {@link getComponent} method. */ - protected inputNamesDependentForComponent: (keyof this & string)[] = [ - 'context', - ]; + protected inputNamesDependentForComponent: (keyof this & string)[] = []; - protected inputNames: (keyof this & string)[] = [ - 'context', - ]; + /** + * The list of the @Input() names that should be passed down to the dynamically created components. + */ + protected inputNames: (keyof this & string)[] = []; - protected outputNames: (keyof this & string)[] = [ - ]; + /** + * The list of the @Output() names that should be passed down to the dynamically created components. + */ + protected outputNames: (keyof this & string)[] = []; constructor( protected themeService: ThemeService, diff --git a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts index 4b63a31a2b..d722d24ff6 100644 --- a/src/app/shared/metadata-representation/metadata-representation-loader.component.ts +++ b/src/app/shared/metadata-representation/metadata-representation-loader.component.ts @@ -19,13 +19,20 @@ import { AbstractComponentLoaderComponent } from '../abstract-component-loader/a */ export class MetadataRepresentationLoaderComponent extends AbstractComponentLoaderComponent { + @Input() context: Context; + /** * The item or metadata to determine the component for */ @Input() mdRepresentation: MetadataRepresentation; + protected inputNamesDependentForComponent: (keyof this & string)[] = [ + 'context', + 'mdRepresentation', + ]; + protected inputNames: (keyof this & string)[] = [ - ...this.inputNames, + 'context', 'mdRepresentation', ]; diff --git a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts index fb39347d63..7b01e98249 100644 --- a/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/switcher/claimed-task-actions-loader.component.ts @@ -47,7 +47,6 @@ export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderCo * The list of input and output names for the dynamic component */ protected inputNames: (keyof this & string)[] = [ - ...this.inputNames, 'item', 'object', 'option', @@ -55,7 +54,6 @@ export class ClaimedTaskActionsLoaderComponent extends AbstractComponentLoaderCo ]; protected outputNames: (keyof this & string)[] = [ - ...this.outputNames, 'processCompleted', ]; diff --git a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts index a83cdfb6b2..d280768631 100644 --- a/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts +++ b/src/app/shared/object-collection/shared/listable-object/listable-object-component-loader.component.ts @@ -58,7 +58,7 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa /** * Whether to show the thumbnail preview */ - @Input() showThumbnails; + @Input() showThumbnails: boolean; /** * The value to display for this element @@ -70,13 +70,19 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa */ @Output() contentChange = new EventEmitter(); + protected inputNamesDependentForComponent: (keyof this & string)[] = [ + 'object', + 'viewMode', + 'context', + ]; + /** * The list of input and output names for the dynamic component */ protected inputNames: (keyof this & string)[] = [ - ...this.inputNames, 'object', 'index', + 'context', 'linkType', 'listID', 'showLabel', @@ -86,7 +92,6 @@ export class ListableObjectComponentLoaderComponent extends AbstractComponentLoa ]; protected outputNames: (keyof this & string)[] = [ - ...this.outputNames, 'contentChange', ]; From ac5670a51428c58cf02cf139b97c7d358d5b5f30 Mon Sep 17 00:00:00 2001 From: Sascha Szott Date: Mon, 5 Feb 2024 12:06:11 +0100 Subject: [PATCH 58/94] remove translation (use singular form instead of plural form) --- src/assets/i18n/de.json5 | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/assets/i18n/de.json5 b/src/assets/i18n/de.json5 index c185a13432..94067fccd0 100644 --- a/src/assets/i18n/de.json5 +++ b/src/assets/i18n/de.json5 @@ -731,9 +731,6 @@ // "admin.workflow.title": "Administer Workflow", "admin.workflow.title": "Workflows verwalten", - // "admin.workflow.item.workflow": "Workflow", - "admin.workflow.item.workflow": "Workflows", - // "admin.workflow.item.delete": "Delete", "admin.workflow.item.delete": "Löschen", From 937687c41433073bf51a9abdbfa2aeca69909fc8 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 5 Feb 2024 12:48:23 +0100 Subject: [PATCH 59/94] fix routing, update issue, adapt labels --- .../suggestions-notification.component.ts | 1 + .../suggestions-popup/suggestions-popup.component.ts | 4 ++-- src/app/suggestion-notifications/suggestions.service.ts | 2 +- src/app/suggestions-page/suggestions-page.component.ts | 4 ++-- .../workflowitems-edit-page-routing-paths.ts | 5 +++++ src/assets/i18n/en.json5 | 4 ++-- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts index e2afe8f07c..e123f8f87f 100644 --- a/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts +++ b/src/app/suggestion-notifications/suggestions-notification/suggestions-notification.component.ts @@ -29,6 +29,7 @@ export class SuggestionsNotificationComponent implements OnInit { ) { } ngOnInit() { + this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); this.suggestionsRD$ = this.suggestionTargetsStateService.getCurrentUserSuggestionTargets(); } diff --git a/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts index 762f80085a..0b2176ea89 100644 --- a/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts +++ b/src/app/suggestion-notifications/suggestions-popup/suggestions-popup.component.ts @@ -3,7 +3,7 @@ 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 { take, takeUntil } from 'rxjs/operators'; import { SuggestionTarget } from '../../core/suggestion-notifications/models/suggestion-target.model'; import { isNotEmpty } from '../../shared/empty.util'; import { combineLatest, Subject } from 'rxjs'; @@ -36,7 +36,7 @@ export class SuggestionsPopupComponent implements OnInit, OnDestroy { public initializePopup() { const notifier = new Subject(); this.subscription = combineLatest([ - this.suggestionTargetsStateService.getCurrentUserSuggestionTargets(), + this.suggestionTargetsStateService.getCurrentUserSuggestionTargets().pipe(take(2)), this.suggestionTargetsStateService.hasUserVisitedSuggestions() ]).pipe(takeUntil(notifier)).subscribe(([suggestions, visited]) => { this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); diff --git a/src/app/suggestion-notifications/suggestions.service.ts b/src/app/suggestion-notifications/suggestions.service.ts index 565f8f94db..874659eab5 100644 --- a/src/app/suggestion-notifications/suggestions.service.ts +++ b/src/app/suggestion-notifications/suggestions.service.ts @@ -154,7 +154,7 @@ export class SuggestionsService { * The EPerson id for which to retrieve suggestion targets */ public retrieveCurrentUserSuggestions(userUuid: string): Observable { - return this.researcherProfileService.findById(userUuid).pipe( + return this.researcherProfileService.findById(userUuid, true).pipe( getFirstSucceededRemoteDataPayload(), mergeMap((profile: ResearcherProfile) => { if (isNotEmpty(profile)) { diff --git a/src/app/suggestions-page/suggestions-page.component.ts b/src/app/suggestions-page/suggestions-page.component.ts index ec560dc3ec..2005646010 100644 --- a/src/app/suggestions-page/suggestions-page.component.ts +++ b/src/app/suggestions-page/suggestions-page.component.ts @@ -23,7 +23,7 @@ import { WorkspaceItem } from '../core/submission/models/workspaceitem.model'; import {FindListOptions} from '../core/data/find-list-options.model'; import {redirectOn4xx} from '../core/shared/authorized.operators'; import { - getWorkflowItemEditRoute + getWorkspaceItemEditRoute } from '../workflowitems-edit-page/workflowitems-edit-page-routing-paths'; @Component({ @@ -193,7 +193,7 @@ export class SuggestionsPageComponent implements OnInit { approveAndImport(event: SuggestionApproveAndImport) { this.suggestionService.approveAndImport(this.workspaceItemService, event.suggestion, event.collectionId) .subscribe((workspaceitem: WorkspaceItem) => { - const content = this.translateService.instant('suggestion.approveAndImport.success', { url: getWorkflowItemEditRoute(workspaceitem.id) }); + const content = this.translateService.instant('suggestion.approveAndImport.success', { url: getWorkspaceItemEditRoute(workspaceitem.id) }); this.notificationService.success('', content, {timeOut:0}, true); this.suggestionTargetsStateService.dispatchRefreshUserSuggestionsAction(); this.updatePage(); diff --git a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts index 326eebe4a7..a4d4a0f2c9 100644 --- a/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts +++ b/src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts @@ -28,9 +28,14 @@ export function getWorkspaceItemDeleteRoute(wsiId: string) { return new URLCombiner(getWorkspaceItemModuleRoute(), wsiId, WORKSPACE_ITEM_DELETE_PATH).toString(); } +export function getWorkspaceItemEditRoute(wsiId: string) { + return new URLCombiner(getWorkspaceItemModuleRoute(), wsiId, WORKSPACE_ITEM_EDIT_PATH).toString(); +} + export const WORKFLOW_ITEM_EDIT_PATH = 'edit'; export const WORKFLOW_ITEM_DELETE_PATH = 'delete'; export const WORKFLOW_ITEM_VIEW_PATH = 'view'; export const WORKFLOW_ITEM_SEND_BACK_PATH = 'sendback'; export const ADVANCED_WORKFLOW_PATH = 'advanced'; export const WORKSPACE_ITEM_DELETE_PATH = 'delete'; +export const WORKSPACE_ITEM_EDIT_PATH = 'edit'; diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index bb82085a4a..1aebe0430e 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -24,9 +24,9 @@ "404.page-not-found": "page not found", - "admin.notifications.publicationclaim.breadcrumbs": "Suggestions", + "admin.notifications.publicationclaim.breadcrumbs": "Publication Claim", - "admin.notifications.publicationclaim.page.title": "Suggestions", + "admin.notifications.publicationclaim.page.title": "Publication Claim", "error-page.description.401": "unauthorized", From 06cc944f2b64b94032e383965be1507ecd040b34 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 5 Feb 2024 13:00:08 +0100 Subject: [PATCH 60/94] fix test --- src/app/suggestion-notifications/suggestion.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/suggestion-notifications/suggestion.service.spec.ts b/src/app/suggestion-notifications/suggestion.service.spec.ts index acb0bb3be2..268eb59ddd 100644 --- a/src/app/suggestion-notifications/suggestion.service.spec.ts +++ b/src/app/suggestion-notifications/suggestion.service.spec.ts @@ -121,7 +121,7 @@ describe('SuggestionsService test', () => { it('should retrieve current user suggestions', () => { service.retrieveCurrentUserSuggestions('1234'); - expect(researcherProfileService.findById).toHaveBeenCalledWith('1234'); + expect(researcherProfileService.findById).toHaveBeenCalledWith('1234', true); }); it('should approve and import suggestion', () => { From 9043f63cd2f3ef9d99b4b22db041eddab9f2c95b Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Feb 2024 14:57:34 -0600 Subject: [PATCH 61/94] Bug fix to GitHub CI build. Set compose project name via env variable. Also add default name if unspecified --- .github/workflows/build.yml | 2 ++ docker/cli.yml | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e2680420a2..52f20470a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,6 +33,8 @@ jobs: #CHROME_VERSION: "90.0.4430.212-1" # Bump Node heap size (OOM in CI after upgrading to Angular 15) NODE_OPTIONS: '--max-old-space-size=4096' + # Project name to use when running docker-compose prior to e2e tests + COMPOSE_PROJECT_NAME: 'ci' strategy: # Create a matrix of Node versions to test against (in parallel) matrix: diff --git a/docker/cli.yml b/docker/cli.yml index c2664fdf3e..cc266b186f 100644 --- a/docker/cli.yml +++ b/docker/cli.yml @@ -16,8 +16,9 @@ version: "3.7" networks: # Default to using network named 'dspacenet' from docker-compose-rest.yml. # Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet") + # If COMPOSITE_PROJECT_NAME is missing, default value will be "docker" (name of folder this file is in) default: - name: ${COMPOSE_PROJECT_NAME}_dspacenet + name: ${COMPOSE_PROJECT_NAME:-docker}_dspacenet external: true services: dspace-cli: From be0a6c71efae4c5d2eda09940587338e23bea630 Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Fri, 2 Feb 2024 16:59:52 -0600 Subject: [PATCH 62/94] Resync docker-compose-ci and docker/docker-compose-rest with DSpace/DSpace docker scripts --- docker/docker-compose-ci.yml | 36 +++++++++++++++------------------- docker/docker-compose-rest.yml | 18 +++++++---------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index edbb5b0759..07993e20c6 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -33,11 +33,11 @@ services: # Tell Statistics to commit all views immediately instead of waiting on Solr's autocommit. # This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly. solr__D__statistics__P__autoCommit: 'false' + image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}" depends_on: - dspacedb - image: dspace/dspace:latest-test networks: - dspacenet: + - dspacenet ports: - published: 8080 target: 8080 @@ -45,8 +45,6 @@ services: tty: true volumes: - assetstore:/dspace/assetstore - # Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below) - - solr_configs:/dspace/solr # Ensure that the database is ready BEFORE starting tomcat # 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep # 2. Then, run database migration to init database tables (including any out-of-order ignored migrations, if any) @@ -70,21 +68,18 @@ services: PGDATA: /pgdata image: dspace/dspace-postgres-pgcrypto:loadsql networks: - dspacenet: + - dspacenet stdin_open: true tty: true volumes: + # Keep Postgres data directory between reboots - pgdata:/pgdata # DSpace Solr container dspacesolr: container_name: dspacesolr - # Uses official Solr image at https://hub.docker.com/_/solr/ - image: solr:8.11-slim - # Needs main 'dspace' container to start first to guarantee access to solr_configs - depends_on: - - dspace + image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}" networks: - dspacenet: + - dspacenet ports: - published: 8983 target: 8983 @@ -92,9 +87,6 @@ services: tty: true working_dir: /var/solr/data volumes: - # Mount our "solr_configs" volume available under the Solr's configsets folder (in a 'dspace' subfolder) - # This copies the Solr configs from main 'dspace' container into 'dspacesolr' via that volume - - solr_configs:/opt/solr/server/solr/configsets/dspace # Keep Solr data directory between reboots - solr_data:/var/solr/data # Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr @@ -103,14 +95,18 @@ services: - '-c' - | init-var-solr - precreate-core authority /opt/solr/server/solr/configsets/dspace/authority - precreate-core oai /opt/solr/server/solr/configsets/dspace/oai - precreate-core search /opt/solr/server/solr/configsets/dspace/search - precreate-core statistics /opt/solr/server/solr/configsets/dspace/statistics + precreate-core authority /opt/solr/server/solr/configsets/authority + cp -r /opt/solr/server/solr/configsets/authority/* authority + precreate-core oai /opt/solr/server/solr/configsets/oai + cp -r /opt/solr/server/solr/configsets/oai/* oai + precreate-core search /opt/solr/server/solr/configsets/search + cp -r /opt/solr/server/solr/configsets/search/* search + precreate-core statistics /opt/solr/server/solr/configsets/statistics + cp -r /opt/solr/server/solr/configsets/statistics/* statistics + precreate-core qaevent /opt/solr/server/solr/configsets/qaevent + cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent exec solr -f volumes: assetstore: pgdata: solr_data: - # Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above) - solr_configs: \ No newline at end of file diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml index ea766600ef..e1577ec837 100644 --- a/docker/docker-compose-rest.yml +++ b/docker/docker-compose-rest.yml @@ -43,7 +43,7 @@ services: depends_on: - dspacedb networks: - dspacenet: + - dspacenet ports: - published: 8080 target: 8080 @@ -51,8 +51,6 @@ services: tty: true volumes: - assetstore:/dspace/assetstore - # Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below) - - solr_configs:/dspace/solr # Ensure that the database is ready BEFORE starting tomcat # 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep # 2. Then, run database migration to init database tables @@ -69,25 +67,23 @@ services: container_name: dspacedb environment: PGDATA: /pgdata - image: dspace/dspace-postgres-pgcrypto + image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}" networks: - dspacenet: + - dspacenet ports: - published: 5432 target: 5432 stdin_open: true tty: true volumes: + # Keep Postgres data directory between reboots - pgdata:/pgdata # DSpace Solr container dspacesolr: container_name: dspacesolr image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}" - # Needs main 'dspace' container to start first to guarantee access to solr_configs - depends_on: - - dspace networks: - dspacenet: + - dspacenet ports: - published: 8983 target: 8983 @@ -115,10 +111,10 @@ services: cp -r /opt/solr/server/solr/configsets/search/* search precreate-core statistics /opt/solr/server/solr/configsets/statistics cp -r /opt/solr/server/solr/configsets/statistics/* statistics + precreate-core qaevent /opt/solr/server/solr/configsets/qaevent + cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent exec solr -f volumes: assetstore: pgdata: solr_data: - # Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above) - solr_configs: From 044ec06cb1d47b076e75e1b0bfd51f74bf33b9cf Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 5 Feb 2024 16:53:21 +0100 Subject: [PATCH 63/94] update breadcrumb label --- .../core/breadcrumbs/publication-claim-breadcrumb.service.ts | 3 ++- src/assets/i18n/en.json5 | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.ts b/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.ts index 14ef7917c3..1a87fd7de6 100644 --- a/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.ts +++ b/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.ts @@ -38,7 +38,8 @@ export class PublicationClaimBreadcrumbService implements BreadcrumbsProviderSer 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(itemName, undefined)] : [new Breadcrumb(itemName, undefined)]; + new Breadcrumb(this.tranlsateService.instant('suggestion.suggestionFor.breadcrumb', {name: itemName}), undefined)] : + [new Breadcrumb(this.tranlsateService.instant('suggestion.suggestionFor.breadcrumb', {name: itemName}), undefined)]; }) ); } diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 1aebe0430e..39785eed70 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -3618,6 +3618,8 @@ "suggestion.suggestionFor": "Suggestions for", + "suggestion.suggestionFor.breadcrumb": "Suggestions for {{ name }}", + "suggestion.source.openaire": "OpenAIRE Graph", "suggestion.from.source": "from the ", From 71e3d40c794a82058dc109e3f7793fa6d60fc135 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 5 Feb 2024 17:49:49 +0100 Subject: [PATCH 64/94] fix breadcrumb test --- .../breadcrumbs/publication-claim-breadcrumb.service.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.spec.ts b/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.spec.ts index 6a2e6e54a6..11062210bb 100644 --- a/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.spec.ts +++ b/src/app/core/breadcrumbs/publication-claim-breadcrumb.service.spec.ts @@ -28,7 +28,7 @@ describe('PublicationClaimBreadcrumbService', () => { const ADMIN_PUBLICATION_CLAIMS_BREADCRUMB_KEY = 'admin.notifications.publicationclaim.page.title'; function init() { - exampleKey = '6bee076d-4f2a-4555-a475-04a267769b2a'; + exampleKey = 'suggestion.suggestionFor.breadcrumb'; } beforeEach(waitForAsync(() => { From 2fb393db7f8d45922f3a529b3bc1c34d2625818c Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Thu, 1 Feb 2024 21:02:55 +0100 Subject: [PATCH 65/94] Removed the AbstractBrowseByTypeComponent --- .../abstract-browse-by-type.component.ts | 32 ------------------- .../browse-by-metadata-page.component.ts | 26 +++++++++++---- .../browse-by-page.component.spec.ts | 3 +- .../browse-by-switcher/browse-by-decorator.ts | 10 +++--- .../browse-by-switcher.component.spec.ts | 3 +- .../browse-by-switcher.component.ts | 5 ++- .../browse-by-taxonomy-page.component.ts | 27 +++++++++++++--- 7 files changed, 51 insertions(+), 55 deletions(-) delete mode 100644 src/app/browse-by/abstract-browse-by-type.component.ts diff --git a/src/app/browse-by/abstract-browse-by-type.component.ts b/src/app/browse-by/abstract-browse-by-type.component.ts deleted file mode 100644 index 5bd39cfc50..0000000000 --- a/src/app/browse-by/abstract-browse-by-type.component.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Component, Input, OnDestroy } from '@angular/core'; -import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type'; -import { Context } from '../core/shared/context.model'; -import { Subscription } from 'rxjs'; -import { hasValue } from '../shared/empty.util'; - -@Component({ - selector: 'ds-abstract-browse-by-type', - template: '', -}) -export abstract class AbstractBrowseByTypeComponent implements OnDestroy { - - /** - * The optional context - */ - @Input() context: Context; - - /** - * The {@link BrowseByDataType} of this Component - */ - @Input() browseByType: BrowseByDataType; - - /** - * List of subscriptions - */ - subs: Subscription[] = []; - - ngOnDestroy(): void { - this.subs.filter((sub: Subscription) => hasValue(sub)).forEach((sub: Subscription) => sub.unsubscribe()); - } - -} diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts index 4c897b40f8..499ba441f6 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.ts @@ -1,5 +1,5 @@ -import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; -import { Component, Inject, OnInit, OnDestroy } from '@angular/core'; +import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs'; +import { Component, Inject, OnInit, OnDestroy, Input } from '@angular/core'; import { RemoteData } from '../../core/data/remote-data'; import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; @@ -23,8 +23,8 @@ import { Community } from '../../core/shared/community.model'; import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; -import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; +import { Context } from '../../core/shared/context.model'; export const BBM_PAGINATION_ID = 'bbm'; @@ -40,7 +40,17 @@ export const BBM_PAGINATION_ID = 'bbm'; * 'dc.contributor.*' */ @rendersBrowseBy(BrowseByDataType.Metadata) -export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent implements OnInit, OnDestroy { +export class BrowseByMetadataPageComponent implements OnInit, OnDestroy { + + /** + * The optional context + */ + @Input() context: Context; + + /** + * The {@link BrowseByDataType} of this Component + */ + @Input() browseByType: BrowseByDataType; /** * The list of browse-entries to display @@ -77,6 +87,11 @@ export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent */ currentSort$: Observable; + /** + * List of subscriptions + */ + subs: Subscription[] = []; + /** * The default browse id to resort to when none is provided */ @@ -129,7 +144,6 @@ export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent @Inject(APP_CONFIG) public appConfig: AppConfig, public dsoNameService: DSONameService, ) { - super(); this.fetchThumbnails = this.appConfig.browseBy.showThumbnails; this.paginationConfig = Object.assign(new PaginationComponentOptions(), { id: BBM_PAGINATION_ID, @@ -273,7 +287,7 @@ export class BrowseByMetadataPageComponent extends AbstractBrowseByTypeComponent } ngOnDestroy(): void { - super.ngOnDestroy(); + this.subs.filter((sub: Subscription) => hasValue(sub)).forEach((sub: Subscription) => sub.unsubscribe()); this.paginationService.clearPagination(this.paginationConfig.id); } diff --git a/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts index 3f9271a67d..25483028eb 100644 --- a/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts +++ b/src/app/browse-by/browse-by-page/browse-by-page.component.spec.ts @@ -9,7 +9,6 @@ import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { ThemeService } from '../../shared/theme-support/theme.service'; import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { Component } from '@angular/core'; -import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; import { By } from '@angular/platform-browser'; import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; @@ -20,7 +19,7 @@ import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; selector: '', template: '', }) -class BrowseByTestComponent extends AbstractBrowseByTypeComponent { +class BrowseByTestComponent { } class TestBrowseByPageBrowseDefinition extends BrowseDefinition { diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts index 835c34ebcb..62e666227d 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts @@ -1,6 +1,6 @@ +import { Component } from '@angular/core'; import { hasNoValue } from '../../shared/empty.util'; import { DEFAULT_THEME, resolveTheme } from '../../shared/object-collection/shared/listable-object/listable-object.decorator'; -import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { Context } from '../../core/shared/context.model'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { BrowseByDataType } from './browse-by-data-type'; @@ -8,7 +8,7 @@ import { BrowseByDataType } from './browse-by-data-type'; export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata; export const DEFAULT_BROWSE_BY_CONTEXT = Context.Any; -const map: Map>>> = new Map(); +const map: Map>>> = new Map(); /** * Decorator used for rendering Browse-By pages by type @@ -41,12 +41,12 @@ export function rendersBrowseBy(browseByType: BrowseByDataType, context = DEFAUL * @param context The context to match * @param theme the theme to match */ -export function getComponentByBrowseByType(browseByType: BrowseByDataType, context: Context, theme: string): GenericConstructor { - let contextMap: Map>> = map.get(browseByType); +export function getComponentByBrowseByType(browseByType: BrowseByDataType, context: Context, theme: string): GenericConstructor { + let contextMap: Map>> = map.get(browseByType); if (hasNoValue(contextMap)) { contextMap = map.get(DEFAULT_BROWSE_BY_TYPE); } - let themeMap: Map> = contextMap.get(context); + let themeMap: Map> = contextMap.get(context); if (hasNoValue(themeMap)) { themeMap = contextMap.get(DEFAULT_BROWSE_BY_CONTEXT); } diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts index 418dfd45e1..65e8c87c61 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts @@ -8,7 +8,6 @@ import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-d import { NonHierarchicalBrowseDefinition } from '../../core/shared/non-hierarchical-browse-definition'; import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; import { DynamicComponentLoaderDirective } from '../../shared/abstract-component-loader/dynamic-component-loader.directive'; -import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { BrowseByDataType } from './browse-by-data-type'; @rendersBrowseBy('BrowseBySwitcherComponent' as BrowseByDataType) @@ -17,7 +16,7 @@ import { BrowseByDataType } from './browse-by-data-type'; selector: '', template: '', }) -class BrowseByTestComponent extends AbstractBrowseByTypeComponent { +class BrowseByTestComponent { } describe('BrowseBySwitcherComponent', () => { diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts index 99ad720dd3..0c200c3453 100644 --- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts +++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts @@ -2,7 +2,6 @@ import { Component, Input } from '@angular/core'; import { getComponentByBrowseByType } from './browse-by-decorator'; import { GenericConstructor } from '../../core/shared/generic-constructor'; import { AbstractComponentLoaderComponent } from '../../shared/abstract-component-loader/abstract-component-loader.component'; -import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { BrowseByDataType } from './browse-by-data-type'; import { Context } from '../../core/shared/context.model'; @@ -10,7 +9,7 @@ import { Context } from '../../core/shared/context.model'; selector: 'ds-browse-by-switcher', templateUrl: '../../shared/abstract-component-loader/abstract-component-loader.component.html' }) -export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent { +export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent { @Input() context: Context; @@ -26,7 +25,7 @@ export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent< 'browseByType', ]; - public getComponent(): GenericConstructor { + public getComponent(): GenericConstructor { return getComponentByBrowseByType(this.browseByType, this.context, this.themeService.getThemeName()); } diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts index f005c66c9e..fb2f28c8c5 100644 --- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts +++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts @@ -1,14 +1,14 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model'; import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model'; import { ActivatedRoute } from '@angular/router'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { BrowseDefinition } from '../../core/shared/browse-definition.model'; import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator'; import { map } from 'rxjs/operators'; import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model'; -import { AbstractBrowseByTypeComponent } from '../abstract-browse-by-type.component'; import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; +import { Context } from '../../core/shared/context.model'; @Component({ selector: 'ds-browse-by-taxonomy-page', @@ -19,7 +19,17 @@ import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type'; * Component for browsing items by metadata in a hierarchical controlled vocabulary */ @rendersBrowseBy(BrowseByDataType.Hierarchy) -export class BrowseByTaxonomyPageComponent extends AbstractBrowseByTypeComponent implements OnInit, OnDestroy { +export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy { + + /** + * The optional context + */ + @Input() context: Context; + + /** + * The {@link BrowseByDataType} of this Component + */ + @Input() browseByType: BrowseByDataType; /** * The {@link VocabularyOptions} object @@ -56,10 +66,14 @@ export class BrowseByTaxonomyPageComponent extends AbstractBrowseByTypeComponent */ browseDefinition$: Observable; + /** + * Subscriptions to track + */ + subs: Subscription[] = []; + public constructor( protected route: ActivatedRoute, ) { - super(); } ngOnInit(): void { @@ -108,4 +122,7 @@ export class BrowseByTaxonomyPageComponent extends AbstractBrowseByTypeComponent }; } + ngOnDestroy(): void { + this.subs.forEach((sub: Subscription) => sub.unsubscribe()); + } } From 90a277fbeb33ca29060d8ffe59954600d0eb9466 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Feb 2024 22:29:37 +0000 Subject: [PATCH 66/94] Bump follow-redirects from 1.15.3 to 1.15.5 Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.5. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.5) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6e04ab27b2..786a832e56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5859,9 +5859,9 @@ flatted@^3.1.0, flatted@^3.2.7: integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.15.0: - version "1.15.3" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" - integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== for-each@^0.3.3: version "0.3.3" From 3e33785311444682eaf1f38ffc0bb8e0b3e3f353 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 7 Feb 2024 11:55:44 +1300 Subject: [PATCH 67/94] Improve cfg handling in item-status.component.ts --- .../item-status/item-status.component.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index d3a5e033d7..759045b3dc 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -3,7 +3,7 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { Item } from '../../../core/shared/item.model'; import { ActivatedRoute } from '@angular/router'; import { ItemOperation } from '../item-operation/itemOperation.model'; -import { concatMap, distinctUntilChanged, first, map, mergeMap, switchMap, toArray } from 'rxjs/operators'; +import {concatMap, distinctUntilChanged, first, map, mergeMap, switchMap, tap, toArray} from 'rxjs/operators'; import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths'; @@ -17,6 +17,7 @@ import { ConfigurationProperty } from '../../../core/shared/configuration-proper import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; import { IdentifierData } from '../../../shared/object-list/identifier-data/identifier-data.model'; import { OrcidAuthService } from '../../../core/orcid/orcid-auth.service'; +import {getDSORoute} from "../../../app-routing-paths"; @Component({ selector: 'ds-item-status', @@ -107,7 +108,19 @@ export class ItemStatusComponent implements OnInit { // Observable for configuration determining whether the Register DOI feature is enabled let registerConfigEnabled$: Observable = this.configurationService.findByPropertyName('identifiers.item-status.register-doi').pipe( getFirstCompletedRemoteData(), - map((enabledRD: RemoteData) => enabledRD.hasSucceeded && enabledRD.payload.values.length > 0) + map((response: RemoteData) => { + // Return true if a successful response with a 'true' value was retrieved, otherwise return false + if (response.hasSucceeded) { + const payload = response.payload; + if (payload.values.length > 0 && hasValue(payload.values[0])) { + return payload.values[0] === 'true'; + } else { + return false; + } + } else { + return false; + } + }) ); /** From 68475d1ed61bd231de1a1efdb2feb3bf638108c2 Mon Sep 17 00:00:00 2001 From: Kim Shepherd Date: Wed, 7 Feb 2024 12:09:16 +1300 Subject: [PATCH 68/94] Improve cfg handling in item-status.component.ts --- .../edit-item-page/item-status/item-status.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/item-page/edit-item-page/item-status/item-status.component.ts b/src/app/item-page/edit-item-page/item-status/item-status.component.ts index 759045b3dc..8b4dc88d9d 100644 --- a/src/app/item-page/edit-item-page/item-status/item-status.component.ts +++ b/src/app/item-page/edit-item-page/item-status/item-status.component.ts @@ -3,7 +3,7 @@ import { fadeIn, fadeInOut } from '../../../shared/animations/fade'; import { Item } from '../../../core/shared/item.model'; import { ActivatedRoute } from '@angular/router'; import { ItemOperation } from '../item-operation/itemOperation.model'; -import {concatMap, distinctUntilChanged, first, map, mergeMap, switchMap, tap, toArray} from 'rxjs/operators'; +import {concatMap, distinctUntilChanged, first, map, mergeMap, switchMap, toArray} from 'rxjs/operators'; import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs'; import { RemoteData } from '../../../core/data/remote-data'; import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths'; @@ -17,7 +17,6 @@ import { ConfigurationProperty } from '../../../core/shared/configuration-proper import { ConfigurationDataService } from '../../../core/data/configuration-data.service'; import { IdentifierData } from '../../../shared/object-list/identifier-data/identifier-data.model'; import { OrcidAuthService } from '../../../core/orcid/orcid-auth.service'; -import {getDSORoute} from "../../../app-routing-paths"; @Component({ selector: 'ds-item-status', From 048361281982dcbe1617275b6e987a8fc083ace1 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 19 Nov 2023 03:22:37 +0100 Subject: [PATCH 69/94] 108588: Moved CommunityPageSubCommunityListComponent & CommunityPageSubCollectionListComponent to a custom sections folder --- .../community-page/community-page.module.ts | 8 ++-- ...ty-page-sub-collection-list.component.html | 0 ...ty-page-sub-collection-list.component.scss | 0 ...page-sub-collection-list.component.spec.ts | 42 +++++++++---------- ...nity-page-sub-collection-list.component.ts | 22 +++++----- ...nity-page-sub-collection-list.component.ts | 8 ++-- ...ity-page-sub-community-list.component.html | 0 ...ity-page-sub-community-list.component.scss | 0 ...-page-sub-community-list.component.spec.ts | 42 +++++++++---------- ...unity-page-sub-community-list.component.ts | 18 ++++---- ...unity-page-sub-community-list.component.ts | 8 ++-- ...ty-page-sub-collection-list.component.html | 0 ...ty-page-sub-collection-list.component.scss | 0 ...nity-page-sub-collection-list.component.ts | 12 ++++++ ...ity-page-sub-community-list.component.html | 0 ...ity-page-sub-community-list.component.scss | 0 ...unity-page-sub-community-list.component.ts | 12 ++++++ ...nity-page-sub-collection-list.component.ts | 12 ------ ...unity-page-sub-community-list.component.ts | 12 ------ src/themes/custom/lazy-theme.module.ts | 7 +--- 20 files changed, 99 insertions(+), 104 deletions(-) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-collection-list/community-page-sub-collection-list.component.html (100%) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-collection-list/community-page-sub-collection-list.component.scss (100%) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-collection-list/community-page-sub-collection-list.component.spec.ts (76%) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-collection-list/community-page-sub-collection-list.component.ts (78%) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-collection-list/themed-community-page-sub-collection-list.component.ts (68%) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-community-list/community-page-sub-community-list.component.html (100%) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-community-list/community-page-sub-community-list.component.scss (100%) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-community-list/community-page-sub-community-list.component.spec.ts (76%) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-community-list/community-page-sub-community-list.component.ts (80%) rename src/app/community-page/{ => sections/sub-com-col-section}/sub-community-list/themed-community-page-sub-community-list.component.ts (68%) rename src/themes/custom/app/community-page/{ => sections/sub-com-col-section}/sub-collection-list/community-page-sub-collection-list.component.html (100%) rename src/themes/custom/app/community-page/{ => sections/sub-com-col-section}/sub-collection-list/community-page-sub-collection-list.component.scss (100%) create mode 100644 src/themes/custom/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.ts rename src/themes/custom/app/community-page/{ => sections/sub-com-col-section}/sub-community-list/community-page-sub-community-list.component.html (100%) rename src/themes/custom/app/community-page/{ => sections/sub-com-col-section}/sub-community-list/community-page-sub-community-list.component.scss (100%) create mode 100644 src/themes/custom/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.ts delete mode 100644 src/themes/custom/app/community-page/sub-collection-list/community-page-sub-collection-list.component.ts delete mode 100644 src/themes/custom/app/community-page/sub-community-list/community-page-sub-community-list.component.ts diff --git a/src/app/community-page/community-page.module.ts b/src/app/community-page/community-page.module.ts index 45ffb2a786..499e9092e3 100644 --- a/src/app/community-page/community-page.module.ts +++ b/src/app/community-page/community-page.module.ts @@ -4,9 +4,9 @@ import { CommonModule } from '@angular/common'; import { SharedModule } from '../shared/shared.module'; import { CommunityPageComponent } from './community-page.component'; -import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component'; +import { CommunityPageSubCollectionListComponent } from './sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component'; import { CommunityPageRoutingModule } from './community-page-routing.module'; -import { CommunityPageSubCommunityListComponent } from './sub-community-list/community-page-sub-community-list.component'; +import { CommunityPageSubCommunityListComponent } from './sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component'; import { StatisticsModule } from '../statistics/statistics.module'; @@ -15,10 +15,10 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component' import { ComcolModule } from '../shared/comcol/comcol.module'; import { ThemedCommunityPageSubCommunityListComponent -} from './sub-community-list/themed-community-page-sub-community-list.component'; +} from './sections/sub-com-col-section/sub-community-list/themed-community-page-sub-community-list.component'; import { ThemedCollectionPageSubCollectionListComponent -} from './sub-collection-list/themed-community-page-sub-collection-list.component'; +} from './sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component'; import { DsoPageModule } from '../shared/dso-page/dso-page.module'; const DECLARATIONS = [CommunityPageComponent, diff --git a/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.html b/src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.html similarity index 100% rename from src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.html rename to src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.html diff --git a/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.scss b/src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.scss similarity index 100% rename from src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.scss rename to src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.scss diff --git a/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts b/src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.spec.ts similarity index 76% rename from src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts rename to src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.spec.ts index bca3c42a95..8c4af30991 100644 --- a/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.spec.ts +++ b/src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.spec.ts @@ -8,27 +8,27 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component'; -import { Community } from '../../core/shared/community.model'; -import { SharedModule } from '../../shared/shared.module'; -import { CollectionDataService } from '../../core/data/collection-data.service'; -import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; -import { buildPaginatedList } from '../../core/data/paginated-list.model'; -import { PageInfo } from '../../core/shared/page-info.model'; -import { HostWindowService } from '../../shared/host-window.service'; -import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub'; -import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; -import { PaginationService } from '../../core/pagination/pagination.service'; -import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; -import { ThemeService } from '../../shared/theme-support/theme.service'; -import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; -import { FindListOptions } from '../../core/data/find-list-options.model'; -import { GroupDataService } from '../../core/eperson/group-data.service'; -import { LinkHeadService } from '../../core/services/link-head.service'; -import { ConfigurationDataService } from '../../core/data/configuration-data.service'; -import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; -import { ConfigurationProperty } from '../../core/shared/configuration-property.model'; -import { createPaginatedList } from '../../shared/testing/utils.test'; -import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub'; +import { Community } from '../../../../core/shared/community.model'; +import { SharedModule } from '../../../../shared/shared.module'; +import { CollectionDataService } from '../../../../core/data/collection-data.service'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { buildPaginatedList } from '../../../../core/data/paginated-list.model'; +import { PageInfo } from '../../../../core/shared/page-info.model'; +import { HostWindowService } from '../../../../shared/host-window.service'; +import { HostWindowServiceStub } from '../../../../shared/testing/host-window-service.stub'; +import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { getMockThemeService } from '../../../../shared/mocks/theme-service.mock'; +import { ThemeService } from '../../../../shared/theme-support/theme.service'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; +import { FindListOptions } from '../../../../core/data/find-list-options.model'; +import { GroupDataService } from '../../../../core/eperson/group-data.service'; +import { LinkHeadService } from '../../../../core/services/link-head.service'; +import { ConfigurationDataService } from '../../../../core/data/configuration-data.service'; +import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; +import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model'; +import { createPaginatedList } from '../../../../shared/testing/utils.test'; +import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service.stub'; describe('CommunityPageSubCollectionList Component', () => { let comp: CommunityPageSubCollectionListComponent; diff --git a/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.ts b/src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.ts similarity index 78% rename from src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.ts rename to src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.ts index 3a77149e5b..bd506882ee 100644 --- a/src/app/community-page/sub-collection-list/community-page-sub-collection-list.component.ts +++ b/src/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.ts @@ -1,19 +1,17 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; - import { BehaviorSubject, combineLatest as observableCombineLatest } from 'rxjs'; - -import { RemoteData } from '../../core/data/remote-data'; -import { Collection } from '../../core/shared/collection.model'; -import { Community } from '../../core/shared/community.model'; -import { fadeIn } from '../../shared/animations/fade'; -import { PaginatedList } from '../../core/data/paginated-list.model'; -import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { CollectionDataService } from '../../core/data/collection-data.service'; -import { PaginationService } from '../../core/pagination/pagination.service'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { Collection } from '../../../../core/shared/collection.model'; +import { Community } from '../../../../core/shared/community.model'; +import { fadeIn } from '../../../../shared/animations/fade'; +import { PaginatedList } from '../../../../core/data/paginated-list.model'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { CollectionDataService } from '../../../../core/data/collection-data.service'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; import { switchMap } from 'rxjs/operators'; -import { hasValue } from '../../shared/empty.util'; +import { hasValue } from '../../../../shared/empty.util'; @Component({ selector: 'ds-community-page-sub-collection-list', diff --git a/src/app/community-page/sub-collection-list/themed-community-page-sub-collection-list.component.ts b/src/app/community-page/sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component.ts similarity index 68% rename from src/app/community-page/sub-collection-list/themed-community-page-sub-collection-list.component.ts rename to src/app/community-page/sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component.ts index f1f49f204c..ebbec33e8e 100644 --- a/src/app/community-page/sub-collection-list/themed-community-page-sub-collection-list.component.ts +++ b/src/app/community-page/sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component.ts @@ -1,12 +1,12 @@ -import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { ThemedComponent } from '../../../../shared/theme-support/themed.component'; import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component'; import { Component, Input } from '@angular/core'; -import { Community } from '../../core/shared/community.model'; +import { Community } from '../../../../core/shared/community.model'; @Component({ selector: 'ds-themed-community-page-sub-collection-list', styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html', + templateUrl: '../../../../shared/theme-support/themed.component.html', }) export class ThemedCollectionPageSubCollectionListComponent extends ThemedComponent { @Input() community: Community; @@ -18,7 +18,7 @@ export class ThemedCollectionPageSubCollectionListComponent extends ThemedCompon } protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/community-page/sub-collection-list/community-page-sub-collection-list.component`); + return import(`../../../../../themes/${themeName}/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component`); } protected importUnthemedComponent(): Promise { diff --git a/src/app/community-page/sub-community-list/community-page-sub-community-list.component.html b/src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.html similarity index 100% rename from src/app/community-page/sub-community-list/community-page-sub-community-list.component.html rename to src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.html diff --git a/src/app/community-page/sub-community-list/community-page-sub-community-list.component.scss b/src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.scss similarity index 100% rename from src/app/community-page/sub-community-list/community-page-sub-community-list.component.scss rename to src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.scss diff --git a/src/app/community-page/sub-community-list/community-page-sub-community-list.component.spec.ts b/src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.spec.ts similarity index 76% rename from src/app/community-page/sub-community-list/community-page-sub-community-list.component.spec.ts rename to src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.spec.ts index 0a14fe6dd1..c5efc9c2c1 100644 --- a/src/app/community-page/sub-community-list/community-page-sub-community-list.component.spec.ts +++ b/src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.spec.ts @@ -8,27 +8,27 @@ import { By } from '@angular/platform-browser'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component'; -import { Community } from '../../core/shared/community.model'; -import { buildPaginatedList } from '../../core/data/paginated-list.model'; -import { PageInfo } from '../../core/shared/page-info.model'; -import { SharedModule } from '../../shared/shared.module'; -import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; -import { HostWindowService } from '../../shared/host-window.service'; -import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub'; -import { CommunityDataService } from '../../core/data/community-data.service'; -import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service'; -import { PaginationService } from '../../core/pagination/pagination.service'; -import { getMockThemeService } from '../../shared/mocks/theme-service.mock'; -import { ThemeService } from '../../shared/theme-support/theme.service'; -import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub'; -import { FindListOptions } from '../../core/data/find-list-options.model'; -import { GroupDataService } from '../../core/eperson/group-data.service'; -import { LinkHeadService } from '../../core/services/link-head.service'; -import { ConfigurationDataService } from '../../core/data/configuration-data.service'; -import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service'; -import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub'; -import { ConfigurationProperty } from '../../core/shared/configuration-property.model'; -import { createPaginatedList } from '../../shared/testing/utils.test'; +import { Community } from '../../../../core/shared/community.model'; +import { buildPaginatedList } from '../../../../core/data/paginated-list.model'; +import { PageInfo } from '../../../../core/shared/page-info.model'; +import { SharedModule } from '../../../../shared/shared.module'; +import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; +import { HostWindowService } from '../../../../shared/host-window.service'; +import { HostWindowServiceStub } from '../../../../shared/testing/host-window-service.stub'; +import { CommunityDataService } from '../../../../core/data/community-data.service'; +import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { getMockThemeService } from '../../../../shared/mocks/theme-service.mock'; +import { ThemeService } from '../../../../shared/theme-support/theme.service'; +import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub'; +import { FindListOptions } from '../../../../core/data/find-list-options.model'; +import { GroupDataService } from '../../../../core/eperson/group-data.service'; +import { LinkHeadService } from '../../../../core/services/link-head.service'; +import { ConfigurationDataService } from '../../../../core/data/configuration-data.service'; +import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service'; +import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service.stub'; +import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model'; +import { createPaginatedList } from '../../../../shared/testing/utils.test'; describe('CommunityPageSubCommunityListComponent Component', () => { let comp: CommunityPageSubCommunityListComponent; diff --git a/src/app/community-page/sub-community-list/community-page-sub-community-list.component.ts b/src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.ts similarity index 80% rename from src/app/community-page/sub-community-list/community-page-sub-community-list.component.ts rename to src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.ts index 5a0409a051..61633e5128 100644 --- a/src/app/community-page/sub-community-list/community-page-sub-community-list.component.ts +++ b/src/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.ts @@ -3,16 +3,16 @@ import { ActivatedRoute } from '@angular/router'; import { BehaviorSubject, combineLatest as observableCombineLatest } from 'rxjs'; -import { RemoteData } from '../../core/data/remote-data'; -import { Community } from '../../core/shared/community.model'; -import { fadeIn } from '../../shared/animations/fade'; -import { PaginatedList } from '../../core/data/paginated-list.model'; -import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; -import { CommunityDataService } from '../../core/data/community-data.service'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { Community } from '../../../../core/shared/community.model'; +import { fadeIn } from '../../../../shared/animations/fade'; +import { PaginatedList } from '../../../../core/data/paginated-list.model'; +import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model'; +import { CommunityDataService } from '../../../../core/data/community-data.service'; import { switchMap } from 'rxjs/operators'; -import { PaginationService } from '../../core/pagination/pagination.service'; -import { hasValue } from '../../shared/empty.util'; +import { PaginationService } from '../../../../core/pagination/pagination.service'; +import { hasValue } from '../../../../shared/empty.util'; @Component({ selector: 'ds-community-page-sub-community-list', diff --git a/src/app/community-page/sub-community-list/themed-community-page-sub-community-list.component.ts b/src/app/community-page/sections/sub-com-col-section/sub-community-list/themed-community-page-sub-community-list.component.ts similarity index 68% rename from src/app/community-page/sub-community-list/themed-community-page-sub-community-list.component.ts rename to src/app/community-page/sections/sub-com-col-section/sub-community-list/themed-community-page-sub-community-list.component.ts index 852c53186e..9c500cac10 100644 --- a/src/app/community-page/sub-community-list/themed-community-page-sub-community-list.component.ts +++ b/src/app/community-page/sections/sub-com-col-section/sub-community-list/themed-community-page-sub-community-list.component.ts @@ -1,12 +1,12 @@ -import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { ThemedComponent } from '../../../../shared/theme-support/themed.component'; import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component'; import { Component, Input } from '@angular/core'; -import { Community } from '../../core/shared/community.model'; +import { Community } from '../../../../core/shared/community.model'; @Component({ selector: 'ds-themed-community-page-sub-community-list', styleUrls: [], - templateUrl: '../../shared/theme-support/themed.component.html', + templateUrl: '../../../../shared/theme-support/themed.component.html', }) export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponent { @@ -19,7 +19,7 @@ export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponen } protected importThemedComponent(themeName: string): Promise { - return import(`../../../themes/${themeName}/app/community-page/sub-community-list/community-page-sub-community-list.component`); + return import(`../../../../../themes/${themeName}/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component`); } protected importUnthemedComponent(): Promise { diff --git a/src/themes/custom/app/community-page/sub-collection-list/community-page-sub-collection-list.component.html b/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.html similarity index 100% rename from src/themes/custom/app/community-page/sub-collection-list/community-page-sub-collection-list.component.html rename to src/themes/custom/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.html diff --git a/src/themes/custom/app/community-page/sub-collection-list/community-page-sub-collection-list.component.scss b/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.scss similarity index 100% rename from src/themes/custom/app/community-page/sub-collection-list/community-page-sub-collection-list.component.scss rename to src/themes/custom/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.scss diff --git a/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.ts b/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.ts new file mode 100644 index 0000000000..a3f51bf916 --- /dev/null +++ b/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { CommunityPageSubCollectionListComponent as BaseComponent } from '../../../../../../../app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component'; + +@Component({ + selector: 'ds-community-page-sub-collection-list', + // styleUrls: ['./community-page-sub-collection-list.component.scss'], + styleUrls: ['../../../../../app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.scss'], + // templateUrl: './community-page-sub-collection-list.component.html', + templateUrl: '../../../../../app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component.html', +}) +export class CommunityPageSubCollectionListComponent extends BaseComponent { +} diff --git a/src/themes/custom/app/community-page/sub-community-list/community-page-sub-community-list.component.html b/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.html similarity index 100% rename from src/themes/custom/app/community-page/sub-community-list/community-page-sub-community-list.component.html rename to src/themes/custom/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.html diff --git a/src/themes/custom/app/community-page/sub-community-list/community-page-sub-community-list.component.scss b/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.scss similarity index 100% rename from src/themes/custom/app/community-page/sub-community-list/community-page-sub-community-list.component.scss rename to src/themes/custom/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.scss diff --git a/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.ts b/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.ts new file mode 100644 index 0000000000..68af7dddd0 --- /dev/null +++ b/src/themes/custom/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.ts @@ -0,0 +1,12 @@ +import { Component } from '@angular/core'; +import { CommunityPageSubCommunityListComponent as BaseComponent } from '../../../../../../../app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component'; + +@Component({ + selector: 'ds-community-page-sub-community-list', + // styleUrls: ['./community-page-sub-community-list.component.scss'], + styleUrls: ['../../../../../../../app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.scss'], + // templateUrl: './community-page-sub-community-list.component.html', + templateUrl: '../../../../../../../app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component.html', +}) +export class CommunityPageSubCommunityListComponent extends BaseComponent { +} diff --git a/src/themes/custom/app/community-page/sub-collection-list/community-page-sub-collection-list.component.ts b/src/themes/custom/app/community-page/sub-collection-list/community-page-sub-collection-list.component.ts deleted file mode 100644 index a228fe1071..0000000000 --- a/src/themes/custom/app/community-page/sub-collection-list/community-page-sub-collection-list.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from '@angular/core'; -import { CommunityPageSubCollectionListComponent as BaseComponent } - from '../../../../../app/community-page/sub-collection-list/community-page-sub-collection-list.component'; - -@Component({ - selector: 'ds-community-page-sub-collection-list', - // styleUrls: ['./community-page-sub-collection-list.component.scss'], - styleUrls: ['../../../../../app/community-page/sub-collection-list/community-page-sub-collection-list.component.scss'], - // templateUrl: './community-page-sub-collection-list.component.html', - templateUrl: '../../../../../app/community-page/sub-collection-list/community-page-sub-collection-list.component.html' -}) -export class CommunityPageSubCollectionListComponent extends BaseComponent {} diff --git a/src/themes/custom/app/community-page/sub-community-list/community-page-sub-community-list.component.ts b/src/themes/custom/app/community-page/sub-community-list/community-page-sub-community-list.component.ts deleted file mode 100644 index 1db265a844..0000000000 --- a/src/themes/custom/app/community-page/sub-community-list/community-page-sub-community-list.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from '@angular/core'; -import { CommunityPageSubCommunityListComponent as BaseComponent } - from '../../../../../app/community-page/sub-community-list/community-page-sub-community-list.component'; - -@Component({ - selector: 'ds-community-page-sub-community-list', - // styleUrls: ['./community-page-sub-community-list.component.scss'], - styleUrls: ['../../../../../app/community-page/sub-community-list/community-page-sub-community-list.component.scss'], - // templateUrl: './community-page-sub-community-list.component.html', - templateUrl: '../../../../../app/community-page/sub-community-list/community-page-sub-community-list.component.html' -}) -export class CommunityPageSubCommunityListComponent extends BaseComponent {} diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts index e102089361..3bef57cb18 100644 --- a/src/themes/custom/lazy-theme.module.ts +++ b/src/themes/custom/lazy-theme.module.ts @@ -93,8 +93,8 @@ import { SearchResultsComponent } from './app/shared/search/search-results/searc import { AdminSidebarComponent } from './app/admin/admin-sidebar/admin-sidebar.component'; import { ComcolPageBrowseByComponent } from './app/shared/comcol-page-browse-by/comcol-page-browse-by.component'; import { SearchSettingsComponent } from './app/shared/search/search-settings/search-settings.component'; -import { CommunityPageSubCommunityListComponent } from './app/community-page/sub-community-list/community-page-sub-community-list.component'; -import { CommunityPageSubCollectionListComponent } from './app/community-page/sub-collection-list/community-page-sub-collection-list.component'; +import { CommunityPageSubCommunityListComponent } from './app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component'; +import { CommunityPageSubCollectionListComponent } from './app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component'; import { ObjectListComponent } from './app/shared/object-list/object-list.component'; import { BrowseByMetadataPageComponent } from './app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component'; @@ -299,9 +299,6 @@ const DECLARATIONS = [ RequestCopyModule, ], declarations: DECLARATIONS, - exports: [ - CommunityPageSubCollectionListComponent - ] }) /** From bb0b66f927a368f8bb35329eab242ad57f6f33b7 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 19 Nov 2023 03:39:25 +0100 Subject: [PATCH 70/94] 108588: Moved sub communities & collections from community page to new component --- .../community-page-routing.module.ts | 9 +++++- .../community-page/community-page.module.ts | 5 ++- .../sub-com-col-section.component.html | 8 +++++ .../sub-com-col-section.component.scss | 0 .../sub-com-col-section.component.spec.ts | 32 +++++++++++++++++++ .../sub-com-col-section.component.ts | 28 ++++++++++++++++ 6 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.html create mode 100644 src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.scss create mode 100644 src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.spec.ts create mode 100644 src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.ts diff --git a/src/app/community-page/community-page-routing.module.ts b/src/app/community-page/community-page-routing.module.ts index c37f8832f8..a2d727218e 100644 --- a/src/app/community-page/community-page-routing.module.ts +++ b/src/app/community-page/community-page-routing.module.ts @@ -15,6 +15,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; import { ThemedCommunityPageComponent } from './themed-community-page.component'; import { MenuItemType } from '../shared/menu/menu-item-type.model'; import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; +import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component'; @NgModule({ imports: [ @@ -46,9 +47,15 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; canActivate: [AuthenticatedGuard], }, { - path: '', + path: '**', component: ThemedCommunityPageComponent, pathMatch: 'full', + children: [ + { + path: '', + component: SubComColSectionComponent, + }, + ], } ], data: { diff --git a/src/app/community-page/community-page.module.ts b/src/app/community-page/community-page.module.ts index 499e9092e3..61c17de8fa 100644 --- a/src/app/community-page/community-page.module.ts +++ b/src/app/community-page/community-page.module.ts @@ -20,6 +20,7 @@ import { ThemedCollectionPageSubCollectionListComponent } from './sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component'; import { DsoPageModule } from '../shared/dso-page/dso-page.module'; +import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component'; const DECLARATIONS = [CommunityPageComponent, ThemedCommunityPageComponent, @@ -28,7 +29,9 @@ const DECLARATIONS = [CommunityPageComponent, ThemedCollectionPageSubCollectionListComponent, CommunityPageSubCommunityListComponent, CreateCommunityPageComponent, - DeleteCommunityPageComponent]; + DeleteCommunityPageComponent, + SubComColSectionComponent, +]; @NgModule({ imports: [ diff --git a/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.html b/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.html new file mode 100644 index 0000000000..515e08ffdf --- /dev/null +++ b/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.html @@ -0,0 +1,8 @@ + + + + + + diff --git a/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.scss b/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.spec.ts b/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.spec.ts new file mode 100644 index 0000000000..804299d3d9 --- /dev/null +++ b/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.spec.ts @@ -0,0 +1,32 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SubComColSectionComponent } from './sub-com-col-section.component'; +import { ActivatedRoute } from '@angular/router'; +import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub'; + +describe('SubComColSectionComponent', () => { + let component: SubComColSectionComponent; + let fixture: ComponentFixture; + + let activatedRoute: ActivatedRouteStub; + + beforeEach(async () => { + activatedRoute = new ActivatedRouteStub(); + + await TestBed.configureTestingModule({ + declarations: [ + SubComColSectionComponent, + ], + providers: [ + { provide: ActivatedRoute, useValue: activatedRoute }, + ], + }).compileComponents(); + + fixture = TestBed.createComponent(SubComColSectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.ts b/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.ts new file mode 100644 index 0000000000..ff30e51607 --- /dev/null +++ b/src/app/community-page/sections/sub-com-col-section/sub-com-col-section.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { RemoteData } from '../../../core/data/remote-data'; +import { Community } from '../../../core/shared/community.model'; +import { ActivatedRoute, Data } from '@angular/router'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'ds-sub-com-col-section', + templateUrl: './sub-com-col-section.component.html', + styleUrls: ['./sub-com-col-section.component.scss'], +}) +export class SubComColSectionComponent implements OnInit { + + community$: Observable; + + constructor( + private route: ActivatedRoute, + ) { + } + + ngOnInit(): void { + this.community$ = this.route.data.pipe( + map((data: Data) => (data.dso as RemoteData).payload), + ); + } + +} From 2c543ad570e7e20d287060e41d9f1e8ac721fea7 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Sun, 19 Nov 2023 18:29:27 +0100 Subject: [PATCH 71/94] 108588: Updated CommunityPageRoutingModule to use custom sections --- .../browse-by-metadata-page.component.html | 83 ++++------------ .../community-page-routing.module.ts | 17 +++- .../community-page.component.html | 7 +- .../community-page.component.ts | 8 -- .../community-page/community-page.module.ts | 5 +- .../community-browse-section.component.html | 0 .../comcol-page-browse-by.component.html | 14 ++- .../comcol-page-browse-by.component.ts | 99 ++++++++++++------- 8 files changed, 113 insertions(+), 120 deletions(-) create mode 100644 src/app/community-page/sections/community-browse-section/community-browse-section.component.html diff --git a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html index 8d062d739f..bac74736cf 100644 --- a/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html +++ b/src/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component.html @@ -1,67 +1,24 @@
    - - -
    - -
    - - - - - - - - - - - - - - - -
    - -
    - - -
    - -
    +
    -
    - - -
    -
    - - - - + + +
    -
    -
    -
    +
    diff --git a/src/app/community-page/community-page-routing.module.ts b/src/app/community-page/community-page-routing.module.ts index a2d727218e..f84fa1c9f7 100644 --- a/src/app/community-page/community-page-routing.module.ts +++ b/src/app/community-page/community-page-routing.module.ts @@ -16,6 +16,9 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component' import { MenuItemType } from '../shared/menu/menu-item-type.model'; import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component'; +import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver'; +import { BrowseByGuard } from '../browse-by/browse-by-guard'; +import { BrowseBySwitcherComponent } from '../browse-by/browse-by-switcher/browse-by-switcher.component'; @NgModule({ imports: [ @@ -47,14 +50,24 @@ import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-co canActivate: [AuthenticatedGuard], }, { - path: '**', + path: '', component: ThemedCommunityPageComponent, - pathMatch: 'full', children: [ { path: '', + pathMatch: 'full', component: SubComColSectionComponent, }, + { + path: 'browse/:id', + pathMatch: 'full', + component: BrowseBySwitcherComponent, + canActivate: [BrowseByGuard], + resolve: { + breadcrumb: BrowseByI18nBreadcrumbResolver, + }, + data: { breadcrumbKey: 'browse.metadata' }, + }, ], } ], diff --git a/src/app/community-page/community-page.component.html b/src/app/community-page/community-page.component.html index 6d5262d933..fdefccdd51 100644 --- a/src/app/community-page/community-page.component.html +++ b/src/app/community-page/community-page.component.html @@ -17,7 +17,7 @@ + [title]="'community.page.news'"> @@ -31,10 +31,9 @@ - - + -
    +
    diff --git a/src/app/community-page/community-page.component.ts b/src/app/community-page/community-page.component.ts index a5bbff3cee..206aa54cb0 100644 --- a/src/app/community-page/community-page.component.ts +++ b/src/app/community-page/community-page.component.ts @@ -1,16 +1,10 @@ import { mergeMap, filter, map } from 'rxjs/operators'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; - import { Observable } from 'rxjs'; -import { CommunityDataService } from '../core/data/community-data.service'; import { RemoteData } from '../core/data/remote-data'; import { Bitstream } from '../core/shared/bitstream.model'; - import { Community } from '../core/shared/community.model'; - -import { MetadataService } from '../core/metadata/metadata.service'; - import { fadeInOut } from '../shared/animations/fade'; import { hasValue } from '../shared/empty.util'; import { getAllSucceededRemoteDataPayload} from '../core/shared/operators'; @@ -53,8 +47,6 @@ export class CommunityPageComponent implements OnInit { communityPageRoute$: Observable; constructor( - private communityDataService: CommunityDataService, - private metadata: MetadataService, private route: ActivatedRoute, private router: Router, private authService: AuthService, diff --git a/src/app/community-page/community-page.module.ts b/src/app/community-page/community-page.module.ts index 61c17de8fa..5ebd7c3057 100644 --- a/src/app/community-page/community-page.module.ts +++ b/src/app/community-page/community-page.module.ts @@ -21,8 +21,10 @@ import { } from './sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component'; import { DsoPageModule } from '../shared/dso-page/dso-page.module'; import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component'; +import { BrowseByPageModule } from '../browse-by/browse-by-page.module'; -const DECLARATIONS = [CommunityPageComponent, +const DECLARATIONS = [ + CommunityPageComponent, ThemedCommunityPageComponent, ThemedCommunityPageSubCommunityListComponent, CommunityPageSubCollectionListComponent, @@ -42,6 +44,7 @@ const DECLARATIONS = [CommunityPageComponent, CommunityFormModule, ComcolModule, DsoPageModule, + BrowseByPageModule, ], declarations: [ ...DECLARATIONS diff --git a/src/app/community-page/sections/community-browse-section/community-browse-section.component.html b/src/app/community-page/sections/community-browse-section/community-browse-section.component.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html index 504d9f4bcd..dd7d8d12f7 100644 --- a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -1,5 +1,5 @@

    {{'browse.comcol.head' | translate}}

    -

    diff --git a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.ts b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.ts index 0527d283f0..2a3ec220d6 100644 --- a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.ts +++ b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.ts @@ -1,7 +1,7 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { ActivatedRoute, Params, Router } from '@angular/router'; +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs'; +import { map, take } from 'rxjs/operators'; +import { Router, EventType, Scroll } from '@angular/router'; import { getCommunityPageRoute } from '../../../community-page/community-page-routing-paths'; import { getCollectionPageRoute } from '../../../collection-page/collection-page-routing-paths'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; @@ -26,62 +26,87 @@ export interface ComColPageNavOption { styleUrls: ['./comcol-page-browse-by.component.scss'], templateUrl: './comcol-page-browse-by.component.html' }) -export class ComcolPageBrowseByComponent implements OnInit { +export class ComcolPageBrowseByComponent implements OnDestroy, OnInit { /** * The ID of the Community or Collection */ @Input() id: string; @Input() contentType: string; - allOptions: ComColPageNavOption[]; + allOptions$: Observable; - currentOptionId$: Observable; + currentOption$: BehaviorSubject = new BehaviorSubject(undefined); + + subs: Subscription[] = []; constructor( - private route: ActivatedRoute, - private router: Router, + public router: Router, private browseService: BrowseService ) { } ngOnInit(): void { - this.browseService.getBrowseDefinitions() - .pipe(getFirstCompletedRemoteData>()) - .subscribe((browseDefListRD: RemoteData>) => { + this.allOptions$ = this.browseService.getBrowseDefinitions().pipe( + getFirstCompletedRemoteData(), + map((browseDefListRD: RemoteData>) => { + const allOptions: ComColPageNavOption[] = []; if (browseDefListRD.hasSucceeded) { - this.allOptions = browseDefListRD.payload.page - .map((config: BrowseDefinition) => ({ - id: config.id, - label: `browse.comcol.by.${config.id}`, - routerLink: `/browse/${config.id}`, - params: { scope: this.id } - })); - + let comColRoute: string; if (this.contentType === 'collection') { - this.allOptions = [{ - id: this.id, + comColRoute = getCollectionPageRoute(this.id); + allOptions.push({ + id: 'recent_submissions', label: 'collection.page.browse.recent.head', - routerLink: getCollectionPageRoute(this.id) - }, ...this.allOptions]; + routerLink: comColRoute, + }); } else if (this.contentType === 'community') { - this.allOptions = [{ - id: this.id, + comColRoute = getCommunityPageRoute(this.id); + allOptions.push({ + id: 'comcols', label: 'community.all-lists.head', - routerLink: getCommunityPageRoute(this.id) - }, ...this.allOptions]; + routerLink: comColRoute, + }); + } + + allOptions.push(...browseDefListRD.payload.page.map((config: BrowseDefinition) => ({ + id: `browse_${config.id}`, + label: `browse.comcol.by.${config.id}`, + routerLink: `${comColRoute}/browse/${config.id}`, + }))); + } + return allOptions; + }), + ); + + this.subs.push(combineLatest([ + this.allOptions$, + this.router.events, + ]).subscribe(([navOptions, scrollEvent]: [ComColPageNavOption[], Scroll]) => { + if (scrollEvent.type === EventType.Scroll) { + for (let option of navOptions) { + if (option.routerLink === scrollEvent.routerEvent.urlAfterRedirects.split('?')[0]) { + this.currentOption$.next(option); } } - }); - - this.currentOptionId$ = this.route.params.pipe( - map((params: Params) => params.id) - ); + } + })); } - onSelectChange(newId: string) { - const selectedOption = this.allOptions - .find((option: ComColPageNavOption) => option.id === newId); + ngOnDestroy(): void { + this.subs.forEach((sub: Subscription) => sub.unsubscribe()); + } - this.router.navigate([selectedOption.routerLink], { queryParams: selectedOption.params }); + onSelectChange(event: any): void { + this.allOptions$.pipe( + take(1), + ).subscribe((allOptions: ComColPageNavOption[]) => { + for (let option of allOptions) { + if (option.id === event.target.value) { + this.currentOption$.next(option[0]); + void this.router.navigate([option.routerLink], { queryParams: option.params }); + break; + } + } + }); } } From e66a14cffd6b754b347346f21ed3f034b4733f26 Mon Sep 17 00:00:00 2001 From: Alexandre Vryghem Date: Tue, 5 Dec 2023 00:45:19 +0100 Subject: [PATCH 72/94] 108588: Fixed accessibility issues on ds-comcol-page-browse-by --- .../comcol-page-browse-by.component.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html index dd7d8d12f7..d0fd33a834 100644 --- a/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html +++ b/src/app/shared/comcol/comcol-page-browse-by/comcol-page-browse-by.component.html @@ -2,9 +2,11 @@