mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
[CST-3088] Added vocabulary service and models
This commit is contained in:
@@ -16,8 +16,8 @@ import { MenuService } from '../shared/menu/menu.service';
|
||||
import { EndpointMockingRestService } from '../shared/mocks/dspace-rest-v2/endpoint-mocking-rest.service';
|
||||
import {
|
||||
MOCK_RESPONSE_MAP,
|
||||
ResponseMapMock,
|
||||
mockResponseMap
|
||||
mockResponseMap,
|
||||
ResponseMapMock
|
||||
} from '../shared/mocks/dspace-rest-v2/mocks/response-map.mock';
|
||||
import { NotificationsService } from '../shared/notifications/notifications.service';
|
||||
import { SelectableListService } from '../shared/object-list/selectable-list/selectable-list.service';
|
||||
@@ -145,6 +145,9 @@ import { Version } from './shared/version.model';
|
||||
import { VersionHistory } from './shared/version-history.model';
|
||||
import { WorkflowActionDataService } from './data/workflow-action-data.service';
|
||||
import { WorkflowAction } from './tasks/models/workflow-action-object.model';
|
||||
import { VocabularyEntry } from './submission/vocabularies/models/vocabulary-entry.model';
|
||||
import { Vocabulary } from './submission/vocabularies/models/vocabulary.model';
|
||||
import { VocabularyEntriesResponseParsingService } from './submission/vocabularies/vocabulary-entries-response-parsing.service';
|
||||
|
||||
/**
|
||||
* When not in production, endpoint responses can be mocked for testing purposes
|
||||
@@ -178,7 +181,7 @@ const PROVIDERS = [
|
||||
SiteDataService,
|
||||
DSOResponseParsingService,
|
||||
{ provide: MOCK_RESPONSE_MAP, useValue: mockResponseMap },
|
||||
{ provide: DSpaceRESTv2Service, useFactory: restServiceFactory, deps: [MOCK_RESPONSE_MAP, HttpClient]},
|
||||
{ provide: DSpaceRESTv2Service, useFactory: restServiceFactory, deps: [MOCK_RESPONSE_MAP, HttpClient] },
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormService,
|
||||
DynamicFormValidationService,
|
||||
@@ -272,7 +275,8 @@ const PROVIDERS = [
|
||||
},
|
||||
NotificationsService,
|
||||
FilteredDiscoveryPageResponseParsingService,
|
||||
{ provide: NativeWindowService, useFactory: NativeWindowFactory }
|
||||
{ provide: NativeWindowService, useFactory: NativeWindowFactory },
|
||||
VocabularyEntriesResponseParsingService
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -314,7 +318,9 @@ export const models =
|
||||
ExternalSourceEntry,
|
||||
Version,
|
||||
VersionHistory,
|
||||
WorkflowAction
|
||||
WorkflowAction,
|
||||
Vocabulary,
|
||||
VocabularyEntry
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -333,6 +339,12 @@ export const models =
|
||||
})
|
||||
|
||||
export class CoreModule {
|
||||
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (isNotEmpty(parentModule)) {
|
||||
throw new Error('CoreModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: CoreModule,
|
||||
@@ -341,10 +353,4 @@ export class CoreModule {
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
|
||||
if (isNotEmpty(parentModule)) {
|
||||
throw new Error('CoreModule is already loaded. Import it in the AppModule only');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ import { URLCombiner } from '../url-combiner/url-combiner';
|
||||
import { TaskResponseParsingService } from '../tasks/task-response-parsing.service';
|
||||
import { ContentSourceResponseParsingService } from './content-source-response-parsing.service';
|
||||
import { MappedCollectionsReponseParsingService } from './mapped-collections-reponse-parsing.service';
|
||||
import { VocabularyEntriesResponseParsingService } from '../submission/vocabularies/vocabulary-entries-response-parsing.service';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
@@ -442,6 +443,15 @@ export class MyDSpaceRequest extends GetRequest {
|
||||
public responseMsToLive = 10 * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to get vocabulary entries
|
||||
*/
|
||||
export class VocabularyEntriesRequest extends FindListRequest {
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return VocabularyEntriesResponseParsingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class RequestError extends Error {
|
||||
statusCode: number;
|
||||
statusText: string;
|
||||
|
@@ -0,0 +1,11 @@
|
||||
import { ResourceType } from '../../../shared/resource-type';
|
||||
|
||||
/**
|
||||
* The resource type for vocabulary models
|
||||
*
|
||||
* Needs to be in a separate file to prevent circular
|
||||
* dependencies in webpack.
|
||||
*/
|
||||
|
||||
export const VOCABULARY = new ResourceType('vocabulary');
|
||||
export const VOCABULARY_ENTRY = new ResourceType('vocabularyEntry');
|
@@ -0,0 +1,103 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
|
||||
import { HALLink } from '../../../shared/hal-link.model';
|
||||
import { VOCABULARY_ENTRY } from './vocabularies.resource-type';
|
||||
import { typedObject } from '../../../cache/builders/build-decorators';
|
||||
import { excludeFromEquals } from '../../../utilities/equals.decorators';
|
||||
import { PLACEHOLDER_PARENT_METADATA } from '../../../../shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
|
||||
import { OtherInformation } from '../../../../shared/form/builder/models/form-field-metadata-value.model';
|
||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||
import { ListableObject } from '../../../../shared/object-collection/shared/listable-object.model';
|
||||
import { GenericConstructor } from '../../../shared/generic-constructor';
|
||||
|
||||
/**
|
||||
* Model class for a Vocabulary
|
||||
*/
|
||||
@typedObject
|
||||
export class VocabularyEntry extends ListableObject {
|
||||
static type = VOCABULARY_ENTRY;
|
||||
|
||||
/**
|
||||
* The identifier of this vocabulary entry
|
||||
*/
|
||||
@autoserialize
|
||||
authority: string;
|
||||
|
||||
/**
|
||||
* The display value of this vocabulary entry
|
||||
*/
|
||||
@autoserialize
|
||||
display: string;
|
||||
|
||||
/**
|
||||
* The value of this vocabulary entry
|
||||
*/
|
||||
@autoserialize
|
||||
value: string;
|
||||
|
||||
/**
|
||||
* An object containing additional information related to this vocabulary entry
|
||||
*/
|
||||
@autoserialize
|
||||
otherInformation: OtherInformation;
|
||||
|
||||
/**
|
||||
* A string representing the kind of vocabulary entry
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
public type: any;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this ExternalSourceEntry
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink;
|
||||
vocabularyEntryDetail: HALLink;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method checks if entry has an authority value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasAuthority(): boolean {
|
||||
return isNotEmpty(this.authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if entry has a value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasValue(): boolean {
|
||||
return isNotEmpty(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if entry has related information object
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasOtherInformation(): boolean {
|
||||
return isNotEmpty(this.otherInformation);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks if entry has a placeholder as value
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
hasPlaceholder(): boolean {
|
||||
return this.hasValue() && this.value === PLACEHOLDER_PARENT_METADATA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that returns as which type of object this object should be rendered
|
||||
*/
|
||||
getRenderTypes(): Array<string | GenericConstructor<ListableObject>> {
|
||||
return [this.constructor as GenericConstructor<ListableObject>];
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
import { autoserialize, deserialize } from 'cerialize';
|
||||
|
||||
import { HALLink } from '../../../shared/hal-link.model';
|
||||
import { VOCABULARY } from './vocabularies.resource-type';
|
||||
import { CacheableObject } from '../../../cache/object-cache.reducer';
|
||||
import { typedObject } from '../../../cache/builders/build-decorators';
|
||||
import { excludeFromEquals } from '../../../utilities/equals.decorators';
|
||||
|
||||
/**
|
||||
* Model class for a Vocabulary
|
||||
*/
|
||||
@typedObject
|
||||
export class Vocabulary implements CacheableObject {
|
||||
static type = VOCABULARY;
|
||||
/**
|
||||
* The identifier of this Vocabulary
|
||||
*/
|
||||
@autoserialize
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* The name of this Vocabulary
|
||||
*/
|
||||
@autoserialize
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* True if it is possible to scroll all the entries in the vocabulary without providing a filter parameter
|
||||
*/
|
||||
@autoserialize
|
||||
scrollable: boolean;
|
||||
|
||||
/**
|
||||
* True if the vocabulary exposes a tree structure where some entries are parent of others
|
||||
*/
|
||||
@autoserialize
|
||||
hierarchical: boolean;
|
||||
|
||||
/**
|
||||
* For hierarchical vocabularies express the preference to preload the tree at a specific
|
||||
* level of depth (0 only the top nodes are shown, 1 also their children are preloaded and so on)
|
||||
*/
|
||||
@autoserialize
|
||||
preloadLevel: any;
|
||||
|
||||
/**
|
||||
* A string representing the kind of Vocabulary model
|
||||
*/
|
||||
@excludeFromEquals
|
||||
@autoserialize
|
||||
public type: any;
|
||||
|
||||
/**
|
||||
* The {@link HALLink}s for this Vocabulary
|
||||
*/
|
||||
@deserialize
|
||||
_links: {
|
||||
self: HALLink,
|
||||
entries: HALLink
|
||||
};
|
||||
}
|
@@ -0,0 +1,111 @@
|
||||
import { getMockObjectCacheService } from '../../../shared/mocks/object-cache.service.mock';
|
||||
import { ErrorResponse, GenericSuccessResponse } from '../../cache/response.models';
|
||||
import { DSpaceRESTV2Response } from '../../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||
import { VocabularyEntriesResponseParsingService } from './vocabulary-entries-response-parsing.service';
|
||||
import { VocabularyEntriesRequest } from '../../data/request.models';
|
||||
|
||||
fdescribe('VocabularyEntriesResponseParsingService', () => {
|
||||
let service: VocabularyEntriesResponseParsingService;
|
||||
const metadata = 'dc.type';
|
||||
const collectionUUID = '8b39g7ya-5a4b-438b-851f-be1d5b4a1c5a';
|
||||
const entriesRequestURL = `https://rest.api/rest/api/submission/vocabularies/types/entries?metadata=${metadata}&collection=${collectionUUID}`
|
||||
|
||||
beforeEach(() => {
|
||||
service = new VocabularyEntriesResponseParsingService(getMockObjectCacheService());
|
||||
});
|
||||
|
||||
describe('parse', () => {
|
||||
const request = new VocabularyEntriesRequest('client/f5b4ccb8-fbb0-4548-b558-f234d9fdfad6', entriesRequestURL);
|
||||
|
||||
const validResponse = {
|
||||
payload: {
|
||||
_embedded: {
|
||||
entries: [
|
||||
{
|
||||
display: 'testValue1',
|
||||
value: 'testValue1',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
},
|
||||
{
|
||||
display: 'testValue2',
|
||||
value: 'testValue2',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
},
|
||||
{
|
||||
display: 'testValue3',
|
||||
value: 'testValue3',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
},
|
||||
{
|
||||
authority: 'authorityId1',
|
||||
display: 'testValue1',
|
||||
value: 'testValue1',
|
||||
otherInformation: {
|
||||
id: 'VR131402',
|
||||
parent: 'Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work',
|
||||
hasChildren: 'false',
|
||||
note: 'Familjeforskning'
|
||||
},
|
||||
type: 'vocabularyEntry',
|
||||
_links: {
|
||||
vocabularyEntryDetail: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:VR131402'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
_links: {
|
||||
first: {
|
||||
href: 'https://rest.api/discover/browses/author/entries?page=0&size=5'
|
||||
},
|
||||
self: {
|
||||
href: 'https://rest.api/discover/browses/author/entries'
|
||||
},
|
||||
next: {
|
||||
href: 'https://rest.api/discover/browses/author/entries?page=1&size=5'
|
||||
},
|
||||
last: {
|
||||
href: 'https://rest.api/discover/browses/author/entries?page=9&size=5'
|
||||
}
|
||||
},
|
||||
page: {
|
||||
size: 5,
|
||||
totalElements: 50,
|
||||
totalPages: 10,
|
||||
number: 0
|
||||
}
|
||||
},
|
||||
statusCode: 200,
|
||||
statusText: 'OK'
|
||||
} as DSpaceRESTV2Response;
|
||||
|
||||
const invalidResponseNotAList = {
|
||||
statusCode: 200,
|
||||
statusText: 'OK'
|
||||
} as DSpaceRESTV2Response;
|
||||
|
||||
const invalidResponseStatusCode = {
|
||||
payload: {}, statusCode: 500, statusText: 'Internal Server Error'
|
||||
} as DSpaceRESTV2Response;
|
||||
|
||||
it('should return a GenericSuccessResponse if data contains a valid browse entries response', () => {
|
||||
const response = service.parse(request, validResponse);
|
||||
expect(response.constructor).toBe(GenericSuccessResponse);
|
||||
});
|
||||
|
||||
it('should return an ErrorResponse if data contains an invalid browse entries response', () => {
|
||||
const response = service.parse(request, invalidResponseNotAList);
|
||||
expect(response.constructor).toBe(ErrorResponse);
|
||||
});
|
||||
|
||||
it('should return an ErrorResponse if data contains a statuscode other than 200', () => {
|
||||
const response = service.parse(request, invalidResponseStatusCode);
|
||||
expect(response.constructor).toBe(ErrorResponse);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
@@ -0,0 +1,20 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||
import { BrowseEntriesResponseParsingService } from '../../data/browse-entries-response-parsing.service';
|
||||
|
||||
/**
|
||||
* A service responsible for parsing data for a vocabulary entries response
|
||||
*/
|
||||
@Injectable()
|
||||
export class VocabularyEntriesResponseParsingService extends BrowseEntriesResponseParsingService {
|
||||
|
||||
protected toCache = false;
|
||||
|
||||
constructor(
|
||||
protected objectCache: ObjectCacheService,
|
||||
) {
|
||||
super(objectCache);
|
||||
}
|
||||
|
||||
}
|
269
src/app/core/submission/vocabularies/vocabulary.service.spec.ts
Normal file
269
src/app/core/submission/vocabularies/vocabulary.service.spec.ts
Normal file
@@ -0,0 +1,269 @@
|
||||
import { HttpClient } 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 { 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';
|
||||
import { createSuccessfulRemoteDataObject } from '../../../shared/remote-data.utils';
|
||||
import { RequestEntry } from '../../data/request.reducer';
|
||||
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';
|
||||
|
||||
describe('VocabularyService', () => {
|
||||
let scheduler: TestScheduler;
|
||||
let service: VocabularyService;
|
||||
let requestService: RequestService;
|
||||
let rdbService: RemoteDataBuildService;
|
||||
let objectCache: ObjectCacheService;
|
||||
let halService: HALEndpointService;
|
||||
let responseCacheEntry: RequestEntry;
|
||||
|
||||
const vocabulary: any = {
|
||||
id: 'types',
|
||||
name: 'types',
|
||||
scrollable: true,
|
||||
hierarchical: false,
|
||||
preloadLevel: 1,
|
||||
type: 'vocabulary',
|
||||
uuid: 'vocabulary-types',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types'
|
||||
},
|
||||
entries: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types/entries'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const anotherVocabulary: any = {
|
||||
id: 'srsc',
|
||||
name: 'srsc',
|
||||
scrollable: false,
|
||||
hierarchical: true,
|
||||
preloadLevel: 2,
|
||||
type: 'vocabulary',
|
||||
uuid: 'vocabulary-srsc',
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types'
|
||||
},
|
||||
entries: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularies/types/entries'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
const vocabularyEntry: any = {
|
||||
display: 'testValue1',
|
||||
value: 'testValue1',
|
||||
otherInformation: {},
|
||||
type: 'vocabularyEntry'
|
||||
};
|
||||
|
||||
const vocabularyEntryWithAuthority: any = {
|
||||
authority: 'authorityId1',
|
||||
display: 'testValue1',
|
||||
value: 'testValue1',
|
||||
otherInformation: {
|
||||
id: 'VR131402',
|
||||
parent: 'Research Subject Categories::SOCIAL SCIENCES::Social sciences::Social work',
|
||||
hasChildren: 'false',
|
||||
note: 'Familjeforskning'
|
||||
},
|
||||
type: 'vocabularyEntry',
|
||||
_links: {
|
||||
vocabularyEntryDetail: {
|
||||
href: 'https://rest.api/rest/api/submission/vocabularyEntryDetails/srsc:VR131402'
|
||||
}
|
||||
}
|
||||
};
|
||||
const endpointURL = `https://rest.api/rest/api/submission/vocabularies`;
|
||||
const requestURL = `https://rest.api/rest/api/submission/vocabularies/${vocabulary.id}`;
|
||||
const requestUUID = '8b3c613a-5a4b-438b-9686-be1d5b4a1c5a';
|
||||
const vocabularyId = 'types';
|
||||
const metadata = 'dc.type';
|
||||
const collectionUUID = '8b39g7ya-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 pageInfo = new PageInfo();
|
||||
const array = [vocabulary, anotherVocabulary];
|
||||
const paginatedList = new PaginatedList(pageInfo, array);
|
||||
const vocabularyRD = createSuccessfulRemoteDataObject(vocabulary);
|
||||
const paginatedListRD = createSuccessfulRemoteDataObject(paginatedList);
|
||||
const getRequestEntry$ = (successful: boolean) => {
|
||||
return observableOf({
|
||||
response: { isSuccessful: successful, payload: vocabulary } as any
|
||||
} as RequestEntry)
|
||||
};
|
||||
objectCache = {} as ObjectCacheService;
|
||||
const notificationsService = {} as NotificationsService;
|
||||
const http = {} as HttpClient;
|
||||
const comparator = {} as any;
|
||||
|
||||
function initTestService() {
|
||||
return new VocabularyService(
|
||||
requestService,
|
||||
rdbService,
|
||||
objectCache,
|
||||
halService,
|
||||
notificationsService,
|
||||
http,
|
||||
comparator
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
halService = jasmine.createSpyObj('halService', {
|
||||
getEndpoint: cold('a', { a: endpointURL })
|
||||
});
|
||||
|
||||
responseCacheEntry = new RequestEntry();
|
||||
responseCacheEntry.response = new RestResponse(true, 200, 'Success');
|
||||
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: requestUUID,
|
||||
configure: true,
|
||||
removeByHrefSubstring: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: observableOf(responseCacheEntry),
|
||||
});
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: hot('a|', {
|
||||
a: vocabularyRD
|
||||
}),
|
||||
buildList: hot('a|', {
|
||||
a: paginatedListRD
|
||||
}),
|
||||
});
|
||||
|
||||
service = initTestService();
|
||||
|
||||
spyOn((service as any).dataService, 'findById').and.callThrough();
|
||||
spyOn((service as any).dataService, 'findAll').and.callThrough();
|
||||
spyOn((service as any).dataService, 'findByHref').and.callThrough();
|
||||
spyOn((service as any).dataService, 'searchBy').and.callThrough();
|
||||
spyOn((service as any).dataService, 'getSearchByHref').and.returnValue(observableOf(searchRequestURL));
|
||||
spyOn((service as any).dataService, 'getFindAllHref').and.returnValue(observableOf(entriesRequestURL));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
service = null;
|
||||
});
|
||||
|
||||
describe('findById', () => {
|
||||
it('should proxy the call to dataservice.findById', () => {
|
||||
scheduler.schedule(() => service.findById(vocabularyId));
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).dataService.findById).toHaveBeenCalledWith(vocabularyId);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<Vocabulary> for the object with the given id', () => {
|
||||
const result = service.findById(vocabularyId);
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findByHref', () => {
|
||||
it('should proxy the call to dataservice.findByHref', () => {
|
||||
scheduler.schedule(() => service.findByHref(requestURL));
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).dataService.findByHref).toHaveBeenCalledWith(requestURL);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<Vocabulary> for the object with the given URL', () => {
|
||||
const result = service.findByHref(requestURL);
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAll', () => {
|
||||
it('should proxy the call to dataservice.findAll', () => {
|
||||
scheduler.schedule(() => service.findAll());
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).dataService.findAll).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return a RemoteData<PaginatedList<Vocabulary>>', () => {
|
||||
const result = service.findAll();
|
||||
const expected = cold('a|', {
|
||||
a: paginatedListRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchByMetadataAndCollection', () => {
|
||||
it('should proxy the call to dataservice.findByHref', () => {
|
||||
const options = new FindListOptions();
|
||||
options.searchParams = [
|
||||
new RequestParam('metadata', metadata),
|
||||
new RequestParam('collection', collectionUUID)
|
||||
];
|
||||
scheduler.schedule(() => service.searchByMetadataAndCollection(metadata, collectionUUID).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect((service as any).dataService.findByHref).toHaveBeenCalledWith(searchRequestURL);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<Vocabulary> for the search', () => {
|
||||
const result = service.searchByMetadataAndCollection(metadata, collectionUUID);
|
||||
const expected = cold('a|', {
|
||||
a: vocabularyRD
|
||||
});
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('getVocabularyEntries', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
requestService = getMockRequestService(getRequestEntry$(true));
|
||||
rdbService = getMockRemoteDataBuildService();
|
||||
spyOn(rdbService, 'toRemoteDataObservable').and.callThrough();
|
||||
service = initTestService();
|
||||
});
|
||||
|
||||
it('should configure a new VocabularyEntriesRequest', () => {
|
||||
const expected = new VocabularyEntriesRequest(requestService.generateRequestId(), entriesRequestURL);
|
||||
|
||||
scheduler.schedule(() => service.getVocabularyEntries(vocabularyId, metadata, collectionUUID).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
service.getVocabularyEntries(vocabularyId, metadata, collectionUUID);
|
||||
|
||||
expect(rdbService.toRemoteDataObservable).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
194
src/app/core/submission/vocabularies/vocabulary.service.ts
Normal file
194
src/app/core/submission/vocabularies/vocabulary.service.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, first, flatMap, map } from 'rxjs/operators';
|
||||
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { dataService } from '../../cache/builders/build-decorators';
|
||||
import { DataService } from '../../data/data.service';
|
||||
import { RequestService } from '../../data/request.service';
|
||||
import { FindListOptions, RestRequest, VocabularyEntriesRequest } from '../../data/request.models';
|
||||
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||
import { CoreState } from '../../core.reducers';
|
||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { ChangeAnalyzer } from '../../data/change-analyzer';
|
||||
import { DefaultChangeAnalyzer } from '../../data/default-change-analyzer.service';
|
||||
import { PaginatedList } from '../../data/paginated-list';
|
||||
import { RequestParam } from '../../cache/models/request-param.model';
|
||||
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 { GenericSuccessResponse } from '../../cache/response.models';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
*/
|
||||
class DataServiceImpl extends DataService<Vocabulary> {
|
||||
protected linkPath = 'vocabularies';
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: ChangeAnalyzer<Vocabulary>) {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A service responsible for fetching/sending data from/to the REST API on the vocabularies endpoint
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
@dataService(VOCABULARY)
|
||||
export class VocabularyService {
|
||||
protected searchByMetadataAndCollectionMethod = 'byMetadataAndCollection';
|
||||
private dataService: DataServiceImpl;
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<Vocabulary>) {
|
||||
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of a {@link Vocabulary}, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the {@link Vocabulary}
|
||||
* @param href The url 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<RemoteData<Vocabulary>>}
|
||||
* Return an observable that emits vocabulary object
|
||||
*/
|
||||
findByHref(href: string, ...linksToFollow: Array<FollowLinkConfig<Vocabulary>>): Observable<RemoteData<any>> {
|
||||
return this.dataService.findByHref(href, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<Vocabulary>>}
|
||||
* Return an observable that emits vocabulary object
|
||||
*/
|
||||
findById(id: string, ...linksToFollow: Array<FollowLinkConfig<Vocabulary>>): Observable<RemoteData<Vocabulary>> {
|
||||
return this.dataService.findById(id, ...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 linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<Vocabulary>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
findAll(options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<Vocabulary>>): Observable<RemoteData<PaginatedList<Vocabulary>>> {
|
||||
return this.dataService.findAll(options, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link VocabularyEntry} list for a given {@link Vocabulary}
|
||||
*
|
||||
* @param id The vocabulary id to retrieve the entries for
|
||||
* @param metadata The metadata name
|
||||
* @param collectionUUID The collection UUID
|
||||
* @param options The {@link FindListOptions} for the request
|
||||
* @return {Observable<RemoteData<PaginatedList<VocabularyEntry>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
getVocabularyEntries(id: string, metadata: string, collectionUUID: string, options: FindListOptions = {}): Observable<RemoteData<PaginatedList<VocabularyEntry>>> {
|
||||
options = Object.assign({}, options, {
|
||||
searchParams: [
|
||||
new RequestParam('metadata', metadata),
|
||||
new RequestParam('collection', collectionUUID)
|
||||
]
|
||||
});
|
||||
|
||||
return this.dataService.getFindAllHref(options, `${id}/entries`).pipe(
|
||||
isNotEmptyOperator(),
|
||||
distinctUntilChanged(),
|
||||
getVocabularyEntriesFor(this.requestService, this.rdbService)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the controlled {@link Vocabulary} configured for the specified metadata and collection if any.
|
||||
*
|
||||
* @param metadata The metadata name
|
||||
* @param collectionUUID The collection UUID
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<Vocabulary>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
searchByMetadataAndCollection(metadata: string, collectionUUID: string, ...linksToFollow: Array<FollowLinkConfig<Vocabulary>>): Observable<RemoteData<Vocabulary>> {
|
||||
const options = new FindListOptions();
|
||||
options.searchParams = [
|
||||
new RequestParam('metadata', metadata),
|
||||
new RequestParam('collection', collectionUUID)
|
||||
];
|
||||
|
||||
return this.dataService.getSearchByHref(this.searchByMetadataAndCollectionMethod, options).pipe(
|
||||
first((href: string) => hasValue(href)),
|
||||
flatMap((href: string) => this.dataService.findByHref(href))
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Operator for turning a href into a PaginatedList of VocabularyEntry
|
||||
* @param requestService
|
||||
* @param rdb
|
||||
*/
|
||||
export const getVocabularyEntriesFor = (requestService: RequestService, rdb: RemoteDataBuildService) =>
|
||||
(source: Observable<string>): Observable<RemoteData<PaginatedList<VocabularyEntry>>> =>
|
||||
source.pipe(
|
||||
map((href: string) => new VocabularyEntriesRequest(requestService.generateRequestId(), href)),
|
||||
configureRequest(requestService),
|
||||
toRDPaginatedVocabularyEntries(requestService, rdb)
|
||||
);
|
||||
|
||||
/**
|
||||
* Operator for turning a RestRequest into a PaginatedList of VocabularyEntry
|
||||
* @param requestService
|
||||
* @param rdb
|
||||
*/
|
||||
export const toRDPaginatedVocabularyEntries = (requestService: RequestService, rdb: RemoteDataBuildService) =>
|
||||
(source: Observable<RestRequest>): Observable<RemoteData<PaginatedList<VocabularyEntry>>> => {
|
||||
const href$ = source.pipe(map((request: RestRequest) => request.href));
|
||||
|
||||
const requestEntry$ = href$.pipe(getRequestFromRequestHref(requestService));
|
||||
|
||||
const payload$ = requestEntry$.pipe(
|
||||
filterSuccessfulResponses(),
|
||||
map((response: GenericSuccessResponse<VocabularyEntry[]>) => new PaginatedList(response.pageInfo, response.payload)),
|
||||
map((list: PaginatedList<VocabularyEntry>) => Object.assign(list, {
|
||||
page: list.page ? list.page.map((entry: VocabularyEntry) => Object.assign(new VocabularyEntry(), entry)) : list.page
|
||||
})),
|
||||
distinctUntilChanged()
|
||||
);
|
||||
|
||||
return rdb.toRemoteDataObservable(requestEntry$, payload$);
|
||||
};
|
Reference in New Issue
Block a user