Skip to content

Commit 45d0e72

Browse files
authored
Merge pull request #4956 from 4Science/task/main/DURACOM-426
[DSpace-CRIS] Porting of authority framework functionalities (Frontend)
2 parents 13d0272 + f9389d4 commit 45d0e72

File tree

142 files changed

+4667
-341
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

142 files changed

+4667
-341
lines changed

config/config.example.yml

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,12 @@ submission:
237237
- value: default
238238
style: text-muted
239239
icon: fa-circle-xmark
240-
240+
# Icons to be displayed next to an authority controlled value, to give indication of the source.
241+
sourceIcons:
242+
# Example of configuration for authority logo based on sources.
243+
# The configured icon will be displayed next to the authority value in submission and on item page or search results.
244+
- source: orcid
245+
- path: assets/images/orcid.logo.icon.svg
241246
# Fallback language in which the UI will be rendered if the user's browser language is not an active language
242247
fallbackLanguage: en
243248

@@ -397,6 +402,30 @@ item:
397402
pageSize: 5
398403
# Show the bitstream access status label on the item page
399404
showAccessStatuses: false
405+
# Configuration of metadata to be displayed in the item metadata link view popover
406+
metadataLinkViewPopoverData:
407+
# Metdadata list to be displayed for entities without a specific configuration
408+
fallbackMetdataList:
409+
- dc.description.abstract
410+
- dc.description.note
411+
# Configuration for each entity type
412+
entityDataConfig:
413+
- entityType: Person
414+
# Descriptive metadata (popover body)
415+
metadataList:
416+
- person.affiliation.name
417+
- person.email
418+
# Title metadata (popover header)
419+
titleMetadataList:
420+
- person.givenName
421+
- person.familyName
422+
# Configuration for identifier subtypes, based on metadata like dc.identifier.ror where ror is the subtype.
423+
# This is used to map the layout of the identifier in the popover and the icon displayed next to the metadata value.
424+
identifierSubtypes:
425+
- name: ror
426+
icon: assets/images/ror.logo.icon.svg
427+
iconPosition: IdentifierSubtypesIconPositionEnum.LEFT
428+
link: https://ror.org
400429

401430
# Community Page Config
402431
community:
@@ -645,3 +674,62 @@ geospatialMapViewer:
645674
accessibility:
646675
# The duration in days after which the accessibility settings cookie expires
647676
cookieExpirationDuration: 7
677+
678+
# Configuration for layout customization of metadata rendering in Item page
679+
# Currently only the authority reference config is available, more will follow with the integration of the so called CRIS layout.
680+
layout:
681+
# Configuration of icons and styles to be used for each authority controlled link
682+
authorityRef:
683+
- entityType: DEFAULT
684+
entityStyle:
685+
default:
686+
icon: fa fa-user
687+
style: text-info
688+
- entityType: PERSON
689+
entityStyle:
690+
person:
691+
icon: fa fa-user
692+
style: text-success
693+
default:
694+
icon: fa fa-user
695+
style: text-info
696+
- entityType: ORGUNIT
697+
entityStyle:
698+
default:
699+
icon: fa fa-university
700+
style: text-success
701+
- entityType: PROJECT
702+
entityStyle:
703+
default:
704+
icon: fas fa-project-diagram
705+
style: text-success
706+
707+
# Configuration for customization of search results
708+
searchResults:
709+
# Metadata fields to be displayed in the search results under the standard ones
710+
additionalMetadataFields:
711+
- dc.contributor.author
712+
- dc.date.issued
713+
- dc.type
714+
# Metadata fields to be displayed in the search results for the author section
715+
authorMetadata:
716+
- dc.contributor.author
717+
- dc.creator
718+
- dc.contributor.*
719+
# When the search results are retrieved, for each item type the metadata with a valid authority value are inspected.
720+
# Referenced items will be fetched with a find all by id strategy to avoid individual rest requests
721+
# to efficiently display the search results.
722+
followAuthorityMetadata:
723+
- type: Publication
724+
metadata: dc.contributor.author
725+
- type: Product
726+
metadata: dc.contributor.author
727+
728+
# The maximum number of item to process when following authority metadata values.
729+
followAuthorityMaxItemLimit: 100
730+
731+
# The maximum number of metadata values to process for each metadata key
732+
# when following authority metadata values.
733+
followAuthorityMetadataValuesLimit: 5
734+
735+

src/app/browse-by/browse-by-taxonomy/browse-by-taxonomy.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export class BrowseByTaxonomyComponent implements OnInit, OnChanges, OnDestroy {
128128
this.selectedItems = [];
129129
this.facetType = browseDefinition.facetType;
130130
this.vocabularyName = browseDefinition.vocabulary;
131-
this.vocabularyOptions = { name: this.vocabularyName, closed: true };
131+
this.vocabularyOptions = { name: this.vocabularyName, metadata: null, scope: null, closed: true };
132132
this.description = this.translate.instant(`browse.metadata.${this.vocabularyName}.tree.description`);
133133
}));
134134
this.subs.push(this.scope$.subscribe(() => {
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { Injectable } from '@angular/core';
2+
import { FollowAuthorityMetadata } from '@dspace/config/search-follow-metadata.interface';
3+
import {
4+
hasValue,
5+
isNotEmpty,
6+
} from '@dspace/shared/utils/empty.util';
7+
import isArray from 'lodash/isArray';
8+
import {
9+
Observable,
10+
of,
11+
} from 'rxjs';
12+
import {
13+
map,
14+
switchMap,
15+
} from 'rxjs/operators';
16+
import { SearchService } from 'src/app/shared/search/search.service';
17+
18+
import { environment } from '../../../environments/environment';
19+
import { ItemDataService } from '../data/item-data.service';
20+
import { PaginatedList } from '../data/paginated-list.model';
21+
import { RemoteData } from '../data/remote-data';
22+
import { WORKFLOWITEM } from '../eperson/models/workflowitem.resource-type';
23+
import { WORKSPACEITEM } from '../eperson/models/workspaceitem.resource-type';
24+
import { DSpaceObject } from '../shared/dspace-object.model';
25+
import { FollowLinkConfig } from '../shared/follow-link-config.model';
26+
import { Item } from '../shared/item.model';
27+
import { ITEM } from '../shared/item.resource-type';
28+
import { MetadataValue } from '../shared/metadata.models';
29+
import { Metadata } from '../shared/metadata.utils';
30+
import { getFirstCompletedRemoteData } from '../shared/operators';
31+
import { PaginatedSearchOptions } from '../shared/search/models/paginated-search-options.model';
32+
import { SearchObjects } from '../shared/search/models/search-objects.model';
33+
import { BrowseService } from './browse.service';
34+
35+
/**
36+
* The service aims to manage browse requests and subsequent extra fetch requests.
37+
*/
38+
@Injectable({ providedIn: 'root' })
39+
export class SearchManager {
40+
41+
constructor(
42+
protected itemService: ItemDataService,
43+
protected browseService: BrowseService,
44+
protected searchService: SearchService,
45+
) {
46+
}
47+
48+
/**
49+
* Method to retrieve a paginated list of search results from the server
50+
* @param {PaginatedSearchOptions} searchOptions The configuration necessary to perform this search
51+
* @param responseMsToLive The amount of milliseconds for the response to live in cache
52+
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
53+
* no valid cached version. Defaults to true
54+
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
55+
* the response becomes stale
56+
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
57+
* @returns {Observable<RemoteData<SearchObjects<T>>>} Emits a paginated list with all search results found
58+
*/
59+
search<T extends DSpaceObject>(
60+
searchOptions?: PaginatedSearchOptions,
61+
responseMsToLive?: number,
62+
useCachedVersionIfAvailable = true,
63+
reRequestOnStale = true,
64+
...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<SearchObjects<T>>> {
65+
return this.searchService.search(searchOptions, responseMsToLive, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)
66+
.pipe(this.completeSearchObjectsWithExtraData());
67+
}
68+
69+
70+
protected completeWithExtraData() {
71+
return switchMap((itemsRD: RemoteData<PaginatedList<Item>>) => {
72+
if (itemsRD.isSuccess) {
73+
return this.fetchExtraData(itemsRD.payload.page).pipe(map(() => {
74+
return itemsRD;
75+
}));
76+
}
77+
return of(itemsRD);
78+
});
79+
}
80+
81+
protected completeSearchObjectsWithExtraData<T extends DSpaceObject>() {
82+
return switchMap((searchObjectsRD: RemoteData<SearchObjects<T>>) => {
83+
if (searchObjectsRD.isSuccess) {
84+
const items: Item[] = searchObjectsRD.payload.page
85+
.map((searchResult) => isNotEmpty(searchResult?._embedded?.indexableObject) ? searchResult._embedded.indexableObject : searchResult.indexableObject) as any;
86+
return this.fetchExtraData(items).pipe(map(() => {
87+
return searchObjectsRD;
88+
}));
89+
}
90+
return of(searchObjectsRD);
91+
});
92+
}
93+
94+
protected fetchExtraData<T extends DSpaceObject>(objects: T[]): Observable<any> {
95+
96+
const items: Item[] = objects
97+
.map((object: any) => {
98+
if (object.type === ITEM.value) {
99+
return object as Item;
100+
} else if (object.type === WORKSPACEITEM.value || object.type === WORKFLOWITEM.value) {
101+
return object?._embedded?.item as Item;
102+
} else {
103+
// Handle workflow task here, where the item is embedded in a workflowitem
104+
return object?._embedded?.workflowitem?._embedded?.item as Item;
105+
}
106+
107+
})
108+
.filter((item) => hasValue(item));
109+
110+
const uuidList = this.extractUUID(items, environment.searchResult.followAuthorityMetadata, environment.searchResult.followAuthorityMaxItemLimit);
111+
112+
return uuidList.length > 0 ? this.itemService.findAllById(uuidList).pipe(
113+
getFirstCompletedRemoteData(),
114+
map(data => {
115+
if (data.hasSucceeded) {
116+
return of(data);
117+
} else {
118+
of(null);
119+
}
120+
}),
121+
) : of(null);
122+
}
123+
124+
protected extractUUID(items: Item[], metadataToFollow: FollowAuthorityMetadata[], numberOfElementsToReturn?: number): string[] {
125+
const uuidMap = {};
126+
127+
items.forEach((item) => {
128+
metadataToFollow.forEach((followMetadata: FollowAuthorityMetadata) => {
129+
if (item.entityType === followMetadata.type) {
130+
if (isArray(followMetadata.metadata)) {
131+
followMetadata.metadata.forEach((metadata) => {
132+
Metadata.all(item.metadata, metadata, null, null, false, environment.searchResult.followAuthorityMetadataValuesLimit)
133+
.filter((metadataValue: MetadataValue) => Metadata.hasValidItemAuthority(metadataValue.authority))
134+
.forEach((metadataValue: MetadataValue) => uuidMap[metadataValue.authority] = metadataValue);
135+
});
136+
} else {
137+
Metadata.all(item.metadata, followMetadata.metadata, null, null, false, environment.searchResult.followAuthorityMetadataValuesLimit)
138+
.filter((metadataValue: MetadataValue) => Metadata.hasValidItemAuthority(metadataValue.authority))
139+
.forEach((metadataValue: MetadataValue) => uuidMap[metadataValue.authority] = metadataValue);
140+
}
141+
}
142+
});
143+
});
144+
145+
if (hasValue(numberOfElementsToReturn) && numberOfElementsToReturn > 0) {
146+
return Object.keys(uuidMap).slice(0, numberOfElementsToReturn);
147+
}
148+
149+
return Object.keys(uuidMap);
150+
}
151+
}

0 commit comments

Comments
 (0)