mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-11 12:03:03 +00:00
intermediate commit
This commit is contained in:

committed by
Lotte Hofstede

parent
605c3524c8
commit
4c2cbc55e0
13
src/app/+search-page/normalized-search-result.model.ts
Normal file
13
src/app/+search-page/normalized-search-result.model.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { autoserialize } from 'cerialize';
|
||||||
|
import { Metadatum } from '../core/shared/metadatum.model';
|
||||||
|
import { ListableObject } from '../shared/object-collection/shared/listable-object.model';
|
||||||
|
|
||||||
|
export class NormalizedSearchResult implements ListableObject {
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
dspaceObject: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
hitHighlights: Metadatum[];
|
||||||
|
|
||||||
|
}
|
@@ -96,8 +96,8 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
link = comp.getSearchLink();
|
link = comp.getSearchLink();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the value of the searchLink variable in the filter service', () => {
|
it('should return the value of the uiSearchRoute variable in the filter service', () => {
|
||||||
expect(link).toEqual(filterService.searchLink);
|
expect(link).toEqual(filterService.uiSearchRoute);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -192,13 +192,13 @@ describe('SearchFilterService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the searchLink method is called', () => {
|
describe('when the uiSearchRoute method is called', () => {
|
||||||
let link: string;
|
let link: string;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
link = service.searchLink;
|
link = service.searchLink;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the value of searchLink in the search service', () => {
|
it('should return the value of uiSearchRoute in the search service', () => {
|
||||||
expect(link).toEqual(searchServiceStub.searchLink);
|
expect(link).toEqual(searchServiceStub.searchLink);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -46,7 +46,7 @@ export class SearchFilterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get searchLink() {
|
get searchLink() {
|
||||||
return this.searchService.searchLink;
|
return this.searchService.uiSearchRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCollapsed(filterName: string): Observable<boolean> {
|
isCollapsed(filterName: string): Observable<boolean> {
|
||||||
|
@@ -0,0 +1,47 @@
|
|||||||
|
import { autoserialize, autoserializeAs } from 'cerialize';
|
||||||
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
import { NormalizedSearchResult } from '../normalized-search-result.model';
|
||||||
|
|
||||||
|
export class SearchQueryResponse {
|
||||||
|
@autoserialize
|
||||||
|
scope: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
query: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
appliedFilters: any[]; // TODO
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
sort: any; // TODO
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
configurationName: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
public type: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
page: PageInfo;
|
||||||
|
|
||||||
|
@autoserializeAs(NormalizedSearchResult)
|
||||||
|
objects: NormalizedSearchResult[];
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
facets: any; // TODO
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
self: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
next: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
previous: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
first: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
last: string;
|
||||||
|
}
|
@@ -1,12 +1,25 @@
|
|||||||
import { Injectable, OnDestroy } from '@angular/core';
|
import { Inject, Injectable, OnDestroy } from '@angular/core';
|
||||||
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
import { ViewMode } from '../../+search-page/search-options.model';
|
import { ViewMode } from '../../+search-page/search-options.model';
|
||||||
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
import { SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { RestResponse } from '../../core/cache/response-cache.models';
|
||||||
|
import { ResponseCacheService } from '../../core/cache/response-cache.service';
|
||||||
|
import { DebugResponseParsingService } from '../../core/data/debug-response-parsing.service';
|
||||||
|
import { DSOResponseParsingService } from '../../core/data/dso-response-parsing.service';
|
||||||
import { ItemDataService } from '../../core/data/item-data.service';
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list';
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { ResponseParsingService } from '../../core/data/parsing.service';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { GetRequest, EndpointMapRequest, RestRequest } from '../../core/data/request.models';
|
||||||
|
import { RequestService } from '../../core/data/request.service';
|
||||||
|
import { DSpaceRESTV2Response } from '../../core/dspace-rest-v2/dspace-rest-v2-response.model';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
|
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { Metadatum } from '../../core/shared/metadatum.model';
|
import { Metadatum } from '../../core/shared/metadatum.model';
|
||||||
import { PageInfo } from '../../core/shared/page-info.model';
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
@@ -19,6 +32,7 @@ import { SearchResult } from '../search-result.model';
|
|||||||
import { FacetValue } from './facet-value.model';
|
import { FacetValue } from './facet-value.model';
|
||||||
import { FilterType } from './filter-type.model';
|
import { FilterType } from './filter-type.model';
|
||||||
import { SearchFilterConfig } from './search-filter-config.model';
|
import { SearchFilterConfig } from './search-filter-config.model';
|
||||||
|
import { SearchResponseParsingService } from '../../core/data/search-response-parsing.service';
|
||||||
|
|
||||||
function shuffle(array: any[]) {
|
function shuffle(array: any[]) {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
@@ -35,23 +49,11 @@ function shuffle(array: any[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchService implements OnDestroy {
|
export class SearchService extends HALEndpointService implements OnDestroy {
|
||||||
|
protected linkPath = 'discover/search/objects';
|
||||||
|
|
||||||
totalPages = 5;
|
|
||||||
mockedHighlights: string[] = new Array(
|
|
||||||
'This is a <em>sample abstract</em>.',
|
|
||||||
'This is a sample abstract. But, to fill up some space, here\'s <em>"Hello"</em> in several different languages : ',
|
|
||||||
'This is a Sample HTML webpage including several <em>images</em> and styles (CSS).',
|
|
||||||
'This is <em>really</em> just a sample abstract. But, Í’vé thrown ïn a cõuple of spëciâl charactèrs för êxtrå fuñ!',
|
|
||||||
'This abstract is <em>really quite great</em>',
|
|
||||||
'The solution structure of the <em>bee</em> venom neurotoxin',
|
|
||||||
'BACKGROUND: The <em>Open Archive Initiative (OAI)</em> refers to a movement started around the \'90 s to guarantee free access to scientific information',
|
|
||||||
'The collision fault detection of a <em>XXY</em> stage is proposed for the first time in this paper',
|
|
||||||
'<em>This was blank in the actual item, no abstract</em>',
|
|
||||||
'<em>The QSAR DataBank (QsarDB) repository</em>',
|
|
||||||
);
|
|
||||||
private sub;
|
private sub;
|
||||||
searchLink = '/search';
|
uiSearchRoute = '/search';
|
||||||
|
|
||||||
config: SearchFilterConfig[] = [
|
config: SearchFilterConfig[] = [
|
||||||
Object.assign(new SearchFilterConfig(),
|
Object.assign(new SearchFilterConfig(),
|
||||||
@@ -86,11 +88,16 @@ export class SearchService implements OnDestroy {
|
|||||||
// searchOptions: BehaviorSubject<SearchOptions>;
|
// searchOptions: BehaviorSubject<SearchOptions>;
|
||||||
searchOptions: SearchOptions;
|
searchOptions: SearchOptions;
|
||||||
|
|
||||||
constructor(private itemDataService: ItemDataService,
|
constructor(
|
||||||
private routeService: RouteService,
|
protected responseCache: ResponseCacheService,
|
||||||
private route: ActivatedRoute,
|
protected requestService: RequestService,
|
||||||
private router: Router) {
|
private itemDataService: ItemDataService,
|
||||||
|
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||||
|
private routeService: RouteService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
super();
|
||||||
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
||||||
pagination.id = 'search-results-pagination';
|
pagination.id = 'search-results-pagination';
|
||||||
pagination.currentPage = 1;
|
pagination.currentPage = 1;
|
||||||
@@ -101,74 +108,18 @@ export class SearchService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
search(query: string, scopeId?: string, searchOptions?: SearchOptions): Observable<RemoteData<Array<SearchResult<DSpaceObject>>>> {
|
search(query: string, scopeId?: string, searchOptions?: SearchOptions): Observable<RemoteData<Array<SearchResult<DSpaceObject>>>> {
|
||||||
this.searchOptions = searchOptions;
|
const searchEndpointUrlObs = this.getEndpoint();
|
||||||
let self = `https://dspace7.4science.it/dspace-spring-rest/api/search?query=${query}`;
|
searchEndpointUrlObs.pipe(
|
||||||
if (hasValue(scopeId)) {
|
map((url: string) => {
|
||||||
self += `&scope=${scopeId}`;
|
const request = new GetRequest(this.requestService.generateRequestId(), url);
|
||||||
}
|
return Object.assign(request, {
|
||||||
if (isNotEmpty(searchOptions) && hasValue(searchOptions.pagination.currentPage)) {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
self += `&page=${searchOptions.pagination.currentPage}`;
|
return SearchResponseParsingService;
|
||||||
}
|
}
|
||||||
if (isNotEmpty(searchOptions) && hasValue(searchOptions.pagination.pageSize)) {
|
});
|
||||||
self += `&pageSize=${searchOptions.pagination.pageSize}`;
|
})
|
||||||
}
|
).subscribe((request: RestRequest) => this.requestService.configure(request));
|
||||||
if (isNotEmpty(searchOptions) && hasValue(searchOptions.sort.direction)) {
|
return Observable.of(undefined);
|
||||||
self += `&sortDirection=${searchOptions.sort.direction}`;
|
|
||||||
}
|
|
||||||
if (isNotEmpty(searchOptions) && hasValue(searchOptions.sort.field)) {
|
|
||||||
self += `&sortField=${searchOptions.sort.field}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const error = undefined;
|
|
||||||
const returningPageInfo = new PageInfo();
|
|
||||||
|
|
||||||
if (isNotEmpty(searchOptions)) {
|
|
||||||
returningPageInfo.elementsPerPage = searchOptions.pagination.pageSize;
|
|
||||||
returningPageInfo.currentPage = searchOptions.pagination.currentPage;
|
|
||||||
} else {
|
|
||||||
returningPageInfo.elementsPerPage = 10;
|
|
||||||
returningPageInfo.currentPage = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemsObs = this.itemDataService.findAll({
|
|
||||||
scopeID: scopeId,
|
|
||||||
currentPage: returningPageInfo.currentPage,
|
|
||||||
elementsPerPage: returningPageInfo.elementsPerPage
|
|
||||||
});
|
|
||||||
|
|
||||||
return itemsObs
|
|
||||||
.filter((rd: RemoteData<PaginatedList<Item>>) => rd.hasSucceeded)
|
|
||||||
.map((rd: RemoteData<PaginatedList<Item>>) => {
|
|
||||||
|
|
||||||
const totalElements = rd.payload.totalElements > 20 ? 20 : rd.payload.totalElements;
|
|
||||||
|
|
||||||
const page = shuffle(rd.payload.page)
|
|
||||||
.map((item: Item, index: number) => {
|
|
||||||
const mockResult: SearchResult<DSpaceObject> = new ItemSearchResult();
|
|
||||||
mockResult.dspaceObject = item;
|
|
||||||
const highlight = new Metadatum();
|
|
||||||
highlight.key = 'dc.description.abstract';
|
|
||||||
highlight.value = this.mockedHighlights[index % this.mockedHighlights.length];
|
|
||||||
mockResult.hitHighlights = new Array(highlight);
|
|
||||||
return mockResult;
|
|
||||||
});
|
|
||||||
|
|
||||||
const payload = Object.assign({}, rd.payload, { totalElements: totalElements, page });
|
|
||||||
|
|
||||||
return new RemoteData(
|
|
||||||
rd.isRequestPending,
|
|
||||||
rd.isResponsePending,
|
|
||||||
rd.hasSucceeded,
|
|
||||||
error,
|
|
||||||
payload
|
|
||||||
)
|
|
||||||
}).startWith(new RemoteData(
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfig(): Observable<RemoteData<SearchFilterConfig[]>> {
|
getConfig(): Observable<RemoteData<SearchFilterConfig[]>> {
|
||||||
@@ -231,7 +182,7 @@ export class SearchService implements OnDestroy {
|
|||||||
queryParamsHandling: 'merge'
|
queryParamsHandling: 'merge'
|
||||||
};
|
};
|
||||||
|
|
||||||
this.router.navigate([this.searchLink], navigationExtras);
|
this.router.navigate([this.uiSearchRoute], navigationExtras);
|
||||||
}
|
}
|
||||||
|
|
||||||
getClearFiltersQueryParams(): any {
|
getClearFiltersQueryParams(): any {
|
||||||
@@ -249,7 +200,7 @@ export class SearchService implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSearchLink() {
|
getSearchLink() {
|
||||||
return this.searchLink;
|
return this.uiSearchRoute;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
@@ -110,12 +110,12 @@ describe('BrowseService', () => {
|
|||||||
.returnValue(hot('--a-', { a: browsesEndpointURL }));
|
.returnValue(hot('--a-', { a: browsesEndpointURL }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the URL for the given metadatumKey and linkName', () => {
|
it('should return the URL for the given metadatumKey and linkPath', () => {
|
||||||
const metadatumKey = 'dc.date.issued';
|
const metadatumKey = 'dc.date.issued';
|
||||||
const linkName = 'items';
|
const linkPath = 'items';
|
||||||
const expectedURL = browseDefinitions[0]._links[linkName];
|
const expectedURL = browseDefinitions[0]._links[linkPath];
|
||||||
|
|
||||||
const result = service.getBrowseURLFor(metadatumKey, linkName);
|
const result = service.getBrowseURLFor(metadatumKey, linkPath);
|
||||||
const expected = cold('c-d-', { c: undefined, d: expectedURL });
|
const expected = cold('c-d-', { c: undefined, d: expectedURL });
|
||||||
|
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
@@ -123,10 +123,10 @@ describe('BrowseService', () => {
|
|||||||
|
|
||||||
it('should work when the definition uses a wildcard in the metadatumKey', () => {
|
it('should work when the definition uses a wildcard in the metadatumKey', () => {
|
||||||
const metadatumKey = 'dc.contributor.author'; // should match dc.contributor.* in the definition
|
const metadatumKey = 'dc.contributor.author'; // should match dc.contributor.* in the definition
|
||||||
const linkName = 'items';
|
const linkPath = 'items';
|
||||||
const expectedURL = browseDefinitions[1]._links[linkName];
|
const expectedURL = browseDefinitions[1]._links[linkPath];
|
||||||
|
|
||||||
const result = service.getBrowseURLFor(metadatumKey, linkName);
|
const result = service.getBrowseURLFor(metadatumKey, linkPath);
|
||||||
const expected = cold('c-d-', { c: undefined, d: expectedURL });
|
const expected = cold('c-d-', { c: undefined, d: expectedURL });
|
||||||
|
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
@@ -134,30 +134,30 @@ describe('BrowseService', () => {
|
|||||||
|
|
||||||
it('should throw an error when the key doesn\'t match', () => {
|
it('should throw an error when the key doesn\'t match', () => {
|
||||||
const metadatumKey = 'dc.title'; // isn't in the definitions
|
const metadatumKey = 'dc.title'; // isn't in the definitions
|
||||||
const linkName = 'items';
|
const linkPath = 'items';
|
||||||
|
|
||||||
const result = service.getBrowseURLFor(metadatumKey, linkName);
|
const result = service.getBrowseURLFor(metadatumKey, linkPath);
|
||||||
const expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkName} on ${metadatumKey} isn't configured`));
|
const expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkPath} on ${metadatumKey} isn't configured`));
|
||||||
|
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error when the link doesn\'t match', () => {
|
it('should throw an error when the link doesn\'t match', () => {
|
||||||
const metadatumKey = 'dc.date.issued';
|
const metadatumKey = 'dc.date.issued';
|
||||||
const linkName = 'collections'; // isn't in the definitions
|
const linkPath = 'collections'; // isn't in the definitions
|
||||||
|
|
||||||
const result = service.getBrowseURLFor(metadatumKey, linkName);
|
const result = service.getBrowseURLFor(metadatumKey, linkPath);
|
||||||
const expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkName} on ${metadatumKey} isn't configured`));
|
const expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkPath} on ${metadatumKey} isn't configured`));
|
||||||
|
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should configure a new BrowseEndpointRequest', () => {
|
it('should configure a new BrowseEndpointRequest', () => {
|
||||||
const metadatumKey = 'dc.date.issued';
|
const metadatumKey = 'dc.date.issued';
|
||||||
const linkName = 'items';
|
const linkPath = 'items';
|
||||||
const expected = new BrowseEndpointRequest(requestService.generateRequestId(), browsesEndpointURL);
|
const expected = new BrowseEndpointRequest(requestService.generateRequestId(), browsesEndpointURL);
|
||||||
|
|
||||||
scheduler.schedule(() => service.getBrowseURLFor(metadatumKey, linkName).subscribe());
|
scheduler.schedule(() => service.getBrowseURLFor(metadatumKey, linkPath).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||||
@@ -175,9 +175,9 @@ describe('BrowseService', () => {
|
|||||||
.returnValue(hot('----'));
|
.returnValue(hot('----'));
|
||||||
|
|
||||||
const metadatumKey = 'dc.date.issued';
|
const metadatumKey = 'dc.date.issued';
|
||||||
const linkName = 'items';
|
const linkPath = 'items';
|
||||||
|
|
||||||
const result = service.getBrowseURLFor(metadatumKey, linkName);
|
const result = service.getBrowseURLFor(metadatumKey, linkPath);
|
||||||
const expected = cold('b---', { b: undefined });
|
const expected = cold('b---', { b: undefined });
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
@@ -192,9 +192,9 @@ describe('BrowseService', () => {
|
|||||||
.returnValue(hot('--a-', { a: browsesEndpointURL }));
|
.returnValue(hot('--a-', { a: browsesEndpointURL }));
|
||||||
|
|
||||||
const metadatumKey = 'dc.date.issued';
|
const metadatumKey = 'dc.date.issued';
|
||||||
const linkName = 'items';
|
const linkPath = 'items';
|
||||||
|
|
||||||
const result = service.getBrowseURLFor(metadatumKey, linkName);
|
const result = service.getBrowseURLFor(metadatumKey, linkPath);
|
||||||
const expected = cold('c-#-', { c: undefined }, new Error(`Couldn't retrieve the browses endpoint`));
|
const expected = cold('c-#-', { c: undefined }, new Error(`Couldn't retrieve the browses endpoint`));
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
@@ -13,7 +13,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BrowseService extends HALEndpointService {
|
export class BrowseService extends HALEndpointService {
|
||||||
protected linkName = 'browses';
|
protected linkPath = 'browses';
|
||||||
|
|
||||||
private static toSearchKeyArray(metadatumKey: string): string[] {
|
private static toSearchKeyArray(metadatumKey: string): string[] {
|
||||||
const keyParts = metadatumKey.split('.');
|
const keyParts = metadatumKey.split('.');
|
||||||
@@ -35,7 +35,7 @@ export class BrowseService extends HALEndpointService {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
getBrowseURLFor(metadatumKey: string, linkName: string): Observable<string> {
|
getBrowseURLFor(metadatumKey: string, linkPath: string): Observable<string> {
|
||||||
const searchKeyArray = BrowseService.toSearchKeyArray(metadatumKey);
|
const searchKeyArray = BrowseService.toSearchKeyArray(metadatumKey);
|
||||||
return this.getEndpoint()
|
return this.getEndpoint()
|
||||||
.filter((href: string) => isNotEmpty(href))
|
.filter((href: string) => isNotEmpty(href))
|
||||||
@@ -59,10 +59,10 @@ export class BrowseService extends HALEndpointService {
|
|||||||
return isNotEmpty(matchingKeys);
|
return isNotEmpty(matchingKeys);
|
||||||
})
|
})
|
||||||
).map((def: BrowseDefinition) => {
|
).map((def: BrowseDefinition) => {
|
||||||
if (isEmpty(def) || isEmpty(def._links) || isEmpty(def._links[linkName])) {
|
if (isEmpty(def) || isEmpty(def._links) || isEmpty(def._links[linkPath])) {
|
||||||
throw new Error(`A browse endpoint for ${linkName} on ${metadatumKey} isn't configured`);
|
throw new Error(`A browse endpoint for ${linkPath} on ${metadatumKey} isn't configured`);
|
||||||
} else {
|
} else {
|
||||||
return def._links[linkName];
|
return def._links[linkPath];
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
17
src/app/core/cache/response-cache.models.ts
vendored
17
src/app/core/cache/response-cache.models.ts
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
import { SearchQueryResponse } from '../../+search-page/search-service/search-query-response.model';
|
||||||
import { RequestError } from '../data/request.models';
|
import { RequestError } from '../data/request.models';
|
||||||
import { PageInfo } from '../shared/page-info.model';
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||||
@@ -21,11 +22,21 @@ export class DSOSuccessResponse extends RestResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EndpointMap {
|
export class SearchSuccessResponse extends RestResponse {
|
||||||
[linkName: string]: string
|
constructor(
|
||||||
|
public results: SearchQueryResponse,
|
||||||
|
public statusCode: string,
|
||||||
|
public pageInfo?: PageInfo
|
||||||
|
) {
|
||||||
|
super(true, statusCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RootSuccessResponse extends RestResponse {
|
export class EndpointMap {
|
||||||
|
[linkPath: string]: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EndpointMapSuccessResponse extends RestResponse {
|
||||||
constructor(
|
constructor(
|
||||||
public endpointMap: EndpointMap,
|
public endpointMap: EndpointMap,
|
||||||
public statusCode: string,
|
public statusCode: string,
|
||||||
|
@@ -11,7 +11,7 @@ const LINK_NAME = 'test';
|
|||||||
const BROWSE = 'search/findByCollection';
|
const BROWSE = 'search/findByCollection';
|
||||||
|
|
||||||
class TestService extends ConfigService {
|
class TestService extends ConfigService {
|
||||||
protected linkName = LINK_NAME;
|
protected linkPath = LINK_NAME;
|
||||||
protected browseEndpoint = BROWSE;
|
protected browseEndpoint = BROWSE;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -16,7 +16,7 @@ export abstract class ConfigService extends HALEndpointService {
|
|||||||
protected request: ConfigRequest;
|
protected request: ConfigRequest;
|
||||||
protected abstract responseCache: ResponseCacheService;
|
protected abstract responseCache: ResponseCacheService;
|
||||||
protected abstract requestService: RequestService;
|
protected abstract requestService: RequestService;
|
||||||
protected abstract linkName: string;
|
protected abstract linkPath: string;
|
||||||
protected abstract EnvConfig: GlobalConfig;
|
protected abstract EnvConfig: GlobalConfig;
|
||||||
protected abstract browseEndpoint: string;
|
protected abstract browseEndpoint: string;
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ import { GlobalConfig } from '../../../config/global-config.interface';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SubmissionDefinitionsConfigService extends ConfigService {
|
export class SubmissionDefinitionsConfigService extends ConfigService {
|
||||||
protected linkName = 'submissiondefinitions';
|
protected linkPath = 'submissiondefinitions';
|
||||||
protected browseEndpoint = 'search/findByCollection';
|
protected browseEndpoint = 'search/findByCollection';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -8,7 +8,7 @@ import { GlobalConfig } from '../../../config/global-config.interface';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SubmissionFormsConfigService extends ConfigService {
|
export class SubmissionFormsConfigService extends ConfigService {
|
||||||
protected linkName = 'submissionforms';
|
protected linkPath = 'submissionforms';
|
||||||
protected browseEndpoint = '';
|
protected browseEndpoint = '';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -8,7 +8,7 @@ import { GlobalConfig } from '../../../config/global-config.interface';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SubmissionSectionsConfigService extends ConfigService {
|
export class SubmissionSectionsConfigService extends ConfigService {
|
||||||
protected linkName = 'submissionsections';
|
protected linkPath = 'submissionsections';
|
||||||
protected browseEndpoint = '';
|
protected browseEndpoint = '';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -17,7 +17,9 @@ import { isNotEmpty } from '../shared/empty.util';
|
|||||||
import { ApiService } from '../shared/api.service';
|
import { ApiService } from '../shared/api.service';
|
||||||
import { CollectionDataService } from './data/collection-data.service';
|
import { CollectionDataService } from './data/collection-data.service';
|
||||||
import { CommunityDataService } from './data/community-data.service';
|
import { CommunityDataService } from './data/community-data.service';
|
||||||
|
import { DebugResponseParsingService } from './data/debug-response-parsing.service';
|
||||||
import { DSOResponseParsingService } from './data/dso-response-parsing.service';
|
import { DSOResponseParsingService } from './data/dso-response-parsing.service';
|
||||||
|
import { SearchResponseParsingService } from './data/search-response-parsing.service';
|
||||||
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { ItemDataService } from './data/item-data.service';
|
import { ItemDataService } from './data/item-data.service';
|
||||||
@@ -27,7 +29,7 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp
|
|||||||
import { RemoteDataBuildService } from './cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from './cache/builders/remote-data-build.service';
|
||||||
import { RequestService } from './data/request.service';
|
import { RequestService } from './data/request.service';
|
||||||
import { ResponseCacheService } from './cache/response-cache.service';
|
import { ResponseCacheService } from './cache/response-cache.service';
|
||||||
import { RootResponseParsingService } from './data/root-response-parsing.service';
|
import { EndpointMapResponseParsingService } from './data/endpoint-map-response-parsing.service';
|
||||||
import { ServerResponseService } from '../shared/server-response.service';
|
import { ServerResponseService } from '../shared/server-response.service';
|
||||||
import { NativeWindowFactory, NativeWindowService } from '../shared/window.service';
|
import { NativeWindowFactory, NativeWindowService } from '../shared/window.service';
|
||||||
import { BrowseService } from './browse/browse.service';
|
import { BrowseService } from './browse/browse.service';
|
||||||
@@ -67,7 +69,9 @@ const PROVIDERS = [
|
|||||||
RemoteDataBuildService,
|
RemoteDataBuildService,
|
||||||
RequestService,
|
RequestService,
|
||||||
ResponseCacheService,
|
ResponseCacheService,
|
||||||
RootResponseParsingService,
|
EndpointMapResponseParsingService,
|
||||||
|
DebugResponseParsingService,
|
||||||
|
SearchResponseParsingService,
|
||||||
ServerResponseService,
|
ServerResponseService,
|
||||||
BrowseResponseParsingService,
|
BrowseResponseParsingService,
|
||||||
BrowseService,
|
BrowseService,
|
||||||
|
@@ -13,7 +13,7 @@ import { RequestService } from './request.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
|
export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
|
||||||
protected linkName = 'collections';
|
protected linkPath = 'collections';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected responseCache: ResponseCacheService,
|
protected responseCache: ResponseCacheService,
|
||||||
|
@@ -22,7 +22,7 @@ class NormalizedTestObject implements CacheableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class TestService extends ComColDataService<NormalizedTestObject, any> {
|
class TestService extends ComColDataService<NormalizedTestObject, any> {
|
||||||
protected linkName = LINK_NAME;
|
protected linkPath = LINK_NAME;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected responseCache: ResponseCacheService,
|
protected responseCache: ResponseCacheService,
|
||||||
|
@@ -17,7 +17,7 @@ export abstract class ComColDataService<TNormalized extends CacheableObject, TDo
|
|||||||
/**
|
/**
|
||||||
* Get the scoped endpoint URL by fetching the object with
|
* Get the scoped endpoint URL by fetching the object with
|
||||||
* the given scopeID and returning its HAL link with this
|
* the given scopeID and returning its HAL link with this
|
||||||
* data-service's linkName
|
* data-service's linkPath
|
||||||
*
|
*
|
||||||
* @param {string} scopeID
|
* @param {string} scopeID
|
||||||
* the id of the scope object
|
* the id of the scope object
|
||||||
@@ -48,7 +48,7 @@ export abstract class ComColDataService<TNormalized extends CacheableObject, TDo
|
|||||||
Observable.throw(new Error(`The Community with scope ${scopeID} couldn't be retrieved`))),
|
Observable.throw(new Error(`The Community with scope ${scopeID} couldn't be retrieved`))),
|
||||||
successResponse
|
successResponse
|
||||||
.flatMap((response: DSOSuccessResponse) => this.objectCache.getByUUID(scopeID, NormalizedCommunity))
|
.flatMap((response: DSOSuccessResponse) => this.objectCache.getByUUID(scopeID, NormalizedCommunity))
|
||||||
.map((nc: NormalizedCommunity) => nc._links[this.linkName])
|
.map((nc: NormalizedCommunity) => nc._links[this.linkPath])
|
||||||
.filter((href) => isNotEmpty(href))
|
.filter((href) => isNotEmpty(href))
|
||||||
).distinctUntilChanged();
|
).distinctUntilChanged();
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ import { RequestService } from './request.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CommunityDataService extends ComColDataService<NormalizedCommunity, Community> {
|
export class CommunityDataService extends ComColDataService<NormalizedCommunity, Community> {
|
||||||
protected linkName = 'communities';
|
protected linkPath = 'communities';
|
||||||
protected cds = this;
|
protected cds = this;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@@ -19,7 +19,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
|||||||
protected abstract requestService: RequestService;
|
protected abstract requestService: RequestService;
|
||||||
protected abstract rdbService: RemoteDataBuildService;
|
protected abstract rdbService: RemoteDataBuildService;
|
||||||
protected abstract store: Store<CoreState>;
|
protected abstract store: Store<CoreState>;
|
||||||
protected abstract linkName: string;
|
protected abstract linkPath: string;
|
||||||
protected abstract EnvConfig: GlobalConfig;
|
protected abstract EnvConfig: GlobalConfig;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
13
src/app/core/data/debug-response-parsing.service.ts
Normal file
13
src/app/core/data/debug-response-parsing.service.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { RestResponse } from '../cache/response-cache.models';
|
||||||
|
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||||
|
import { ResponseParsingService } from './parsing.service';
|
||||||
|
import { RestRequest } from './request.models';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DebugResponseParsingService implements ResponseParsingService {
|
||||||
|
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||||
|
console.log('request', request, 'data', data);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,15 +1,14 @@
|
|||||||
import { Inject, Injectable } from '@angular/core';
|
import { Inject, Injectable } from '@angular/core';
|
||||||
import { GLOBAL_CONFIG } from '../../../config';
|
import { GLOBAL_CONFIG } from '../../../config';
|
||||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
import { ErrorResponse, RestResponse, RootSuccessResponse } from '../cache/response-cache.models';
|
import { ErrorResponse, RestResponse, EndpointMapSuccessResponse } from '../cache/response-cache.models';
|
||||||
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||||
import { ResponseParsingService } from './parsing.service';
|
import { ResponseParsingService } from './parsing.service';
|
||||||
import { RestRequest } from './request.models';
|
import { RestRequest } from './request.models';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RootResponseParsingService implements ResponseParsingService {
|
export class EndpointMapResponseParsingService implements ResponseParsingService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
||||||
) {
|
) {
|
||||||
@@ -21,7 +20,7 @@ export class RootResponseParsingService implements ResponseParsingService {
|
|||||||
for (const link of Object.keys(links)) {
|
for (const link of Object.keys(links)) {
|
||||||
links[link] = links[link].href;
|
links[link] = links[link].href;
|
||||||
}
|
}
|
||||||
return new RootSuccessResponse(links, data.statusCode);
|
return new EndpointMapSuccessResponse(links, data.statusCode);
|
||||||
} else {
|
} else {
|
||||||
return new ErrorResponse(
|
return new ErrorResponse(
|
||||||
Object.assign(
|
Object.assign(
|
@@ -17,7 +17,7 @@ import { RequestService } from './request.service';
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||||
protected linkName = 'items';
|
protected linkPath = 'items';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected responseCache: ResponseCacheService,
|
protected responseCache: ResponseCacheService,
|
||||||
@@ -34,7 +34,7 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
|
|||||||
if (isEmpty(scopeID)) {
|
if (isEmpty(scopeID)) {
|
||||||
return this.getEndpoint();
|
return this.getEndpoint();
|
||||||
} else {
|
} else {
|
||||||
return this.bs.getBrowseURLFor('dc.date.issued', this.linkName)
|
return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath)
|
||||||
.filter((href: string) => isNotEmpty(href))
|
.filter((href: string) => isNotEmpty(href))
|
||||||
.map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString())
|
.map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString())
|
||||||
.distinctUntilChanged();
|
.distinctUntilChanged();
|
||||||
|
@@ -4,7 +4,7 @@ import { GlobalConfig } from '../../../config/global-config.interface';
|
|||||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||||
import { DSOResponseParsingService } from './dso-response-parsing.service';
|
import { DSOResponseParsingService } from './dso-response-parsing.service';
|
||||||
import { ResponseParsingService } from './parsing.service';
|
import { ResponseParsingService } from './parsing.service';
|
||||||
import { RootResponseParsingService } from './root-response-parsing.service';
|
import { EndpointMapResponseParsingService } from './endpoint-map-response-parsing.service';
|
||||||
import { BrowseResponseParsingService } from './browse-response-parsing.service';
|
import { BrowseResponseParsingService } from './browse-response-parsing.service';
|
||||||
import { ConfigResponseParsingService } from './config-response-parsing.service';
|
import { ConfigResponseParsingService } from './config-response-parsing.service';
|
||||||
|
|
||||||
@@ -140,14 +140,24 @@ export class FindAllRequest extends GetRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RootEndpointRequest extends GetRequest {
|
export class EndpointMapRequest extends GetRequest {
|
||||||
constructor(uuid: string, EnvConfig: GlobalConfig) {
|
constructor(
|
||||||
const href = new RESTURLCombiner(EnvConfig, '/').toString();
|
public uuid: string,
|
||||||
super(uuid, href);
|
public href: string,
|
||||||
|
public body?: any
|
||||||
|
) {
|
||||||
|
super(uuid, href, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
return RootResponseParsingService;
|
return EndpointMapResponseParsingService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RootEndpointRequest extends EndpointMapRequest {
|
||||||
|
constructor(uuid: string, EnvConfig: GlobalConfig) {
|
||||||
|
const href = new RESTURLCombiner(EnvConfig, '/').toString();
|
||||||
|
super(uuid, href);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
42
src/app/core/data/search-response-parsing.service.ts
Normal file
42
src/app/core/data/search-response-parsing.service.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {
|
||||||
|
DSOSuccessResponse, RestResponse,
|
||||||
|
SearchSuccessResponse
|
||||||
|
} from '../cache/response-cache.models';
|
||||||
|
import { DSOResponseParsingService } from './dso-response-parsing.service';
|
||||||
|
import { ResponseParsingService } from './parsing.service';
|
||||||
|
import { RestRequest } from './request.models';
|
||||||
|
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
|
||||||
|
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||||
|
import { PageInfo } from '../shared/page-info.model';
|
||||||
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { SearchQueryResponse } from '../../+search-page/search-service/search-query-response.model';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SearchResponseParsingService implements ResponseParsingService {
|
||||||
|
constructor(private dsoParser: DSOResponseParsingService) {}
|
||||||
|
|
||||||
|
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
|
||||||
|
const payload = data.payload;
|
||||||
|
const dsoSelfLinks = payload._embedded.objects
|
||||||
|
.map((object) => object._embedded.dspaceObject)
|
||||||
|
// we don't need embedded collections, bitstreamformats, etc for search results.
|
||||||
|
// And parsing them all takes up a lot of time. Throw them away to improve performance
|
||||||
|
// until objs until partial results are supported by the rest api
|
||||||
|
.map((dso) => Object.assign({}, dso, { _embedded: undefined }))
|
||||||
|
.map((dso) => this.dsoParser.parse(request, {
|
||||||
|
payload: dso,
|
||||||
|
statusCode: data.statusCode
|
||||||
|
}))
|
||||||
|
.map((obj) => obj.resourceSelfLinks)
|
||||||
|
.reduce((combined, thisElement) => [...combined, ...thisElement], []);
|
||||||
|
|
||||||
|
const objects = payload._embedded.objects
|
||||||
|
.map((object, index) => Object.assign({}, object, { dspaceObject: dsoSelfLinks[index] }));
|
||||||
|
|
||||||
|
payload.objects = objects;
|
||||||
|
const deserialized = new DSpaceRESTv2Serializer(SearchQueryResponse).deserialize(payload);
|
||||||
|
return new SearchSuccessResponse(deserialized, data.statusCode, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -18,7 +18,7 @@ describe('HALEndpointService', () => {
|
|||||||
|
|
||||||
/* tslint:disable:no-shadowed-variable */
|
/* tslint:disable:no-shadowed-variable */
|
||||||
class TestService extends HALEndpointService {
|
class TestService extends HALEndpointService {
|
||||||
protected linkName = 'test';
|
protected linkPath = 'test';
|
||||||
|
|
||||||
constructor(protected responseCache: ResponseCacheService,
|
constructor(protected responseCache: ResponseCacheService,
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
@@ -29,7 +29,7 @@ describe('HALEndpointService', () => {
|
|||||||
|
|
||||||
/* tslint:enable:no-shadowed-variable */
|
/* tslint:enable:no-shadowed-variable */
|
||||||
|
|
||||||
describe('getEndpointMap', () => {
|
describe('getRootEndpointMap', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
responseCache = jasmine.createSpyObj('responseCache', {
|
responseCache = jasmine.createSpyObj('responseCache', {
|
||||||
get: hot('--a-', {
|
get: hot('--a-', {
|
||||||
@@ -53,13 +53,13 @@ describe('HALEndpointService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should configure a new RootEndpointRequest', () => {
|
it('should configure a new RootEndpointRequest', () => {
|
||||||
(service as any).getEndpointMap();
|
(service as any).getRootEndpointMap();
|
||||||
const expected = new RootEndpointRequest(requestService.generateRequestId(), envConfig);
|
const expected = new RootEndpointRequest(requestService.generateRequestId(), envConfig);
|
||||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an Observable of the endpoint map', () => {
|
it('should return an Observable of the endpoint map', () => {
|
||||||
const result = (service as any).getEndpointMap();
|
const result = (service as any).getRootEndpointMap();
|
||||||
const expected = cold('--b-', { b: endpointMap });
|
const expected = cold('--b-', { b: endpointMap });
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
@@ -74,18 +74,18 @@ describe('HALEndpointService', () => {
|
|||||||
envConfig
|
envConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
spyOn(service as any, 'getEndpointMap').and
|
spyOn(service as any, 'getRootEndpointMap').and
|
||||||
.returnValue(hot('--a-', { a: endpointMap }));
|
.returnValue(hot('--a-', { a: endpointMap }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the endpoint URL for the service\'s linkName', () => {
|
it('should return the endpoint URL for the service\'s linkPath', () => {
|
||||||
const result = service.getEndpoint();
|
const result = service.getEndpoint();
|
||||||
const expected = cold('--b-', { b: endpointMap.test });
|
const expected = cold('--b-', { b: endpointMap.test });
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return undefined for a linkName that isn\'t in the endpoint map', () => {
|
it('should return undefined for a linkPath that isn\'t in the endpoint map', () => {
|
||||||
(service as any).linkName = 'unknown';
|
(service as any).linkPath = 'unknown';
|
||||||
const result = service.getEndpoint();
|
const result = service.getEndpoint();
|
||||||
const expected = cold('--b-', { b: undefined });
|
const expected = cold('--b-', { b: undefined });
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
@@ -103,8 +103,8 @@ describe('HALEndpointService', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return undefined as long as getEndpointMap hasn\'t fired', () => {
|
it('should return undefined as long as getRootEndpointMap hasn\'t fired', () => {
|
||||||
spyOn(service as any, 'getEndpointMap').and
|
spyOn(service as any, 'getRootEndpointMap').and
|
||||||
.returnValue(hot('----'));
|
.returnValue(hot('----'));
|
||||||
|
|
||||||
const result = service.isEnabledOnRestApi();
|
const result = service.isEnabledOnRestApi();
|
||||||
@@ -112,8 +112,8 @@ describe('HALEndpointService', () => {
|
|||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true if the service\'s linkName is in the endpoint map', () => {
|
it('should return true if the service\'s linkPath is in the endpoint map', () => {
|
||||||
spyOn(service as any, 'getEndpointMap').and
|
spyOn(service as any, 'getRootEndpointMap').and
|
||||||
.returnValue(hot('--a-', { a: endpointMap }));
|
.returnValue(hot('--a-', { a: endpointMap }));
|
||||||
|
|
||||||
const result = service.isEnabledOnRestApi();
|
const result = service.isEnabledOnRestApi();
|
||||||
@@ -121,11 +121,11 @@ describe('HALEndpointService', () => {
|
|||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return false if the service\'s linkName isn\'t in the endpoint map', () => {
|
it('should return false if the service\'s linkPath isn\'t in the endpoint map', () => {
|
||||||
spyOn(service as any, 'getEndpointMap').and
|
spyOn(service as any, 'getRootEndpointMap').and
|
||||||
.returnValue(hot('--a-', { a: endpointMap }));
|
.returnValue(hot('--a-', { a: endpointMap }));
|
||||||
|
|
||||||
(service as any).linkName = 'unknown';
|
(service as any).linkPath = 'unknown';
|
||||||
const result = service.isEnabledOnRestApi();
|
const result = service.isEnabledOnRestApi();
|
||||||
const expected = cold('b-c-', { b: undefined, c: false });
|
const expected = cold('b-c-', { b: undefined, c: false });
|
||||||
expect(result).toBeObservable(expected);
|
expect(result).toBeObservable(expected);
|
||||||
|
@@ -1,39 +1,62 @@
|
|||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { distinctUntilChanged, map, flatMap, startWith } from 'rxjs/operators';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||||
import { EndpointMap, RootSuccessResponse } from '../cache/response-cache.models';
|
import { EndpointMap, EndpointMapSuccessResponse } from '../cache/response-cache.models';
|
||||||
import { RootEndpointRequest } from '../data/request.models';
|
import { EndpointMapRequest, RootEndpointRequest } from '../data/request.models';
|
||||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||||
|
|
||||||
export abstract class HALEndpointService {
|
export abstract class HALEndpointService {
|
||||||
protected abstract responseCache: ResponseCacheService;
|
protected abstract responseCache: ResponseCacheService;
|
||||||
protected abstract requestService: RequestService;
|
protected abstract requestService: RequestService;
|
||||||
protected abstract linkName: string;
|
protected abstract linkPath: string;
|
||||||
protected abstract EnvConfig: GlobalConfig;
|
protected abstract EnvConfig: GlobalConfig;
|
||||||
|
|
||||||
protected getEndpointMap(): Observable<EndpointMap> {
|
protected getRootHref(): string {
|
||||||
const request = new RootEndpointRequest(this.requestService.generateRequestId(), this.EnvConfig);
|
return new RESTURLCombiner(this.EnvConfig, '/').toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getRootEndpointMap(): Observable<EndpointMap> {
|
||||||
|
return this.getEndpointMapAt(this.getRootHref());
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEndpointMapAt(href): Observable<EndpointMap> {
|
||||||
|
const request = new EndpointMapRequest(this.requestService.generateRequestId(), href);
|
||||||
this.requestService.configure(request);
|
this.requestService.configure(request);
|
||||||
return this.responseCache.get(request.href)
|
return this.responseCache.get(request.href)
|
||||||
.map((entry: ResponseCacheEntry) => entry.response)
|
.map((entry: ResponseCacheEntry) => entry.response)
|
||||||
.filter((response: RootSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap))
|
.filter((response: EndpointMapSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap))
|
||||||
.map((response: RootSuccessResponse) => response.endpointMap)
|
.map((response: EndpointMapSuccessResponse) => response.endpointMap)
|
||||||
.distinctUntilChanged();
|
.distinctUntilChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEndpoint(): Observable<string> {
|
public getEndpoint(): Observable<string> {
|
||||||
return this.getEndpointMap()
|
return this.getEndpointAt(...this.linkPath.split('/'));
|
||||||
.map((map: EndpointMap) => map[this.linkName])
|
}
|
||||||
.distinctUntilChanged();
|
|
||||||
|
private getEndpointAt(...path: string[]): Observable<string> {
|
||||||
|
if (isEmpty(path)) {
|
||||||
|
path = ['/'];
|
||||||
|
}
|
||||||
|
const pipeArguments = path
|
||||||
|
.map((subPath: string) => [
|
||||||
|
flatMap((href: string) => this.getEndpointMapAt(href)),
|
||||||
|
map((endpointMap: EndpointMap) => endpointMap[subPath]),
|
||||||
|
])
|
||||||
|
.reduce((combined, thisElement) => [...combined, ...thisElement], []);
|
||||||
|
return Observable.of(this.getRootHref()).pipe(...pipeArguments, distinctUntilChanged());
|
||||||
}
|
}
|
||||||
|
|
||||||
public isEnabledOnRestApi(): Observable<boolean> {
|
public isEnabledOnRestApi(): Observable<boolean> {
|
||||||
return this.getEndpointMap()
|
return this.getRootEndpointMap().pipe(
|
||||||
.map((map: EndpointMap) => isNotEmpty(map[this.linkName]))
|
// TODO this only works when there's no / in linkPath
|
||||||
.startWith(undefined)
|
map((endpointMap: EndpointMap) => isNotEmpty(endpointMap[this.linkPath])),
|
||||||
.distinctUntilChanged();
|
startWith(undefined),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user