Merge branch CST-5249_suggestion of https://github.com/4Science/DSpace into CST-11299

This commit is contained in:
Francesco Bacchelli
2023-08-22 11:16:30 +02:00
parent f31d4d5276
commit 9b556fd703
27 changed files with 193 additions and 62 deletions

View File

@@ -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<T>(obj))
.map((obj: any) =>
this.linkService.resolveLinks(obj, ...pageLink.linksToFollow)

View File

@@ -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({

View File

@@ -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
*

View File

@@ -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');

View File

@@ -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';

View File

@@ -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');

View File

@@ -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';

View File

@@ -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';

View File

@@ -168,8 +168,8 @@ export class SuggestionsDataService {
...linksToFollow: FollowLinkConfig<SuggestionTarget>[]
): Observable<RemoteData<PaginatedList<SuggestionTarget>>> {
options.searchParams = [new RequestParam('target', userId)];
return this.suggestionTargetsDataService.getTargetsByUser(this.searchFindByTargetMethod, options, ...linksToFollow);
//return this.suggestionTargetsDataService.getTargetsByUser(this.searchFindByTargetMethod, options, ...linksToFollow);
return this.suggestionTargetsDataService.getTargetsByUser(userId, options, ...linksToFollow);
}
/**

View File

@@ -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)

View File

@@ -7,3 +7,4 @@
<ds-themed-top-level-community-list></ds-themed-top-level-community-list>
<ds-recent-item-list *ngIf="recentSubmissionspageSize>0"></ds-recent-item-list>
</div>
<ds-suggestions-popup></ds-suggestions-popup>

View File

@@ -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,
],

View File

@@ -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<boolean> {
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',

View File

@@ -1,5 +1,6 @@
<div class="container">
<ds-my-dspace-new-submission *dsShowOnlyForRole="[roleTypeEnum.Submitter]"></ds-my-dspace-new-submission>
<ds-suggestions-notification></ds-suggestions-notification>
</div>
<ds-themed-search *ngIf="configuration && context"

View File

@@ -15,6 +15,7 @@ import { MyDSpaceNewExternalDropdownComponent } from './my-dspace-new-submission
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
import { SearchModule } from '../shared/search/search.module';
import { UploadModule } from '../shared/upload/upload.module';
import { SuggestionNotificationsModule } from '../suggestion-notifications/suggestion-notifications.module';
const DECLARATIONS = [
MyDSpacePageComponent,
@@ -26,14 +27,15 @@ const DECLARATIONS = [
];
@NgModule({
imports: [
CommonModule,
SharedModule,
SearchModule,
MyDspacePageRoutingModule,
MyDspaceSearchModule.withEntryComponents(),
UploadModule,
],
imports: [
CommonModule,
SharedModule,
SearchModule,
MyDspacePageRoutingModule,
MyDspaceSearchModule.withEntryComponents(),
UploadModule,
SuggestionNotificationsModule,
],
declarations: DECLARATIONS,
providers: [
MyDSpaceGuard,

View File

@@ -8,6 +8,7 @@
<div class="mb-4">
<ds-profile-page-researcher-form [user]="user" ></ds-profile-page-researcher-form>
</div>
<ds-suggestions-notification></ds-suggestions-notification>
</div>
</div>
</ng-container>

View File

@@ -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,

View File

@@ -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<DSpaceObject>;
/**
* 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<DSpaceObject> = new EventEmitter<DSpaceObject>();
/**
* 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);
}
}
/**

View File

@@ -27,7 +27,7 @@ export function reciterSuggestionTargetStateSelector(): MemoizedSelector<Suggest
/**
* Returns the Reciter Suggestion Targets list.
* @function reciterSuggestionTargetObjectSelector
* @return {OpenaireReciterSuggestionTarget[]}
* @return {SuggestionTarget[]}
*/
export function reciterSuggestionTargetObjectSelector(): MemoizedSelector<SuggestionNotificationsState, SuggestionTarget[]> {
return subStateSelector<SuggestionNotificationsState, SuggestionTarget[]>(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
);

View File

@@ -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,

View File

@@ -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)

View File

@@ -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<boolean> {
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<number> {
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<number> {
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<number> {
return this.store.pipe(select(getreciterSuggestionTargetTotalsSelector));
return this.store.pipe(select(getReciterSuggestionTargetTotalsSelector));
}
/**

View File

@@ -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(),

View File

@@ -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(

View File

@@ -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
];

View File

@@ -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 <b>{{count}} publications</b><br> in the {{source}} that seems to be related to your profile.<br> Please <a href='/suggestions/{{suggestionId}}'>review the suggestions</a>",
"mydspace.notification.suggestion.page": "We found <b>{{count}} {{type}}</b> in the {{source}} that seems to be related to your profile. Please <a href='/suggestions/{{suggestionId}}'>review the suggestions.</a>",
"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. <a href='/workspaceitems/{{workspaceItemId}}/edit'>View.</a>",
"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",

View File

@@ -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: [