Skip to content

Commit 948907f

Browse files
FrancescoMolinaroatarix83
authored andcommitted
Merged in task/dspace-cris-2023_02_x/DSC-1956 (pull request DSpace#2401)
Task/dspace cris 2023 02 x/DSC-1956 Approved-by: Giuseppe Digilio
2 parents f90ef54 + 23a7d86 commit 948907f

36 files changed

Lines changed: 1195 additions & 132 deletions

src/app/app.effects.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import { NavbarEffects } from './navbar/navbar.effects';
44
import { SidebarEffects } from './shared/sidebar/sidebar-effects.service';
55
import { RelationshipEffects } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects';
66
import { ThemeEffects } from './shared/theme-support/theme.effects';
7+
import { AuthorizationEffects } from './core/data/feature-authorization/authorization.effects';
78

89
export const appEffects = [
910
StoreEffects,
1011
NavbarEffects,
1112
NotificationsEffects,
1213
SidebarEffects,
1314
ThemeEffects,
14-
RelationshipEffects
15+
RelationshipEffects,
16+
AuthorizationEffects
1517
];

src/app/app.reducer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ import {
5353
editItemRelationshipsReducer,
5454
EditItemRelationshipsState
5555
} from './edit-item-relationships/edit-item-relationships.reducer';
56+
import { AuthorizationsState } from './core/data/feature-authorization/authorization.interfaces';
57+
import { authorizationReducer} from './core/data/feature-authorization/authorization.reducer';
5658

5759
export interface AppState {
5860
router: RouterReducerState;
@@ -76,6 +78,7 @@ export interface AppState {
7678
correlationId: string;
7779
contextHelp: ContextHelpState;
7880
editItemRelationships: EditItemRelationshipsState;
81+
authorizationFeatures: AuthorizationsState;
7982
}
8083

8184
export const appReducers: ActionReducerMap<AppState> = {
@@ -99,7 +102,8 @@ export const appReducers: ActionReducerMap<AppState> = {
99102
correlationId: correlationIdReducer,
100103
contextHelp: contextHelpReducer,
101104
statistics: StatisticsReducer,
102-
editItemRelationships: editItemRelationshipsReducer
105+
editItemRelationships: editItemRelationshipsReducer,
106+
authorizationFeatures: authorizationReducer
103107
};
104108

105109
export const routerStateSelector = (state: AppState) => state.router;

src/app/breadcrumbs/breadcrumb/breadcrumb-tooltip.pipe.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/app/breadcrumbs/breadcrumb/truncate-breadcrumb-item-characters.pipe.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/app/core/browse/search-manager.ts

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Injectable } from '@angular/core';
1+
import { Inject, Injectable } from '@angular/core';
22
import { Observable, of } from 'rxjs';
3-
import { map, switchMap } from 'rxjs/operators';
3+
import { filter, map, switchMap } from 'rxjs/operators';
44
import { PaginatedList } from '../data/paginated-list.model';
55
import { RemoteData } from '../data/remote-data';
66
import { Item } from '../shared/item.model';
@@ -22,6 +22,12 @@ import isArray from 'lodash/isArray';
2222
import { WORKSPACEITEM } from '../eperson/models/workspaceitem.resource-type';
2323
import { WORKFLOWITEM } from '../eperson/models/workflowitem.resource-type';
2424
import { ITEM } from '../shared/item.resource-type';
25+
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
26+
import { FeatureID } from '../data/feature-authorization/feature-id';
27+
import { SearchOptions } from '../../shared/search/models/search-options.model';
28+
29+
30+
import { AuthorizationService } from '../data/feature-authorization/authorization.service';
2531

2632
/**
2733
* The service aims to manage browse requests and subsequent extra fetch requests.
@@ -33,6 +39,8 @@ export class SearchManager {
3339
protected itemService: ItemDataService,
3440
protected browseService: BrowseService,
3541
protected searchService: SearchService,
42+
protected authorizationService: AuthorizationService,
43+
@Inject(APP_CONFIG) protected appConfig: AppConfig
3644
) {
3745
}
3846

@@ -69,7 +77,7 @@ export class SearchManager {
6977
...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<SearchObjects<T>>> {
7078
const optionsWithDefaultProjection = Object.assign(new PaginatedSearchOptions({}), searchOptions, { projection: searchOptions.projection ?? 'preventMetadataSecurity' });
7179
return this.searchService.search(optionsWithDefaultProjection, responseMsToLive, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)
72-
.pipe(this.completeSearchObjectsWithExtraData());
80+
.pipe(this.completeSearchObjectsWithExtraData(optionsWithDefaultProjection));
7381
}
7482

7583

@@ -84,19 +92,107 @@ export class SearchManager {
8492
});
8593
}
8694

87-
protected completeSearchObjectsWithExtraData<T extends DSpaceObject>() {
95+
protected completeSearchObjectsWithExtraData<T extends DSpaceObject>(searchOptions: SearchOptions) {
8896
return switchMap((searchObjectsRD: RemoteData<SearchObjects<T>>) => {
8997
if (searchObjectsRD.isSuccess) {
9098
const items: Item[] = searchObjectsRD.payload.page
9199
.map((searchResult) => isNotEmpty(searchResult?._embedded?.indexableObject) ? searchResult._embedded.indexableObject : searchResult.indexableObject) as any;
92-
return this.fetchExtraData(items).pipe(map(() => {
93-
return searchObjectsRD;
94-
}));
100+
return this.fetchExtraData(items).pipe(
101+
switchMap(() => this.fetchConfiguredAuthorizations(searchObjectsRD, searchOptions.configuration ?? 'default')),
102+
map(() => {
103+
return searchObjectsRD;
104+
}),
105+
);
95106
}
96107
return of(searchObjectsRD);
97108
});
98109
}
99110

111+
/**
112+
* Retrieve configured authorizations related to current discovery configuration
113+
*
114+
* @param searchObjects
115+
* @param configuration
116+
* @protected
117+
*/
118+
protected fetchConfiguredAuthorizations<T extends DSpaceObject>(searchObjects: RemoteData<SearchObjects<T>>, configuration: string): Observable<any> {
119+
const objects = searchObjects.payload.page.map((searchResult) => searchResult.indexableObject) as any;
120+
const mappedObjects = this.getConfiguredAuthorizationsMap(objects, configuration);
121+
122+
if ([...mappedObjects.keys()].length === 0) {
123+
return of(searchObjects);
124+
}
125+
126+
const uiidListsMappedToAuthorizations = this.groupItemsUuidsByAuthorizations(objects, mappedObjects);
127+
[...uiidListsMappedToAuthorizations.keys()].forEach((features) => {
128+
const uuidList = uiidListsMappedToAuthorizations.get(features);
129+
const type = objects.find(object => object.id === uuidList[0]).uniqueType;
130+
131+
this.authorizationService.initStateForObjects(uuidList, type, features);
132+
});
133+
134+
return this.authorizationService.isLoading().pipe(
135+
filter(loading => !loading),
136+
map(() => {
137+
return searchObjects;
138+
}),
139+
);
140+
}
141+
142+
/**
143+
* Group items by authorization ID in a map
144+
*
145+
* @param objects
146+
* @param mappedEntities
147+
* @private
148+
*/
149+
private groupItemsUuidsByAuthorizations<T extends DSpaceObject>(objects: T[], mappedEntities: Map<string, FeatureID[]>): Map<FeatureID[], string[]> {
150+
const mappedUuidListsToFeatures = new Map();
151+
152+
objects.forEach(object => {
153+
const objectType = object.uniqueType;
154+
const features = mappedEntities.get(objectType);
155+
156+
if (hasValue(features) && hasValue(mappedUuidListsToFeatures.get(features))) {
157+
mappedUuidListsToFeatures.set(features, [...mappedUuidListsToFeatures.get(features), object.id]);
158+
} else if (hasValue(features)) {
159+
mappedUuidListsToFeatures.set(features, [object.id]);
160+
}
161+
});
162+
163+
return mappedUuidListsToFeatures;
164+
}
165+
166+
/**
167+
* Map entity types oe unique type to required authorizations so that we can group the items by feature
168+
*
169+
* @param objects
170+
* @param configuration
171+
* @private
172+
*/
173+
private getConfiguredAuthorizationsMap<T extends DSpaceObject>(objects: T[], configuration: string): Map<string, FeatureID[]> {
174+
const configuredAuthorizationsForDiscovery =
175+
this.appConfig.discoveryAuthorizationFeaturesConfig[configuration] ?? this.appConfig.discoveryAuthorizationFeaturesConfig.default;
176+
const configuredAuthorizationsToType = new Map();
177+
178+
if (!hasValue(configuredAuthorizationsForDiscovery)) {
179+
return configuredAuthorizationsToType;
180+
}
181+
182+
const objectUniqueTypes = [...new Set(objects.map(dso => dso?.uniqueType))];
183+
184+
objectUniqueTypes.forEach((entity) => {
185+
const config = configuredAuthorizationsForDiscovery[entity];
186+
187+
if (hasValue(config)) {
188+
configuredAuthorizationsToType.set(entity, config);
189+
}
190+
});
191+
192+
return configuredAuthorizationsToType;
193+
}
194+
195+
100196
protected fetchExtraData<T extends DSpaceObject>(objects: T[]): Observable<any> {
101197

102198
const items: Item[] = objects

src/app/core/browse/search.manager.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { MetadataValue } from '../shared/metadata.models';
1313
import { v4 as uuidv4 } from 'uuid';
1414
import { AUTHORITY_REFERENCE } from '../shared/metadata.utils';
1515
import { ITEM } from '../shared/item.resource-type';
16+
import { AppConfig } from '../../../config/app-config.interface';
17+
import { FeatureID } from '../data/feature-authorization/feature-id';
1618

1719
describe('SearchManager', () => {
1820
let scheduler: TestScheduler;
@@ -93,6 +95,20 @@ describe('SearchManager', () => {
9395
type: ITEM.value
9496
});
9597

98+
const appConfig: Partial<AppConfig> = {
99+
discoveryAuthorizationFeaturesConfig: {},
100+
followAuthorityMetadata: [
101+
{
102+
type: 'Publication',
103+
metadata: ['dc.contributor.author']
104+
},
105+
{
106+
type: 'Product',
107+
metadata: ['dc.contributor.author']
108+
}
109+
]
110+
};
111+
96112
const mockBrowseService: any = {
97113
getBrowseItemsFor: (options: BrowseEntrySearchOptions) =>
98114
toRemoteData([firstPublication, secondPublication, firstProject]),
@@ -110,8 +126,13 @@ describe('SearchManager', () => {
110126
of(createSuccessfulRemoteDataObject(createPaginatedList([])))
111127
};
112128

129+
const mockAuthorizationService: any = {
130+
getObjectsAuthorizations: (uuidList: string[], uniqueType: string, featuresId?: FeatureID[]) =>
131+
of([])
132+
};
133+
113134
function initTestService() {
114-
return new SearchManager(mockItemService, mockBrowseService, mockSearchService);
135+
return new SearchManager(mockItemService, mockBrowseService, mockSearchService, mockAuthorizationService, appConfig as AppConfig);
115136
}
116137

117138
beforeEach(() => {

0 commit comments

Comments
 (0)