diff --git a/src/app/core/breadcrumbs/dso-name.service.spec.ts b/src/app/core/breadcrumbs/dso-name.service.spec.ts index 7a399ce748..9f2f76599a 100644 --- a/src/app/core/breadcrumbs/dso-name.service.spec.ts +++ b/src/app/core/breadcrumbs/dso-name.service.spec.ts @@ -78,15 +78,32 @@ describe(`DSONameService`, () => { }); describe(`factories.Person`, () => { - beforeEach(() => { - spyOn(mockPerson, 'firstMetadataValue').and.returnValues(...mockPersonName.split(', ')); + describe(`with person.familyName and person.givenName`, () => { + beforeEach(() => { + spyOn(mockPerson, 'firstMetadataValue').and.returnValues(...mockPersonName.split(', ')); + }); + + it(`should return 'person.familyName, person.givenName'`, () => { + const result = (service as any).factories.Person(mockPerson); + expect(result).toBe(mockPersonName); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.familyName'); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.givenName'); + expect(mockPerson.firstMetadataValue).not.toHaveBeenCalledWith('dc.title'); + }); }); - it(`should return 'person.familyName, person.givenName'`, () => { - const result = (service as any).factories.Person(mockPerson); - expect(result).toBe(mockPersonName); - expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.familyName'); - expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.givenName'); + describe(`without person.familyName and person.givenName`, () => { + beforeEach(() => { + spyOn(mockPerson, 'firstMetadataValue').and.returnValues(undefined, undefined, mockPersonName); + }); + + it(`should return dc.title`, () => { + const result = (service as any).factories.Person(mockPerson); + expect(result).toBe(mockPersonName); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.familyName'); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('person.givenName'); + expect(mockPerson.firstMetadataValue).toHaveBeenCalledWith('dc.title'); + }); }); }); diff --git a/src/app/core/profile/researcher-profile.service.spec.ts b/src/app/core/profile/researcher-profile.service.spec.ts new file mode 100644 index 0000000000..103bae2719 --- /dev/null +++ b/src/app/core/profile/researcher-profile.service.spec.ts @@ -0,0 +1,290 @@ +import { HttpClient, HttpHeaders } from '@angular/common/http'; + +import { cold, getTestScheduler, hot } from 'jasmine-marbles'; +import { of as observableOf } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; + +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestService } from '../data/request.service'; +import { PageInfo } from '../shared/page-info.model'; +import { buildPaginatedList } from '../data/paginated-list.model'; +import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { RestResponse } from '../cache/response.models'; +import { RequestEntry } from '../data/request-entry.model'; +import { ResearcherProfileService } from './researcher-profile.service'; +import { RouterMock } from '../../shared/mocks/router.mock'; +import { ResearcherProfile } from './model/researcher-profile.model'; +import { Item } from '../shared/item.model'; +import { ReplaceOperation } from 'fast-json-patch'; +import { HttpOptions } from '../dspace-rest/dspace-rest.service'; +import { PostRequest } from '../data/request.models'; + +describe('ResearcherProfileService', () => { + let scheduler: TestScheduler; + let service: ResearcherProfileService; + let serviceAsAny: any; + let requestService: RequestService; + let rdbService: RemoteDataBuildService; + let objectCache: ObjectCacheService; + let halService: HALEndpointService; + let responseCacheEntry: RequestEntry; + + const researcherProfileId = 'beef9946-rt56-479e-8f11-b90cbe9f7241'; + const itemId = 'beef9946-rt56-479e-8f11-b90cbe9f7241'; + const researcherProfileItem: Item = Object.assign(new Item(), { + id: itemId, + _links: { + self: { + href: `https://rest.api/rest/api/items/${itemId}` + }, + } + }); + const researcherProfile: ResearcherProfile = Object.assign(new ResearcherProfile(), { + id: researcherProfileId, + visible: false, + type: 'profile', + _links: { + item: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId}/item` + }, + self: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId}` + }, + } + }); + + const researcherProfilePatched: ResearcherProfile = Object.assign(new ResearcherProfile(), { + id: researcherProfileId, + visible: true, + type: 'profile', + _links: { + item: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId}/item` + }, + self: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId}` + }, + } + }); + + const researcherProfileId2 = 'agbf9946-f4ce-479e-8f11-b90cbe9f7241'; + const anotherResearcherProfile: ResearcherProfile = Object.assign(new ResearcherProfile(), { + id: researcherProfileId2, + visible: false, + type: 'profile', + _links: { + self: { + href: `https://rest.api/rest/api/profiles/${researcherProfileId2}` + }, + } + }); + const endpointURL = `https://rest.api/rest/api/profiles`; + const sourceUri = `https://rest.api/rest/api/external-source/profile`; + const requestURL = `https://rest.api/rest/api/profiles/${researcherProfileId}`; + const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a'; + + const pageInfo = new PageInfo(); + const array = [researcherProfile, anotherResearcherProfile]; + const paginatedList = buildPaginatedList(pageInfo, array); + const researcherProfileRD = createSuccessfulRemoteDataObject(researcherProfile); + const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList); + + beforeEach(() => { + scheduler = getTestScheduler(); + + halService = jasmine.createSpyObj('halService', { + getEndpoint: cold('a', { a: endpointURL }) + }); + + responseCacheEntry = new RequestEntry(); + responseCacheEntry.request = { href: 'https://rest.api/' } as any; + responseCacheEntry.response = new RestResponse(true, 200, 'Success'); + + requestService = jasmine.createSpyObj('requestService', { + generateRequestId: requestUUID, + send: true, + removeByHrefSubstring: {}, + getByHref: observableOf(responseCacheEntry), + getByUUID: observableOf(responseCacheEntry), + setStaleByHrefSubstring: jasmine.createSpy('setStaleByHrefSubstring') + }); + rdbService = jasmine.createSpyObj('rdbService', { + buildSingle: hot('a|', { + a: researcherProfileRD + }), + buildList: hot('a|', { + a: paginatedListRD + }), + buildFromRequestUUID: hot('a|', { + a: researcherProfileRD + }) + }); + objectCache = {} as ObjectCacheService; + const notificationsService = {} as NotificationsService; + const http = {} as HttpClient; + const comparator = {} as any; + const routerStub: any = new RouterMock(); + const itemService = jasmine.createSpyObj('ItemService', { + findByHref: jasmine.createSpy('findByHref') + }); + + service = new ResearcherProfileService( + requestService, + rdbService, + objectCache, + halService, + notificationsService, + http, + routerStub, + comparator, + itemService + ); + serviceAsAny = service; + + spyOn((service as any).dataService, 'create').and.callThrough(); + spyOn((service as any).dataService, 'delete').and.callThrough(); + spyOn((service as any).dataService, 'update').and.callThrough(); + spyOn((service as any).dataService, 'findById').and.callThrough(); + spyOn((service as any).dataService, 'findByHref').and.callThrough(); + spyOn((service as any).dataService, 'searchBy').and.callThrough(); + spyOn((service as any).dataService, 'getLinkPath').and.returnValue(observableOf(endpointURL)); + + }); + + describe('findById', () => { + it('should proxy the call to dataservice.findById with eperson UUID', () => { + scheduler.schedule(() => service.findById(researcherProfileId)); + scheduler.flush(); + + expect((service as any).dataService.findById).toHaveBeenCalledWith(researcherProfileId, true, true); + }); + + it('should return a ResearcherProfile object with the given id', () => { + const result = service.findById(researcherProfileId); + const expected = cold('a|', { + a: researcherProfileRD + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('create', () => { + it('should proxy the call to dataservice.create with eperson UUID', () => { + scheduler.schedule(() => service.create()); + scheduler.flush(); + + expect((service as any).dataService.create).toHaveBeenCalled(); + }); + + it('should return the RemoteData created', () => { + const result = service.create(); + const expected = cold('a|', { + a: researcherProfileRD + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('delete', () => { + it('should proxy the call to dataservice.delete', () => { + scheduler.schedule(() => service.delete(researcherProfile)); + scheduler.flush(); + + expect((service as any).dataService.delete).toHaveBeenCalledWith(researcherProfile.id); + }); + }); + + describe('findRelatedItemId', () => { + describe('with a related item', () => { + + beforeEach(() => { + (service as any).itemService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(researcherProfileItem)); + }); + + it('should proxy the call to dataservice.findById with eperson UUID', () => { + scheduler.schedule(() => service.findRelatedItemId(researcherProfile)); + scheduler.flush(); + + expect((service as any).itemService.findByHref).toHaveBeenCalledWith(researcherProfile._links.item.href, false); + }); + + it('should return a ResearcherProfile object with the given id', () => { + const result = service.findRelatedItemId(researcherProfile); + const expected = cold('(a|)', { + a: itemId + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('without a related item', () => { + + beforeEach(() => { + (service as any).itemService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$(null)); + }); + + it('should proxy the call to dataservice.findById with eperson UUID', () => { + scheduler.schedule(() => service.findRelatedItemId(researcherProfile)); + scheduler.flush(); + + expect((service as any).itemService.findByHref).toHaveBeenCalledWith(researcherProfile._links.item.href, false); + }); + + it('should return a ResearcherProfile object with the given id', () => { + const result = service.findRelatedItemId(researcherProfile); + const expected = cold('(a|)', { + a: undefined + }); + expect(result).toBeObservable(expected); + }); + }); + }); + + describe('setVisibility', () => { + let patchSpy; + beforeEach(() => { + spyOn((service as any), 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + }); + + it('should proxy the call to dataservice.patch', () => { + const replaceOperation: ReplaceOperation = { + path: '/visible', + op: 'replace', + value: true + }; + + scheduler.schedule(() => service.setVisibility(researcherProfile, true)); + scheduler.flush(); + + expect((service as any).patch).toHaveBeenCalledWith(researcherProfile, [replaceOperation]); + }); + }); + + describe('createFromExternalSource', () => { + let patchSpy; + beforeEach(() => { + spyOn((service as any), 'patch').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + spyOn((service as any), 'findById').and.returnValue(createSuccessfulRemoteDataObject$(researcherProfilePatched)); + }); + + it('should proxy the call to dataservice.patch', () => { + const options: HttpOptions = Object.create({}); + let headers = new HttpHeaders(); + headers = headers.append('Content-Type', 'text/uri-list'); + options.headers = headers; + const request = new PostRequest(requestUUID, endpointURL, sourceUri, options); + + scheduler.schedule(() => service.createFromExternalSource(sourceUri)); + scheduler.flush(); + + expect((service as any).requestService.send).toHaveBeenCalledWith(request); + expect((service as any).rdbService.buildFromRequestUUID).toHaveBeenCalledWith(requestUUID); + + }); + }); + +}); diff --git a/src/app/core/profile/researcher-profile.service.ts b/src/app/core/profile/researcher-profile.service.ts index 0220afb964..eab6ea3d48 100644 --- a/src/app/core/profile/researcher-profile.service.ts +++ b/src/app/core/profile/researcher-profile.service.ts @@ -2,27 +2,25 @@ import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; + import { Store } from '@ngrx/store'; -import { Operation, RemoveOperation, ReplaceOperation } from 'fast-json-patch'; -import { combineLatest, Observable, of as observableOf } from 'rxjs'; +import { Operation, ReplaceOperation } from 'fast-json-patch'; +import { Observable, of as observableOf } from 'rxjs'; import { catchError, find, map, switchMap, tap } from 'rxjs/operators'; -import { environment } from '../../../environments/environment'; + import { NotificationsService } from '../../shared/notifications/notifications.service'; import { dataService } from '../cache/builders/build-decorators'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { ObjectCacheService } from '../cache/object-cache.service'; -import { ConfigurationDataService } from '../data/configuration-data.service'; import { DataService } from '../data/data.service'; import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service'; import { ItemDataService } from '../data/item-data.service'; import { RemoteData } from '../data/remote-data'; import { RequestService } from '../data/request.service'; -import { ConfigurationProperty } from '../shared/configuration-property.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { Item } from '../shared/item.model'; import { NoContent } from '../shared/NoContent.model'; import { - getFinishedRemoteData, + getAllCompletedRemoteData, getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../shared/operators'; @@ -31,25 +29,26 @@ import { RESEARCHER_PROFILE } from './model/researcher-profile.resource-type'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { PostRequest } from '../data/request.models'; import { hasValue } from '../../shared/empty.util'; -import {CoreState} from '../core-state.model'; +import { CoreState } from '../core-state.model'; +import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; /** * A private DataService implementation to delegate specific methods to. */ class ResearcherProfileServiceImpl extends DataService { - protected linkPath = 'profiles'; + protected linkPath = 'profiles'; - constructor( - protected requestService: RequestService, - protected rdbService: RemoteDataBuildService, - protected store: Store, - protected objectCache: ObjectCacheService, - protected halService: HALEndpointService, - protected notificationsService: NotificationsService, - protected http: HttpClient, - protected comparator: DefaultChangeAnalyzer) { - super(); - } + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected store: Store, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected comparator: DefaultChangeAnalyzer) { + super(); + } } @@ -60,100 +59,102 @@ class ResearcherProfileServiceImpl extends DataService { @dataService(RESEARCHER_PROFILE) export class ResearcherProfileService { - dataService: ResearcherProfileServiceImpl; + dataService: ResearcherProfileServiceImpl; - responseMsToLive: number = 10 * 1000; + responseMsToLive: number = 10 * 1000; - constructor( - protected requestService: RequestService, - protected rdbService: RemoteDataBuildService, - protected store: Store, - protected objectCache: ObjectCacheService, - protected halService: HALEndpointService, - protected notificationsService: NotificationsService, - protected http: HttpClient, - protected router: Router, - protected comparator: DefaultChangeAnalyzer, - protected itemService: ItemDataService, - protected configurationService: ConfigurationDataService ) { + constructor( + protected requestService: RequestService, + protected rdbService: RemoteDataBuildService, + protected objectCache: ObjectCacheService, + protected halService: HALEndpointService, + protected notificationsService: NotificationsService, + protected http: HttpClient, + protected router: Router, + protected comparator: DefaultChangeAnalyzer, + protected itemService: ItemDataService) { - this.dataService = new ResearcherProfileServiceImpl(requestService, rdbService, store, objectCache, halService, - notificationsService, http, comparator); + this.dataService = new ResearcherProfileServiceImpl(requestService, rdbService, null, objectCache, halService, + notificationsService, http, comparator); - } + } - /** - * Find the researcher profile with the given uuid. - * - * @param uuid the profile uuid - */ - findById(uuid: string): Observable { - return this.dataService.findById(uuid, false) - .pipe ( getFinishedRemoteData(), - map((remoteData) => remoteData.payload)); - } + /** + * Find the researcher profile with the given uuid. + * + * @param uuid the profile uuid + * @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's + * no valid cached version. Defaults to true + * @param reRequestOnStale Whether or not the request should automatically be re- + * requested after the response becomes stale + * @param linksToFollow List of {@link FollowLinkConfig} that indicate which + * {@link HALLink}s should be automatically resolved + */ + public findById(uuid: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable> { + return this.dataService.findById(uuid, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe( + getAllCompletedRemoteData(), + ); + } - /** - * Create a new researcher profile for the current user. - */ - create(): Observable> { - return this.dataService.create( new ResearcherProfile()); - } + /** + * Create a new researcher profile for the current user. + */ + public create(): Observable> { + return this.dataService.create(new ResearcherProfile()); + } - /** - * Delete a researcher profile. - * - * @param researcherProfile the profile to delete - */ - delete(researcherProfile: ResearcherProfile): Observable { - return this.dataService.delete(researcherProfile.id).pipe( - getFirstCompletedRemoteData(), - tap((response: RemoteData) => { - if (response.isSuccess) { - this.requestService.setStaleByHrefSubstring(researcherProfile._links.self.href); - } + /** + * Delete a researcher profile. + * + * @param researcherProfile the profile to delete + */ + public delete(researcherProfile: ResearcherProfile): Observable { + return this.dataService.delete(researcherProfile.id).pipe( + getFirstCompletedRemoteData(), + tap((response: RemoteData) => { + if (response.isSuccess) { + this.requestService.setStaleByHrefSubstring(researcherProfile._links.self.href); + } + }), + map((response: RemoteData) => response.isSuccess) + ); + } + + /** + * Find the item id related to the given researcher profile. + * + * @param researcherProfile the profile to find for + */ + public findRelatedItemId(researcherProfile: ResearcherProfile): Observable { + return this.itemService.findByHref(researcherProfile._links.item.href, false) + .pipe(getFirstSucceededRemoteDataPayload(), + catchError((error) => { + console.debug(error); + return observableOf(null); }), - map((response: RemoteData) => response.isSuccess) + map((item) => item?.id) ); - } + } - /** - * Find the item id related to the given researcher profile. - * - * @param researcherProfile the profile to find for - */ - findRelatedItemId( researcherProfile: ResearcherProfile ): Observable { - return this.itemService.findByHref(researcherProfile._links.item.href, false) - .pipe (getFirstSucceededRemoteDataPayload(), - catchError((error) => { - console.debug(error); - return observableOf(null); - }), - map((item) => item != null ? item.id : null )); - } + /** + * Change the visibility of the given researcher profile setting the given value. + * + * @param researcherProfile the profile to update + * @param visible the visibility value to set + */ + public setVisibility(researcherProfile: ResearcherProfile, visible: boolean): Observable { - /** - * Change the visibility of the given researcher profile setting the given value. - * - * @param researcherProfile the profile to update - * @param visible the visibility value to set - */ - setVisibility(researcherProfile: ResearcherProfile, visible: boolean): Observable { + const replaceOperation: ReplaceOperation = { + path: '/visible', + op: 'replace', + value: visible + }; - const replaceOperation: ReplaceOperation = { - path: '/visible', - op: 'replace', - value: visible - }; - - return this.patch(researcherProfile, [replaceOperation]).pipe ( - switchMap( ( ) => this.findById(researcherProfile.id)) - ); - } - - patch(researcherProfile: ResearcherProfile, operations: Operation[]): Observable> { - return this.dataService.patch(researcherProfile, operations); - } + return this.patch(researcherProfile, [replaceOperation]).pipe( + switchMap(() => this.findById(researcherProfile.id)), + getFirstSucceededRemoteDataPayload() + ); + } /** * Creates a researcher profile starting from an external source URI @@ -179,4 +180,7 @@ export class ResearcherProfileService { return this.rdbService.buildFromRequestUUID(requestId); } + private patch(researcherProfile: ResearcherProfile, operations: Operation[]): Observable> { + return this.dataService.patch(researcherProfile, operations); + } } diff --git a/src/app/profile-page/profile-claim/profile-claim.service.spec.ts b/src/app/profile-page/profile-claim/profile-claim.service.spec.ts new file mode 100644 index 0000000000..4030c7900c --- /dev/null +++ b/src/app/profile-page/profile-claim/profile-claim.service.spec.ts @@ -0,0 +1,215 @@ +import { cold, getTestScheduler } from 'jasmine-marbles'; + +import { of as observableOf } from 'rxjs'; +import { TestScheduler } from 'rxjs/testing'; + +import { ProfileClaimService } from './profile-claim.service'; +import { SearchService } from '../../core/shared/search/search.service'; +import { ItemSearchResult } from '../../shared/object-collection/shared/item-search-result.model'; +import { SearchObjects } from '../../shared/search/models/search-objects.model'; +import { Item } from '../../core/shared/item.model'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { EPerson } from '../../core/eperson/models/eperson.model'; + +describe('ProfileClaimService', () => { + let scheduler: TestScheduler; + let service: ProfileClaimService; + let serviceAsAny: any; + let searchService: jasmine.SpyObj; + + const eperson: EPerson = Object.assign(new EPerson(), { + id: 'id', + metadata: { + 'eperson.firstname': [ + { + value: 'John' + } + ], + 'eperson.lastname': [ + { + value: 'Doe' + }, + ], + }, + email: 'fake@email.com' + }); + const item1: Item = Object.assign(new Item(), { + uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e', + metadata: { + 'person.email': [ + { + value: 'fake@email.com' + } + ], + 'person.familyName': [ + { + value: 'Doe' + } + ], + 'person.givenName': [ + { + value: 'John' + } + ] + }, + _links: { + self: { + href: 'item-href' + } + } + }); + const item2: Item = Object.assign(new Item(), { + uuid: 'c8279647-1acc-41ae-b036-951d5f65649b', + metadata: { + 'person.email': [ + { + value: 'fake2@email.com' + } + ], + 'dc.title': [ + { + value: 'John, Doe' + } + ] + }, + _links: { + self: { + href: 'item-href' + } + } + }); + const item3: Item = Object.assign(new Item(), { + uuid: 'c8279647-1acc-41ae-b036-951d5f65649b', + metadata: { + 'person.email': [ + { + value: 'fake3@email.com' + } + ], + 'dc.title': [ + { + value: 'John, Doe' + } + ] + }, + _links: { + self: { + href: 'item-href' + } + } + }); + + const searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 }); + const searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 }); + const searchResult3 = Object.assign(new ItemSearchResult(), { indexableObject: item3 }); + + const searchResult = Object.assign(new SearchObjects(), { + page: [searchResult1, searchResult2, searchResult3] + }); + const emptySearchResult = Object.assign(new SearchObjects(), { + page: [] + }); + const searchResultRD = createSuccessfulRemoteDataObject(searchResult); + const emptyRearchResultRD = createSuccessfulRemoteDataObject(emptySearchResult); + + beforeEach(() => { + scheduler = getTestScheduler(); + + searchService = jasmine.createSpyObj('SearchService', { + search: jasmine.createSpy('search') + }); + + service = new ProfileClaimService(searchService); + serviceAsAny = service; + }); + + describe('hasProfilesToSuggest', () => { + + describe('when has suggestions', () => { + beforeEach(() => { + spyOn(service, 'search').and.returnValue(observableOf(searchResultRD)); + }); + + it('should return true', () => { + const result = service.hasProfilesToSuggest(eperson); + const expected = cold('(a|)', { + a: true + }); + expect(result).toBeObservable(expected); + }); + + }); + + describe('when has not suggestions', () => { + beforeEach(() => { + spyOn(service, 'search').and.returnValue(observableOf(emptyRearchResultRD)); + }); + + it('should return false', () => { + const result = service.hasProfilesToSuggest(eperson); + const expected = cold('(a|)', { + a: false + }); + expect(result).toBeObservable(expected); + }); + + }); + + describe('when has not valid eperson', () => { + it('should return false', () => { + const result = service.hasProfilesToSuggest(null); + const expected = cold('(a|)', { + a: false + }); + expect(result).toBeObservable(expected); + }); + + }); + + }); + + describe('search', () => { + + describe('when has search results', () => { + beforeEach(() => { + searchService.search.and.returnValue(observableOf(searchResultRD)); + }); + + it('should return the proper search object', () => { + const result = service.search(eperson); + const expected = cold('(a|)', { + a: searchResultRD + }); + expect(result).toBeObservable(expected); + }); + + }); + + describe('when has not suggestions', () => { + beforeEach(() => { + searchService.search.and.returnValue(observableOf(emptyRearchResultRD)); + }); + + it('should return null', () => { + const result = service.search(eperson); + const expected = cold('(a|)', { + a: emptyRearchResultRD + }); + expect(result).toBeObservable(expected); + }); + + }); + + describe('when has not valid eperson', () => { + it('should return null', () => { + const result = service.search(null); + const expected = cold('(a|)', { + a: null + }); + expect(result).toBeObservable(expected); + }); + + }); + + }); +}); diff --git a/src/app/profile-page/profile-claim/profile-claim.service.ts b/src/app/profile-page/profile-claim/profile-claim.service.ts index 9ee2462778..a61404540b 100644 --- a/src/app/profile-page/profile-claim/profile-claim.service.ts +++ b/src/app/profile-page/profile-claim/profile-claim.service.ts @@ -1,16 +1,16 @@ import { Injectable } from '@angular/core'; + import { Observable, of } from 'rxjs'; -import { mergeMap, take } from 'rxjs/operators'; -import { ConfigurationDataService } from '../../core/data/configuration-data.service'; -import { PaginatedList } from '../../core/data/paginated-list.model'; +import { map } from 'rxjs/operators'; + import { RemoteData } from '../../core/data/remote-data'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { SearchService } from '../../core/shared/search/search.service'; -import { hasValue } from '../../shared/empty.util'; +import { isEmpty, isNotEmpty } from '../../shared/empty.util'; import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model'; -import { SearchResult } from '../../shared/search/models/search-result.model'; -import { getFirstSucceededRemoteData } from './../../core/shared/operators'; +import { getFirstCompletedRemoteData } from '../../core/shared/operators'; +import { SearchObjects } from '../../shared/search/models/search-objects.model'; /** * Service that handle profiles claim. @@ -18,8 +18,7 @@ import { getFirstSucceededRemoteData } from './../../core/shared/operators'; @Injectable() export class ProfileClaimService { - constructor(private searchService: SearchService, - private configurationService: ConfigurationDataService) { + constructor(private searchService: SearchService) { } /** @@ -27,27 +26,21 @@ export class ProfileClaimService { * * @param eperson the eperson */ - canClaimProfiles(eperson: EPerson): Observable { - - const query = this.personQueryData(eperson); - - if (!hasValue(query) || query.length === 0) { - return of(false); - } - - return this.lookup(query).pipe( - mergeMap((rd: RemoteData>>) => of(rd.payload.totalElements > 0)) + hasProfilesToSuggest(eperson: EPerson): Observable { + return this.search(eperson).pipe( + map((rd: RemoteData>) => { + return isNotEmpty(rd) && rd.hasSucceeded && rd.payload?.page?.length > 0; + }) ); - } /** * Returns profiles that could be associated with the given user. * @param eperson the user */ - search(eperson: EPerson): Observable>>> { + search(eperson: EPerson): Observable>> { const query = this.personQueryData(eperson); - if (!hasValue(query) || query.length === 0) { + if (isEmpty(query)) { return of(null); } return this.lookup(query); @@ -57,21 +50,31 @@ export class ProfileClaimService { * Search object by the given query. * @param query the query for the search */ - private lookup(query: string): Observable>>> { - if (!hasValue(query)) { + private lookup(query: string): Observable>> { + if (isEmpty(query)) { return of(null); } return this.searchService.search(new PaginatedSearchOptions({ configuration: 'eperson_claims', query: query - })) - .pipe( - getFirstSucceededRemoteData(), - take(1)); + })).pipe( + getFirstCompletedRemoteData() + ); } + /** + * Return the search query for person lookup, from the given eperson + * + * @param eperson The eperson to use for the lookup + */ private personQueryData(eperson: EPerson): string { - return 'dc.title:' + eperson.name; + if (eperson) { + const firstname = eperson.firstMetadataValue('eperson.firstname'); + const lastname = eperson.firstMetadataValue('eperson.lastname'); + return 'dc.title:' + eperson.name + ' OR (person.familyName:' + lastname + ' AND person.givenName:' + firstname + ')'; + } else { + return null; + } } } diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts index d12c445ce4..b928b20eef 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.spec.ts @@ -16,6 +16,7 @@ import { ProfilePageResearcherFormComponent } from './profile-page-researcher-fo import { ProfileClaimService } from '../profile-claim/profile-claim.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AuthService } from 'src/app/core/auth/auth.service'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; describe('ProfilePageResearcherFormComponent', () => { @@ -51,7 +52,7 @@ describe('ProfilePageResearcherFormComponent', () => { }); researcherProfileService = jasmine.createSpyObj('researcherProfileService', { - findById: observableOf(profile), + findById: createSuccessfulRemoteDataObject$(profile), create: observableOf(profile), setVisibility: observableOf(profile), delete: observableOf(true), @@ -61,7 +62,7 @@ describe('ProfilePageResearcherFormComponent', () => { notificationsServiceStub = new NotificationsServiceStub(); profileClaimService = jasmine.createSpyObj('profileClaimService', { - canClaimProfiles: observableOf(false), + hasProfilesToSuggest: observableOf(false), }); } @@ -91,7 +92,7 @@ describe('ProfilePageResearcherFormComponent', () => { }); it('should search the researcher profile for the current user', () => { - expect(researcherProfileService.findById).toHaveBeenCalledWith(user.id); + expect(researcherProfileService.findById).toHaveBeenCalledWith(user.id, false); }); describe('createProfile', () => { diff --git a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts index 9bb3028ff4..a1887f8b31 100644 --- a/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts +++ b/src/app/profile-page/profile-page-researcher-form/profile-page-researcher-form.component.ts @@ -4,17 +4,18 @@ import { Router } from '@angular/router'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject, Observable } from 'rxjs'; -import { filter, mergeMap, switchMap, take, tap } from 'rxjs/operators'; +import { mergeMap, switchMap, take, tap } from 'rxjs/operators'; -import { getFirstCompletedRemoteData } from '../../core/shared/operators'; -import { ClaimItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component'; +import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators'; +import { + ClaimItemSelectorComponent +} from '../../shared/dso-selector/modal-wrappers/claim-item-selector/claim-item-selector.component'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { AuthService } from '../../core/auth/auth.service'; import { EPerson } from '../../core/eperson/models/eperson.model'; import { ResearcherProfile } from '../../core/profile/model/researcher-profile.model'; import { ResearcherProfileService } from '../../core/profile/researcher-profile.service'; import { ProfileClaimService } from '../profile-claim/profile-claim.service'; -import { isNotEmpty } from '../../shared/empty.util'; @Component({ selector: 'ds-profile-page-researcher-form', @@ -77,10 +78,10 @@ export class ProfilePageResearcherFormComponent implements OnInit { this.processingCreate$.next(true); this.authService.getAuthenticatedUserFromStore().pipe( - switchMap((currentUser) => this.profileClaimService.canClaimProfiles(currentUser))) - .subscribe((canClaimProfiles) => { + switchMap((currentUser) => this.profileClaimService.hasProfilesToSuggest(currentUser))) + .subscribe((hasProfilesToSuggest) => { - if (canClaimProfiles) { + if (hasProfilesToSuggest) { this.processingCreate$.next(false); const modal = this.modalService.open(ClaimItemSelectorComponent); modal.componentInstance.dso = this.user; @@ -174,9 +175,8 @@ export class ProfilePageResearcherFormComponent implements OnInit { * Initializes the researcherProfile and researcherProfileItemId attributes using the profile of the current user. */ private initResearchProfile(): void { - this.researcherProfileService.findById(this.user.id).pipe( - take(1), - filter((researcherProfile) => isNotEmpty(researcherProfile)), + this.researcherProfileService.findById(this.user.id, false).pipe( + getFirstSucceededRemoteDataPayload(), tap((researcherProfile) => this.researcherProfile$.next(researcherProfile)), mergeMap((researcherProfile) => this.researcherProfileService.findRelatedItemId(researcherProfile)), ).subscribe((itemId: string) => { diff --git a/src/app/profile-page/profile-page.component.spec.ts b/src/app/profile-page/profile-page.component.spec.ts index 84aec0c0f1..fcbd4d9e4a 100644 --- a/src/app/profile-page/profile-page.component.spec.ts +++ b/src/app/profile-page/profile-page.component.spec.ts @@ -11,17 +11,17 @@ import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model'; import { EPersonDataService } from '../core/eperson/eperson-data.service'; import { NotificationsService } from '../shared/notifications/notifications.service'; import { authReducer } from '../core/auth/auth.reducer'; -import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; +import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; import { createPaginatedList } from '../shared/testing/utils.test'; import { BehaviorSubject, of as observableOf } from 'rxjs'; import { AuthService } from '../core/auth/auth.service'; import { RestResponse } from '../core/cache/response.models'; import { provideMockStore } from '@ngrx/store/testing'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; -import { getTestScheduler } from 'jasmine-marbles'; +import { cold, getTestScheduler } from 'jasmine-marbles'; import { By } from '@angular/platform-browser'; -import {ConfigurationDataService} from '../core/data/configuration-data.service'; -import {ConfigurationProperty} from '../core/shared/configuration-property.model'; +import { ConfigurationDataService } from '../core/data/configuration-data.service'; +import { ConfigurationProperty } from '../core/shared/configuration-property.model'; describe('ProfilePageComponent', () => { let component: ProfilePageComponent; @@ -30,16 +30,28 @@ describe('ProfilePageComponent', () => { let initialState: any; let authService; + let authorizationService; let epersonService; let notificationsService; + let configurationService; const canChangePassword = new BehaviorSubject(true); + const validConfiguration = Object.assign(new ConfigurationProperty(), { + name: 'researcher-profile.entity-type', + values: [ + 'Person' + ] + }); + const emptyConfiguration = Object.assign(new ConfigurationProperty(), { + name: 'researcher-profile.entity-type', + values: [] + }); function init() { user = Object.assign(new EPerson(), { id: 'userId', groups: createSuccessfulRemoteDataObject$(createPaginatedList([])), - _links: {self: {href: 'test.com/uuid/1234567654321'}} + _links: { self: { href: 'test.com/uuid/1234567654321' } } }); initialState = { core: { @@ -54,7 +66,7 @@ describe('ProfilePageComponent', () => { } } }; - + authorizationService = jasmine.createSpyObj('authorizationService', { isAuthorized: canChangePassword }); authService = jasmine.createSpyObj('authService', { getAuthenticatedUserFromStore: observableOf(user) }); @@ -67,6 +79,9 @@ describe('ProfilePageComponent', () => { error: {}, warning: {} }); + configurationService = jasmine.createSpyObj('configurationDataService', { + findByPropertyName: jasmine.createSpy('findByPropertyName') + }); } beforeEach(waitForAsync(() => { @@ -82,15 +97,8 @@ describe('ProfilePageComponent', () => { { provide: EPersonDataService, useValue: epersonService }, { provide: NotificationsService, useValue: notificationsService }, { provide: AuthService, useValue: authService }, - { provide: ConfigurationDataService, useValue: jasmine.createSpyObj('configurationDataService', { - findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), { - name: 'researcher-profile.entity-type', - values: [ - 'Person' - ] - })) - })}, - { provide: AuthorizationDataService, useValue: jasmine.createSpyObj('authorizationService', { isAuthorized: canChangePassword }) }, + { provide: ConfigurationDataService, useValue: configurationService }, + { provide: AuthorizationDataService, useValue: authorizationService }, provideMockStore({ initialState }), ], schemas: [NO_ERRORS_SCHEMA] @@ -100,148 +108,206 @@ describe('ProfilePageComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ProfilePageComponent); component = fixture.componentInstance; - fixture.detectChanges(); }); - describe('updateProfile', () => { - describe('when the metadata form returns false and the security form returns true', () => { - beforeEach(() => { - component.metadataForm = jasmine.createSpyObj('metadataForm', { - updateProfile: false + describe('', () => { + + beforeEach(() => { + configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(validConfiguration)); + fixture.detectChanges(); + }); + + describe('updateProfile', () => { + describe('when the metadata form returns false and the security form returns true', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: false + }); + spyOn(component, 'updateSecurity').and.returnValue(true); + component.updateProfile(); + }); + + it('should not display a warning', () => { + expect(notificationsService.warning).not.toHaveBeenCalled(); }); - spyOn(component, 'updateSecurity').and.returnValue(true); - component.updateProfile(); }); - it('should not display a warning', () => { - expect(notificationsService.warning).not.toHaveBeenCalled(); + describe('when the metadata form returns true and the security form returns false', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: true + }); + component.updateProfile(); + }); + + it('should not display a warning', () => { + expect(notificationsService.warning).not.toHaveBeenCalled(); + }); + }); + + describe('when the metadata form returns true and the security form returns true', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: true + }); + component.updateProfile(); + }); + + it('should not display a warning', () => { + expect(notificationsService.warning).not.toHaveBeenCalled(); + }); + }); + + describe('when the metadata form returns false and the security form returns false', () => { + beforeEach(() => { + component.metadataForm = jasmine.createSpyObj('metadataForm', { + updateProfile: false + }); + component.updateProfile(); + }); + + it('should display a warning', () => { + expect(notificationsService.warning).toHaveBeenCalled(); + }); }); }); - describe('when the metadata form returns true and the security form returns false', () => { - beforeEach(() => { - component.metadataForm = jasmine.createSpyObj('metadataForm', { - updateProfile: true + describe('updateSecurity', () => { + describe('when no password value present', () => { + let result; + + beforeEach(() => { + component.setPasswordValue(''); + + result = component.updateSecurity(); + }); + + it('should return false', () => { + expect(result).toEqual(false); + }); + + it('should not call epersonService.patch', () => { + expect(epersonService.patch).not.toHaveBeenCalled(); }); - component.updateProfile(); }); - it('should not display a warning', () => { - expect(notificationsService.warning).not.toHaveBeenCalled(); + describe('when password is filled in, but the password is invalid', () => { + let result; + + beforeEach(() => { + component.setPasswordValue('test'); + component.setInvalid(true); + result = component.updateSecurity(); + }); + + it('should return true', () => { + expect(result).toEqual(true); + expect(epersonService.patch).not.toHaveBeenCalled(); + }); + }); + + describe('when password is filled in, and is valid', () => { + let result; + let operations; + + beforeEach(() => { + component.setPasswordValue('testest'); + component.setInvalid(false); + + operations = [{ op: 'add', path: '/password', value: 'testest' }]; + result = component.updateSecurity(); + }); + + it('should return true', () => { + expect(result).toEqual(true); + }); + + it('should return call epersonService.patch', () => { + expect(epersonService.patch).toHaveBeenCalledWith(user, operations); + }); }); }); - describe('when the metadata form returns true and the security form returns true', () => { - beforeEach(() => { - component.metadataForm = jasmine.createSpyObj('metadataForm', { - updateProfile: true + describe('canChangePassword$', () => { + describe('when the user is allowed to change their password', () => { + beforeEach(() => { + canChangePassword.next(true); }); - component.updateProfile(); - }); - it('should not display a warning', () => { - expect(notificationsService.warning).not.toHaveBeenCalled(); - }); - }); - - describe('when the metadata form returns false and the security form returns false', () => { - beforeEach(() => { - component.metadataForm = jasmine.createSpyObj('metadataForm', { - updateProfile: false + it('should contain true', () => { + getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: true }); + }); + + it('should show the security section on the page', () => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.security-section'))).not.toBeNull(); }); - component.updateProfile(); }); - it('should display a warning', () => { - expect(notificationsService.warning).toHaveBeenCalled(); + describe('when the user is not allowed to change their password', () => { + beforeEach(() => { + canChangePassword.next(false); + }); + + it('should contain false', () => { + getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: false }); + }); + + it('should not show the security section on the page', () => { + fixture.detectChanges(); + expect(fixture.debugElement.query(By.css('.security-section'))).toBeNull(); + }); }); }); }); - describe('updateSecurity', () => { - describe('when no password value present', () => { - let result; + describe('isResearcherProfileEnabled', () => { + + describe('when configuration service return values', () => { beforeEach(() => { - component.setPasswordValue(''); + configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(validConfiguration)); + fixture.detectChanges(); + }); - result = component.updateSecurity(); + it('should return true', () => { + const result = component.isResearcherProfileEnabled(); + const expected = cold('a', { + a: true + }); + expect(result).toBeObservable(expected); + }); + }); + + describe('when configuration service return no values', () => { + + beforeEach(() => { + configurationService.findByPropertyName.and.returnValue(createSuccessfulRemoteDataObject$(emptyConfiguration)); + fixture.detectChanges(); }); it('should return false', () => { - expect(result).toEqual(false); - }); - - it('should not call epersonService.patch', () => { - expect(epersonService.patch).not.toHaveBeenCalled(); + const result = component.isResearcherProfileEnabled(); + const expected = cold('a', { + a: false + }); + expect(result).toBeObservable(expected); }); }); - describe('when password is filled in, but the password is invalid', () => { - let result; + describe('when configuration service return an error', () => { beforeEach(() => { - component.setPasswordValue('test'); - component.setInvalid(true); - result = component.updateSecurity(); - }); - - it('should return true', () => { - expect(result).toEqual(true); - expect(epersonService.patch).not.toHaveBeenCalled(); - }); - }); - - describe('when password is filled in, and is valid', () => { - let result; - let operations; - - beforeEach(() => { - component.setPasswordValue('testest'); - component.setInvalid(false); - - operations = [{ op: 'add', path: '/password', value: 'testest' }]; - result = component.updateSecurity(); - }); - - it('should return true', () => { - expect(result).toEqual(true); - }); - - it('should return call epersonService.patch', () => { - expect(epersonService.patch).toHaveBeenCalledWith(user, operations); - }); - }); - }); - - describe('canChangePassword$', () => { - describe('when the user is allowed to change their password', () => { - beforeEach(() => { - canChangePassword.next(true); - }); - - it('should contain true', () => { - getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: true }); - }); - - it('should show the security section on the page', () => { + configurationService.findByPropertyName.and.returnValue(createFailedRemoteDataObject$()); fixture.detectChanges(); - expect(fixture.debugElement.query(By.css('.security-section'))).not.toBeNull(); - }); - }); - - describe('when the user is not allowed to change their password', () => { - beforeEach(() => { - canChangePassword.next(false); }); - it('should contain false', () => { - getTestScheduler().expectObservable(component.canChangePassword$).toBe('(a)', { a: false }); - }); - - it('should not show the security section on the page', () => { - fixture.detectChanges(); - expect(fixture.debugElement.query(By.css('.security-section'))).toBeNull(); + it('should return false', () => { + const result = component.isResearcherProfileEnabled(); + const expected = cold('a', { + a: false + }); + expect(result).toBeObservable(expected); }); }); }); diff --git a/src/app/profile-page/profile-page.component.ts b/src/app/profile-page/profile-page.component.ts index 9c22c8c950..374fa5220b 100644 --- a/src/app/profile-page/profile-page.component.ts +++ b/src/app/profile-page/profile-page.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import {BehaviorSubject, Observable} from 'rxjs'; +import { BehaviorSubject, Observable } from 'rxjs'; import { EPerson } from '../core/eperson/models/eperson.model'; import { ProfilePageMetadataFormComponent } from './profile-page-metadata-form/profile-page-metadata-form.component'; import { NotificationsService } from '../shared/notifications/notifications.service'; @@ -9,18 +9,15 @@ import { RemoteData } from '../core/data/remote-data'; import { PaginatedList } from '../core/data/paginated-list.model'; import { filter, switchMap, tap } from 'rxjs/operators'; import { EPersonDataService } from '../core/eperson/eperson-data.service'; -import { - getAllSucceededRemoteData, - getRemoteDataPayload, - getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload -} from '../core/shared/operators'; +import { getAllSucceededRemoteData, getFirstCompletedRemoteData, getRemoteDataPayload } from '../core/shared/operators'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { followLink } from '../shared/utils/follow-link-config.model'; import { AuthService } from '../core/auth/auth.service'; import { Operation } from 'fast-json-patch'; import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../core/data/feature-authorization/feature-id'; -import {ConfigurationDataService} from '../core/data/configuration-data.service'; +import { ConfigurationDataService } from '../core/data/configuration-data.service'; +import { ConfigurationProperty } from '../core/shared/configuration-property.model'; @Component({ selector: 'ds-profile-page', @@ -94,8 +91,10 @@ export class ProfilePageComponent implements OnInit { this.canChangePassword$ = this.user$.pipe(switchMap((user: EPerson) => this.authorizationService.isAuthorized(FeatureID.CanChangePassword, user._links.self.href))); this.configurationService.findByPropertyName('researcher-profile.entity-type').pipe( - getFirstSucceededRemoteDataPayload() - ).subscribe(() => this.isResearcherProfileEnabled$.next(true)); + getFirstCompletedRemoteData() + ).subscribe((configRD: RemoteData) => { + this.isResearcherProfileEnabled$.next(configRD.hasSucceeded && configRD.payload.values.length > 0); + }); } /** @@ -175,8 +174,8 @@ export class ProfilePageComponent implements OnInit { /** * Returns true if the researcher profile feature is enabled, false otherwise. */ - isResearcherProfileEnabled(){ - return this.isResearcherProfileEnabled$; + isResearcherProfileEnabled(): Observable { + return this.isResearcherProfileEnabled$.asObservable(); } } diff --git a/yarn.lock b/yarn.lock index c06853e625..65c1a61aba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8884,11 +8884,6 @@ ngx-ui-switch@^11.0.1: resolved "https://registry.yarnpkg.com/ngx-ui-switch/-/ngx-ui-switch-11.0.1.tgz#c7f1e97ebe698f827a26f49951b50492b22c7839" integrity sha512-N8QYT/wW+xJdyh/aeebTSLPA6Sgrwp69H6KAcW0XZueg/LF+FKiqyG6Po/gFHq2gDhLikwyJEMpny8sudTI08w== -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - nice-napi@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nice-napi/-/nice-napi-1.0.2.tgz#dc0ab5a1eac20ce548802fc5686eaa6bc654927b"