Skip to content

Commit 0eb1481

Browse files
VSB-TUO/ORCID enhancement (#1004)
* Merge pull request DSpace#3722 from alexandrevryghem/w2p-119915_made-edit-metadata-tab-fields-dynamic_contribute-main Made edit metadata tab fields dynamic and added entity type support * Added ORCID author editing and working * Working, now clean the code * ORCID enhancement working * Added messages --------- Co-authored-by: Tim Donohue <tim.donohue@lyrasis.org>
1 parent e400945 commit 0eb1481

49 files changed

Lines changed: 2213 additions & 86 deletions

File tree

Some content is hidden

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

src/app/core/shared/context.model.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,12 @@ export enum Context {
3939
MyDSpaceValidation = 'mydspaceValidation',
4040

4141
Bitstream = 'bitstream',
42+
43+
CoarNotify = 'coarNotify',
44+
45+
/**
46+
* The Edit Metadata field Context values that are used in the Edit Item Metadata tab.
47+
*/
48+
AddMetadata = 'addMetadata',
49+
EditMetadata = 'editMetadata',
4250
}

src/app/core/submission/vocabularies/vocabulary.service.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { createPaginatedList } from '../../../shared/testing/utils.test';
2525
import { RequestEntry } from '../../data/request-entry.model';
2626
import { VocabularyDataService } from './vocabulary.data.service';
2727
import { VocabularyEntryDetailsDataService } from './vocabulary-entry-details.data.service';
28+
import { ExternalSourceDataService } from '../../data/external-source-data.service';
2829

2930
describe('VocabularyService', () => {
3031
let scheduler: TestScheduler;
@@ -34,6 +35,7 @@ describe('VocabularyService', () => {
3435
let objectCache: ObjectCacheService;
3536
let halService: HALEndpointService;
3637
let hrefOnlyDataService: HrefOnlyDataService;
38+
let externalSourceDataService: ExternalSourceDataService;
3739
let responseCacheEntry: RequestEntry;
3840

3941
const vocabulary: any = {
@@ -205,11 +207,13 @@ describe('VocabularyService', () => {
205207

206208
function initTestService() {
207209
hrefOnlyDataService = getMockHrefOnlyDataService();
210+
externalSourceDataService = jasmine.createSpyObj('ExternalSourceDataService', ['findAll', 'findById']);
208211

209212
return new VocabularyService(
210213
requestService,
211214
new VocabularyDataService(requestService, rdbService, objectCache, halService),
212215
new VocabularyEntryDetailsDataService(requestService, rdbService, objectCache, halService),
216+
externalSourceDataService,
213217
);
214218
}
215219

src/app/core/submission/vocabularies/vocabulary.service.ts

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import { Injectable } from '@angular/core';
2-
import { Observable } from 'rxjs';
3-
import { map, switchMap, mergeMap } from 'rxjs/operators';
2+
import { Observable, of } from 'rxjs';
3+
import { map, switchMap, mergeMap, catchError } from 'rxjs/operators';
44
import { FollowLinkConfig, followLink } from '../../../shared/utils/follow-link-config.model';
55
import { RequestService } from '../../data/request.service';
66
import { RemoteData } from '../../data/remote-data';
77
import { PaginatedList } from '../../data/paginated-list.model';
88
import { Vocabulary } from './models/vocabulary.model';
99
import { VocabularyEntry } from './models/vocabulary-entry.model';
1010
import { isNotEmpty } from '../../../shared/empty.util';
11+
import { ExternalSourceDataService } from '../../data/external-source-data.service';
12+
import { ExternalSourceEntry } from '../../shared/external-source-entry.model';
13+
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
14+
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
1115
import {
1216
getFirstSucceededRemoteDataPayload,
1317
getFirstSucceededRemoteListPayload,
@@ -20,6 +24,8 @@ import { PageInfo } from '../../shared/page-info.model';
2024
import { FindListOptions } from '../../data/find-list-options.model';
2125
import { VocabularyEntryDetailsDataService } from './vocabulary-entry-details.data.service';
2226
import { VocabularyDataService } from './vocabulary.data.service';
27+
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
28+
import { buildPaginatedList } from '../../data/paginated-list.model';
2329

2430
/**
2531
* A service responsible for fetching/sending data from/to the REST API on the vocabularies endpoint
@@ -32,6 +38,7 @@ export class VocabularyService {
3238
protected requestService: RequestService,
3339
protected vocabularyDataService: VocabularyDataService,
3440
protected vocabularyEntryDetailDataService: VocabularyEntryDetailsDataService,
41+
protected externalSourceDataService: ExternalSourceDataService,
3542
) {
3643
}
3744

@@ -124,6 +131,61 @@ export class VocabularyService {
124131
* Return an observable that emits object list
125132
*/
126133
getVocabularyEntriesByValue(value: string, exact: boolean, vocabularyOptions: VocabularyOptions, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<VocabularyEntry>>> {
134+
// Handle authority fields specially for DSpace 7 compatibility
135+
const authorityFields = ['dc.contributor.author', 'dc.creator', 'dc.contributor.editor', 'dc.contributor.advisor'];
136+
if (authorityFields.includes(vocabularyOptions.name)) {
137+
// For authority fields, use ORCID external source if there's search text
138+
if (value && value.length >= 2) {
139+
// Create search options for ORCID
140+
const paginationOptions = Object.assign(new PaginationComponentOptions(), {
141+
id: 'orcid-search',
142+
currentPage: pageInfo.currentPage || 1,
143+
pageSize: pageInfo.elementsPerPage || 10
144+
});
145+
146+
const searchOptions = new PaginatedSearchOptions({
147+
query: value,
148+
pagination: paginationOptions
149+
});
150+
151+
// Use ORCID external source (try both orcid and orcidV2)
152+
return this.externalSourceDataService.getExternalSourceEntries('orcid', searchOptions).pipe(
153+
switchMap((orcidResponse: RemoteData<PaginatedList<ExternalSourceEntry>>) => {
154+
if (orcidResponse.hasSucceeded && orcidResponse.payload && orcidResponse.payload.page.length > 0) {
155+
// Convert ExternalSourceEntry to VocabularyEntry
156+
const vocabularyEntries: VocabularyEntry[] = orcidResponse.payload.page.map(entry => {
157+
const vocabEntry = new VocabularyEntry();
158+
// Display shows author name + ORCID for selection
159+
vocabEntry.display = `${entry.display} (ORCID: ${entry.id})`;
160+
// Value should be just the author name (what goes in the main field)
161+
vocabEntry.value = entry.display;
162+
// Authority is the ORCID ID (what goes in the authority field)
163+
vocabEntry.authority = entry.id;
164+
vocabEntry.otherInformation = { orcid: entry.id };
165+
return vocabEntry;
166+
});
167+
168+
const resultList = buildPaginatedList(pageInfo, vocabularyEntries);
169+
return createSuccessfulRemoteDataObject$(resultList);
170+
} else {
171+
// Return empty list if no results
172+
const emptyList = buildPaginatedList(new PageInfo(), []);
173+
return createSuccessfulRemoteDataObject$(emptyList);
174+
}
175+
}),
176+
catchError(() => {
177+
// On error, return empty list
178+
const emptyList = buildPaginatedList(new PageInfo(), []);
179+
return createSuccessfulRemoteDataObject$(emptyList);
180+
})
181+
);
182+
} else {
183+
// Return empty list if no search text
184+
const emptyList = buildPaginatedList(new PageInfo(), []);
185+
return createSuccessfulRemoteDataObject$(emptyList);
186+
}
187+
}
188+
127189
const options: VocabularyFindOptions = new VocabularyFindOptions(
128190
null,
129191
value,

src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-field-values/dso-edit-metadata-field-values.component.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
<ds-dso-edit-metadata-value-headers role="presentation" [dsoType]="dsoType"></ds-dso-edit-metadata-value-headers>
33
<ds-dso-edit-metadata-value *ngFor="let mdValue of form.fields[mdField]; let idx = index" role="presentation"
44
[dso]="dso"
5+
[context]="Context.EditMetadata"
56
[mdValue]="mdValue"
7+
[mdField]="mdField"
68
[dsoType]="dsoType"
79
[saving$]="saving$"
810
[isOnlyValue]="form.fields[mdField].length === 1"

src/app/dso-shared/dso-edit-metadata/dso-edit-metadata-field-values/dso-edit-metadata-field-values.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Observable } from 'rxjs/internal/Observable';
44
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
55
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
66
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
7+
import { Context } from '../../../core/shared/context.model';
78

89
@Component({
910
selector: 'ds-dso-edit-metadata-field-values',
@@ -57,6 +58,8 @@ export class DsoEditMetadataFieldValuesComponent {
5758
*/
5859
public DsoEditMetadataChangeTypeEnum = DsoEditMetadataChangeType;
5960

61+
public readonly Context = Context;
62+
6063
/**
6164
* Drop a value into a new position
6265
* Update the form's value array for the current field to match the dropped position

0 commit comments

Comments
 (0)