diff --git a/src/app/core/submission/submission-response-parsing.service.ts b/src/app/core/submission/submission-response-parsing.service.ts index afabde831a..b740cde639 100644 --- a/src/app/core/submission/submission-response-parsing.service.ts +++ b/src/app/core/submission/submission-response-parsing.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { deepClone } from 'fast-json-patch'; import { DSOResponseParsingService } from '../data/dso-response-parsing.service'; @@ -113,7 +113,7 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService return new ErrorResponse( Object.assign( new Error('Unexpected response from server'), - {statusCode: data.statusCode, statusText: data.statusText} + { statusCode: data.statusCode, statusText: data.statusText } ) ); } @@ -133,7 +133,7 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService processedList.forEach((item) => { - item = Object.assign({}, item); + // item = Object.assign({}, item); // In case data is an Instance of WorkspaceItem normalize field value of all the section of type form if (item instanceof WorkspaceItem || item instanceof WorkflowItem) { diff --git a/src/app/core/submission/vocabularies/models/vocabulary-find-options.model.ts b/src/app/core/submission/vocabularies/models/vocabulary-find-options.model.ts index 18309ed1fe..9dc12fad57 100644 --- a/src/app/core/submission/vocabularies/models/vocabulary-find-options.model.ts +++ b/src/app/core/submission/vocabularies/models/vocabulary-find-options.model.ts @@ -8,16 +8,15 @@ import { isNotEmpty } from '../../../../shared/empty.util'; */ export class VocabularyFindOptions extends FindListOptions { - constructor(public collection: string = '', - public name: string = '', - public metadata: string = '', + constructor(public collection, + public metadata, public query: string = '', + public filter?: string, + public exact?: boolean, + public entryID?: string, public elementsPerPage?: number, public currentPage?: number, - public sort?: SortOptions, - public filter?: string, - public exact?: string, - public entryID?: string, + public sort?: SortOptions ) { super(); @@ -35,7 +34,7 @@ export class VocabularyFindOptions extends FindListOptions { searchParams.push(new RequestParam('filter', filter)) } if (isNotEmpty(exact)) { - searchParams.push(new RequestParam('exact', exact)) + searchParams.push(new RequestParam('exact', exact.toString())) } if (isNotEmpty(entryID)) { searchParams.push(new RequestParam('entryID', entryID)) diff --git a/src/app/core/submission/vocabularies/vocabulary.service.spec.ts b/src/app/core/submission/vocabularies/vocabulary.service.spec.ts index da496899d1..9e6d3b200e 100644 --- a/src/app/core/submission/vocabularies/vocabulary.service.spec.ts +++ b/src/app/core/submission/vocabularies/vocabulary.service.spec.ts @@ -9,7 +9,7 @@ import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.s import { ObjectCacheService } from '../../cache/object-cache.service'; import { HALEndpointService } from '../../shared/hal-endpoint.service'; import { RequestService } from '../../data/request.service'; -import { VocabularyEntriesRequest } from '../../data/request.models'; +import { FindListOptions, VocabularyEntriesRequest } from '../../data/request.models'; import { RequestParam } from '../../cache/models/request-param.model'; import { PageInfo } from '../../shared/page-info.model'; import { PaginatedList } from '../../data/paginated-list'; @@ -19,7 +19,7 @@ import { RestResponse } from '../../cache/response.models'; import { VocabularyService } from './vocabulary.service'; import { getMockRequestService } from '../../../shared/mocks/request.service.mock'; import { getMockRemoteDataBuildService } from '../../../shared/mocks/remote-data-build.service.mock'; -import { VocabularyFindOptions } from './models/vocabulary-find-options.model'; +import { VocabularyOptions } from './models/vocabulary-options.model'; describe('VocabularyService', () => { let scheduler: TestScheduler; @@ -143,10 +143,18 @@ describe('VocabularyService', () => { const vocabularyId = 'types'; const metadata = 'dc.type'; const collectionUUID = '8b39g7ya-5a4b-438b-851f-be1d5b4a1c5a'; - const vocabularyOptions = new VocabularyFindOptions(collectionUUID, vocabularyId, metadata); + const entryID = 'dsfsfsdf-5a4b-438b-851f-be1d5b4a1c5a'; const searchRequestURL = `https://rest.api/rest/api/submission/vocabularies/search/byMetadataAndCollection?metadata=${metadata}&collection=${collectionUUID}`; const entriesRequestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}/entries?metadata=${metadata}&collection=${collectionUUID}`; - + const entriesByValueRequestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}/entries?metadata=${metadata}&collection=${collectionUUID}&filter=test&exact=false`; + const entryByValueRequestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}/entries?metadata=${metadata}&collection=${collectionUUID}&filter=test&exact=true`; + const entryByIDRequestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}/entries?metadata=${metadata}&collection=${collectionUUID}&entryID=${entryID}`; + const vocabularyOptions: VocabularyOptions = { + name: vocabularyId, + metadata: metadata, + scope: collectionUUID, + closed: false + } const pageInfo = new PageInfo(); const array = [vocabulary, hierarchicalVocabulary]; const arrayEntries = [vocabularyEntryDetail, anotherVocabularyEntryDetail]; @@ -274,11 +282,6 @@ describe('VocabularyService', () => { describe('searchVocabularyByMetadataAndCollection', () => { it('should proxy the call to vocabularyDataService.findVocabularyByHref', () => { - const options = new VocabularyFindOptions(); - options.searchParams = [ - new RequestParam('metadata', metadata), - new RequestParam('collection', collectionUUID) - ]; scheduler.schedule(() => service.searchVocabularyByMetadataAndCollection(vocabularyOptions).subscribe()); scheduler.flush(); @@ -307,20 +310,103 @@ describe('VocabularyService', () => { it('should configure a new VocabularyEntriesRequest', () => { const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entriesRequestURL); - scheduler.schedule(() => service.getVocabularyEntries(vocabularyOptions).subscribe()); + scheduler.schedule(() => service.getVocabularyEntries(vocabularyOptions, pageInfo).subscribe()); scheduler.flush(); expect(requestService.configure).toHaveBeenCalledWith(expected); }); it('should call RemoteDataBuildService to create the RemoteData Observable', () => { - service.getVocabularyEntries(vocabularyOptions); + service.getVocabularyEntries(vocabularyOptions, pageInfo); expect(rdbService.toRemoteDataObservable).toHaveBeenCalled(); }); }); + + describe('', () => { + + beforeEach(() => { + requestService = getMockRequestService(getRequestEntry$(true)); + rdbService = getMockRemoteDataBuildService(); + spyOn(rdbService, 'toRemoteDataObservable').and.callThrough(); + service = initTestService(); + }); + + describe('getVocabularyEntries', () => { + it('should configure a new VocabularyEntriesRequest', () => { + const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entriesRequestURL); + + scheduler.schedule(() => service.getVocabularyEntries(vocabularyOptions, pageInfo).subscribe()); + scheduler.flush(); + + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + + it('should call RemoteDataBuildService to create the RemoteData Observable', () => { + service.getVocabularyEntries(vocabularyOptions, pageInfo); + + expect(rdbService.toRemoteDataObservable).toHaveBeenCalled(); + + }); + }); + + describe('getVocabularyEntriesByValue', () => { + it('should configure a new VocabularyEntriesRequest', () => { + const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entriesByValueRequestURL); + + scheduler.schedule(() => service.getVocabularyEntriesByValue('test', false, vocabularyOptions, pageInfo).subscribe()); + scheduler.flush(); + + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + + it('should call RemoteDataBuildService to create the RemoteData Observable', () => { + service.getVocabularyEntriesByValue('test', false, vocabularyOptions, pageInfo); + + expect(rdbService.toRemoteDataObservable).toHaveBeenCalled(); + + }); + }); + + describe('getVocabularyEntryByValue', () => { + it('should configure a new VocabularyEntriesRequest', () => { + const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entryByValueRequestURL); + + scheduler.schedule(() => service.getVocabularyEntryByValue('test', vocabularyOptions).subscribe()); + scheduler.flush(); + + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + + it('should call RemoteDataBuildService to create the RemoteData Observable', () => { + service.getVocabularyEntryByValue('test', vocabularyOptions); + + expect(rdbService.toRemoteDataObservable).toHaveBeenCalled(); + + }); + }); + + describe('getVocabularyEntryByID', () => { + it('should configure a new VocabularyEntriesRequest', () => { + const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entryByIDRequestURL); + + scheduler.schedule(() => service.getVocabularyEntryByID(entryID, vocabularyOptions).subscribe()); + scheduler.flush(); + + expect(requestService.configure).toHaveBeenCalledWith(expected); + }); + + it('should call RemoteDataBuildService to create the RemoteData Observable', () => { + service.getVocabularyEntryByID('test', vocabularyOptions); + + expect(rdbService.toRemoteDataObservable).toHaveBeenCalled(); + + }); + }); + + }); }); describe('vocabularyEntryDetails endpoint', () => { @@ -400,16 +486,16 @@ describe('VocabularyService', () => { describe('searchByGroup', () => { it('should proxy the call to vocabularyEntryDetailDataService.searchBy', () => { - const options = new VocabularyFindOptions(); - options.searchParams.push(new RequestParam('vocabulary', 'srsc')); - scheduler.schedule(() => service.searchTopEntries('srsc', new VocabularyFindOptions())); + const options = new FindListOptions(); + options.searchParams = [new RequestParam('vocabulary', 'srsc')]; + scheduler.schedule(() => service.searchTopEntries('srsc', pageInfo)); scheduler.flush(); expect((service as any).vocabularyEntryDetailDataService.searchBy).toHaveBeenCalledWith((service as any).searchTopMethod, options); }); it('should return a RemoteData) for the search', () => { - const result = service.searchTopEntries('srsc', new VocabularyFindOptions()); + const result = service.searchTopEntries('srsc', pageInfo); const expected = cold('a|', { a: entriesPaginatedListRD }); diff --git a/src/app/core/submission/vocabularies/vocabulary.service.ts b/src/app/core/submission/vocabularies/vocabulary.service.ts index bd5c5b6c48..5a3f357881 100644 --- a/src/app/core/submission/vocabularies/vocabulary.service.ts +++ b/src/app/core/submission/vocabularies/vocabulary.service.ts @@ -22,12 +22,19 @@ import { PaginatedList } from '../../data/paginated-list'; import { Vocabulary } from './models/vocabulary.model'; import { VOCABULARY } from './models/vocabularies.resource-type'; import { VocabularyEntry } from './models/vocabulary-entry.model'; -import { hasValue, isNotEmptyOperator } from '../../../shared/empty.util'; -import { configureRequest, filterSuccessfulResponses, getRequestFromRequestHref } from '../../shared/operators'; +import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util'; +import { + configureRequest, + filterSuccessfulResponses, + getFirstSucceededRemoteListPayload, + getRequestFromRequestHref +} from '../../shared/operators'; import { GenericSuccessResponse } from '../../cache/response.models'; import { VocabularyFindOptions } from './models/vocabulary-find-options.model'; import { VocabularyEntryDetail } from './models/vocabulary-entry-detail.model'; import { RequestParam } from '../../cache/models/request-param.model'; +import { VocabularyOptions } from './models/vocabulary-options.model'; +import { PageInfo } from '../../shared/page-info.model'; /* tslint:disable:max-classes-per-file */ @@ -110,20 +117,20 @@ export class VocabularyService { /** * Returns an observable of {@link RemoteData} of a {@link Vocabulary}, based on its ID, with a list of {@link FollowLinkConfig}, * to automatically resolve {@link HALLink}s of the object - * @param id ID of {@link Vocabulary} we want to retrieve + * @param name The name of {@link Vocabulary} we want to retrieve * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved * @return {Observable>} * Return an observable that emits vocabulary object */ - findVocabularyById(id: string, ...linksToFollow: Array>): Observable> { - return this.vocabularyDataService.findById(id, ...linksToFollow); + findVocabularyById(name: string, ...linksToFollow: Array>): Observable> { + return this.vocabularyDataService.findById(name, ...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 options Find list options object * @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 @@ -135,28 +142,131 @@ export class VocabularyService { /** * Return the {@link VocabularyEntry} list for a given {@link Vocabulary} * - * @param options The {@link VocabularyFindOptions} for the request + * @param vocabularyOptions The {@link VocabularyOptions} for the request to which the entries belong + * @param pageInfo The {@link PageInfo} for the request * @return {Observable>>} * Return an observable that emits object list */ - getVocabularyEntries(options: VocabularyFindOptions): Observable>> { + getVocabularyEntries(vocabularyOptions: VocabularyOptions, pageInfo: PageInfo): Observable>> { - return this.vocabularyDataService.getFindAllHref(options, `${options.name}/entries`).pipe( + const options: VocabularyFindOptions = new VocabularyFindOptions( + vocabularyOptions.scope, + vocabularyOptions.metadata, + null, + null, + null, + null, + pageInfo.elementsPerPage, + pageInfo.currentPage + ); + + return this.vocabularyDataService.getFindAllHref(options, `${vocabularyOptions.name}/entries`).pipe( isNotEmptyOperator(), distinctUntilChanged(), getVocabularyEntriesFor(this.requestService, this.rdbService) ); } + /** + * Return the {@link VocabularyEntry} list for a given {@link Vocabulary} + * + * @param value The entry value to retrieve + * @param exact If true force the vocabulary to provide only entries that match exactly with the value + * @param vocabularyOptions The {@link VocabularyOptions} for the request to which the entries belong + * @param pageInfo The {@link PageInfo} for the request + * @return {Observable>>} + * Return an observable that emits object list + */ + getVocabularyEntriesByValue(value: string, exact: boolean, vocabularyOptions: VocabularyOptions, pageInfo: PageInfo): Observable>> { + const options: VocabularyFindOptions = new VocabularyFindOptions( + vocabularyOptions.scope, + vocabularyOptions.metadata, + null, + value, + exact, + null, + pageInfo.elementsPerPage, + pageInfo.currentPage + ); + + return this.vocabularyDataService.getFindAllHref(options, `${vocabularyOptions.name}/entries`).pipe( + isNotEmptyOperator(), + distinctUntilChanged(), + getVocabularyEntriesFor(this.requestService, this.rdbService) + ); + } + + /** + * Return the {@link VocabularyEntry} list for a given value + * + * @param value The entry value to retrieve + * @param vocabularyOptions The {@link VocabularyOptions} for the request to which the entry belongs + * @return {Observable>>} + * Return an observable that emits {@link VocabularyEntry} object + */ + getVocabularyEntryByValue(value: string, vocabularyOptions: VocabularyOptions): Observable { + + return this.getVocabularyEntriesByValue(value, true, vocabularyOptions, new PageInfo()).pipe( + getFirstSucceededRemoteListPayload(), + map((list: VocabularyEntry[]) => { + if (isNotEmpty(list)) { + return list[0] + } else { + return null; + } + }) + ); + } + + /** + * Return the {@link VocabularyEntry} list for a given ID + * + * @param ID The entry ID to retrieve + * @param vocabularyOptions The {@link VocabularyOptions} for the request to which the entry belongs + * @return {Observable>>} + * Return an observable that emits {@link VocabularyEntry} object + */ + getVocabularyEntryByID(ID: string, vocabularyOptions: VocabularyOptions): Observable { + const pageInfo = new PageInfo() + const options: VocabularyFindOptions = new VocabularyFindOptions( + vocabularyOptions.scope, + vocabularyOptions.metadata, + null, + null, + null, + ID, + pageInfo.elementsPerPage, + pageInfo.currentPage + ); + + return this.vocabularyDataService.getFindAllHref(options, `${vocabularyOptions.name}/entries`).pipe( + isNotEmptyOperator(), + distinctUntilChanged(), + getVocabularyEntriesFor(this.requestService, this.rdbService), + getFirstSucceededRemoteListPayload(), + map((list: VocabularyEntry[]) => { + if (isNotEmpty(list)) { + return list[0] + } else { + return null; + } + }) + ); + } + /** * Return the controlled {@link Vocabulary} configured for the specified metadata and collection if any. * - * @param options The {@link VocabularyFindOptions} for the request - * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved + * @param vocabularyOptions The {@link VocabularyOptions} for the request + * @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 */ - searchVocabularyByMetadataAndCollection(options: VocabularyFindOptions, ...linksToFollow: Array>): Observable> { + searchVocabularyByMetadataAndCollection(vocabularyOptions: VocabularyOptions, ...linksToFollow: Array>): Observable> { + const options: VocabularyFindOptions = new VocabularyFindOptions( + vocabularyOptions.scope, + vocabularyOptions.metadata + ); return this.vocabularyDataService.getSearchByHref(this.searchByMetadataAndCollectionMethod, options).pipe( first((href: string) => hasValue(href)), @@ -193,12 +303,13 @@ export class VocabularyService { /** * Return the top level {@link VocabularyEntryDetail} list for a given hierarchical vocabulary * - * @param name The name of hierarchical {@link Vocabulary} to which the entries belongs - * @param options The {@link VocabularyFindOptions} for the request + * @param name The name of hierarchical {@link Vocabulary} to which the entries belongs + * @param pageInfo The {@link PageInfo} for the request * @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved */ - searchTopEntries(name: string, options: VocabularyFindOptions, ...linksToFollow: Array>): Observable>> { - options.searchParams.push(new RequestParam('vocabulary', name)); + searchTopEntries(name: string, pageInfo: PageInfo, ...linksToFollow: Array>): Observable>> { + const options = new FindListOptions(); + options.searchParams = [new RequestParam('vocabulary', name)]; return this.vocabularyEntryDetailDataService.searchBy(this.searchTopMethod, options, ...linksToFollow) } } diff --git a/src/app/shared/testing/vocabulary-service.stub.ts b/src/app/shared/testing/vocabulary-service.stub.ts index 15528653c2..fef9b72557 100644 --- a/src/app/shared/testing/vocabulary-service.stub.ts +++ b/src/app/shared/testing/vocabulary-service.stub.ts @@ -1,18 +1,18 @@ -import { Observable } from 'rxjs'; +import { Observable, of as observableOf } from 'rxjs'; -import { VocabularyFindOptions } from '../../core/submission/vocabularies/models/vocabulary-find-options.model'; import { PageInfo } from '../../core/shared/page-info.model'; import { VocabularyEntry } from '../../core/submission/vocabularies/models/vocabulary-entry.model'; import { PaginatedList } from '../../core/data/paginated-list'; import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils'; import { RemoteData } from '../../core/data/remote-data'; +import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model'; export class VocabularyServiceStub { private _payload = [ - Object.assign(new VocabularyEntry(),{authority: 1, display: 'one', value: 1}), - Object.assign(new VocabularyEntry(),{authority: 2, display: 'two', value: 2}), + Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 }), + Object.assign(new VocabularyEntry(), { authority: 2, display: 'two', value: 2 }), ]; setNewPayload(payload) { @@ -23,7 +23,19 @@ export class VocabularyServiceStub { return this._payload } - getVocabularyEntries(options: VocabularyFindOptions): Observable>> { + getVocabularyEntries(vocabularyOptions: VocabularyOptions, pageInfo: PageInfo): Observable>> { return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), this._payload)); } + + getVocabularyEntriesByValue(value: string, exact: boolean, vocabularyOptions: VocabularyOptions, pageInfo: PageInfo): Observable>> { + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), this._payload)); + } + + getVocabularyEntryByValue(value: string, vocabularyOptions: VocabularyOptions): Observable { + return observableOf(Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 })); + } + + getVocabularyEntryByID(id: string, vocabularyOptions: VocabularyOptions): Observable { + return observableOf(Object.assign(new VocabularyEntry(), { authority: 1, display: 'one', value: 1 })); + } }