mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-10 19:43:04 +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();
|
||||
});
|
||||
|
||||
it('should return the value of the searchLink variable in the filter service', () => {
|
||||
expect(link).toEqual(filterService.searchLink);
|
||||
it('should return the value of the uiSearchRoute variable in the filter service', () => {
|
||||
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;
|
||||
beforeEach(() => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
@@ -46,7 +46,7 @@ export class SearchFilterService {
|
||||
}
|
||||
|
||||
get searchLink() {
|
||||
return this.searchService.searchLink;
|
||||
return this.searchService.uiSearchRoute;
|
||||
}
|
||||
|
||||
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 { Observable } from 'rxjs/Observable';
|
||||
import { map } from 'rxjs/operators';
|
||||
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 { 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 { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { ResponseParsingService } from '../../core/data/parsing.service';
|
||||
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 { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { Metadatum } from '../../core/shared/metadatum.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 { FilterType } from './filter-type.model';
|
||||
import { SearchFilterConfig } from './search-filter-config.model';
|
||||
import { SearchResponseParsingService } from '../../core/data/search-response-parsing.service';
|
||||
|
||||
function shuffle(array: any[]) {
|
||||
let i = 0;
|
||||
@@ -35,23 +49,11 @@ function shuffle(array: any[]) {
|
||||
}
|
||||
|
||||
@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;
|
||||
searchLink = '/search';
|
||||
uiSearchRoute = '/search';
|
||||
|
||||
config: SearchFilterConfig[] = [
|
||||
Object.assign(new SearchFilterConfig(),
|
||||
@@ -86,11 +88,16 @@ export class SearchService implements OnDestroy {
|
||||
// searchOptions: BehaviorSubject<SearchOptions>;
|
||||
searchOptions: SearchOptions;
|
||||
|
||||
constructor(private itemDataService: ItemDataService,
|
||||
private routeService: RouteService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router) {
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
private itemDataService: ItemDataService,
|
||||
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||
private routeService: RouteService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router
|
||||
) {
|
||||
super();
|
||||
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
||||
pagination.id = 'search-results-pagination';
|
||||
pagination.currentPage = 1;
|
||||
@@ -101,74 +108,18 @@ export class SearchService implements OnDestroy {
|
||||
}
|
||||
|
||||
search(query: string, scopeId?: string, searchOptions?: SearchOptions): Observable<RemoteData<Array<SearchResult<DSpaceObject>>>> {
|
||||
this.searchOptions = searchOptions;
|
||||
let self = `https://dspace7.4science.it/dspace-spring-rest/api/search?query=${query}`;
|
||||
if (hasValue(scopeId)) {
|
||||
self += `&scope=${scopeId}`;
|
||||
}
|
||||
if (isNotEmpty(searchOptions) && hasValue(searchOptions.pagination.currentPage)) {
|
||||
self += `&page=${searchOptions.pagination.currentPage}`;
|
||||
}
|
||||
if (isNotEmpty(searchOptions) && hasValue(searchOptions.pagination.pageSize)) {
|
||||
self += `&pageSize=${searchOptions.pagination.pageSize}`;
|
||||
}
|
||||
if (isNotEmpty(searchOptions) && hasValue(searchOptions.sort.direction)) {
|
||||
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
|
||||
));
|
||||
const searchEndpointUrlObs = this.getEndpoint();
|
||||
searchEndpointUrlObs.pipe(
|
||||
map((url: string) => {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), url);
|
||||
return Object.assign(request, {
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return SearchResponseParsingService;
|
||||
}
|
||||
});
|
||||
})
|
||||
).subscribe((request: RestRequest) => this.requestService.configure(request));
|
||||
return Observable.of(undefined);
|
||||
}
|
||||
|
||||
getConfig(): Observable<RemoteData<SearchFilterConfig[]>> {
|
||||
@@ -231,7 +182,7 @@ export class SearchService implements OnDestroy {
|
||||
queryParamsHandling: 'merge'
|
||||
};
|
||||
|
||||
this.router.navigate([this.searchLink], navigationExtras);
|
||||
this.router.navigate([this.uiSearchRoute], navigationExtras);
|
||||
}
|
||||
|
||||
getClearFiltersQueryParams(): any {
|
||||
@@ -249,7 +200,7 @@ export class SearchService implements OnDestroy {
|
||||
}
|
||||
|
||||
getSearchLink() {
|
||||
return this.searchLink;
|
||||
return this.uiSearchRoute;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@@ -110,12 +110,12 @@ describe('BrowseService', () => {
|
||||
.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 linkName = 'items';
|
||||
const expectedURL = browseDefinitions[0]._links[linkName];
|
||||
const linkPath = 'items';
|
||||
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 });
|
||||
|
||||
expect(result).toBeObservable(expected);
|
||||
@@ -123,10 +123,10 @@ describe('BrowseService', () => {
|
||||
|
||||
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 linkName = 'items';
|
||||
const expectedURL = browseDefinitions[1]._links[linkName];
|
||||
const linkPath = 'items';
|
||||
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 });
|
||||
|
||||
expect(result).toBeObservable(expected);
|
||||
@@ -134,30 +134,30 @@ describe('BrowseService', () => {
|
||||
|
||||
it('should throw an error when the key doesn\'t match', () => {
|
||||
const metadatumKey = 'dc.title'; // isn't in the definitions
|
||||
const linkName = 'items';
|
||||
const linkPath = 'items';
|
||||
|
||||
const result = service.getBrowseURLFor(metadatumKey, linkName);
|
||||
const expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkName} on ${metadatumKey} isn't configured`));
|
||||
const result = service.getBrowseURLFor(metadatumKey, linkPath);
|
||||
const expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkPath} on ${metadatumKey} isn't configured`));
|
||||
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should throw an error when the link doesn\'t match', () => {
|
||||
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 expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkName} on ${metadatumKey} isn't configured`));
|
||||
const result = service.getBrowseURLFor(metadatumKey, linkPath);
|
||||
const expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkPath} on ${metadatumKey} isn't configured`));
|
||||
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should configure a new BrowseEndpointRequest', () => {
|
||||
const metadatumKey = 'dc.date.issued';
|
||||
const linkName = 'items';
|
||||
const linkPath = 'items';
|
||||
const expected = new BrowseEndpointRequest(requestService.generateRequestId(), browsesEndpointURL);
|
||||
|
||||
scheduler.schedule(() => service.getBrowseURLFor(metadatumKey, linkName).subscribe());
|
||||
scheduler.schedule(() => service.getBrowseURLFor(metadatumKey, linkPath).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
@@ -175,9 +175,9 @@ describe('BrowseService', () => {
|
||||
.returnValue(hot('----'));
|
||||
|
||||
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 });
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
@@ -192,9 +192,9 @@ describe('BrowseService', () => {
|
||||
.returnValue(hot('--a-', { a: browsesEndpointURL }));
|
||||
|
||||
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`));
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
@@ -13,7 +13,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
|
||||
@Injectable()
|
||||
export class BrowseService extends HALEndpointService {
|
||||
protected linkName = 'browses';
|
||||
protected linkPath = 'browses';
|
||||
|
||||
private static toSearchKeyArray(metadatumKey: string): string[] {
|
||||
const keyParts = metadatumKey.split('.');
|
||||
@@ -35,7 +35,7 @@ export class BrowseService extends HALEndpointService {
|
||||
super();
|
||||
}
|
||||
|
||||
getBrowseURLFor(metadatumKey: string, linkName: string): Observable<string> {
|
||||
getBrowseURLFor(metadatumKey: string, linkPath: string): Observable<string> {
|
||||
const searchKeyArray = BrowseService.toSearchKeyArray(metadatumKey);
|
||||
return this.getEndpoint()
|
||||
.filter((href: string) => isNotEmpty(href))
|
||||
@@ -59,10 +59,10 @@ export class BrowseService extends HALEndpointService {
|
||||
return isNotEmpty(matchingKeys);
|
||||
})
|
||||
).map((def: BrowseDefinition) => {
|
||||
if (isEmpty(def) || isEmpty(def._links) || isEmpty(def._links[linkName])) {
|
||||
throw new Error(`A browse endpoint for ${linkName} on ${metadatumKey} isn't configured`);
|
||||
if (isEmpty(def) || isEmpty(def._links) || isEmpty(def._links[linkPath])) {
|
||||
throw new Error(`A browse endpoint for ${linkPath} on ${metadatumKey} isn't configured`);
|
||||
} 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 { PageInfo } from '../shared/page-info.model';
|
||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||
@@ -21,11 +22,21 @@ export class DSOSuccessResponse extends RestResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export class EndpointMap {
|
||||
[linkName: string]: string
|
||||
export class SearchSuccessResponse extends RestResponse {
|
||||
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(
|
||||
public endpointMap: EndpointMap,
|
||||
public statusCode: string,
|
||||
|
@@ -11,7 +11,7 @@ const LINK_NAME = 'test';
|
||||
const BROWSE = 'search/findByCollection';
|
||||
|
||||
class TestService extends ConfigService {
|
||||
protected linkName = LINK_NAME;
|
||||
protected linkPath = LINK_NAME;
|
||||
protected browseEndpoint = BROWSE;
|
||||
|
||||
constructor(
|
||||
|
@@ -16,7 +16,7 @@ export abstract class ConfigService extends HALEndpointService {
|
||||
protected request: ConfigRequest;
|
||||
protected abstract responseCache: ResponseCacheService;
|
||||
protected abstract requestService: RequestService;
|
||||
protected abstract linkName: string;
|
||||
protected abstract linkPath: string;
|
||||
protected abstract EnvConfig: GlobalConfig;
|
||||
protected abstract browseEndpoint: string;
|
||||
|
||||
|
@@ -8,7 +8,7 @@ import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
|
||||
@Injectable()
|
||||
export class SubmissionDefinitionsConfigService extends ConfigService {
|
||||
protected linkName = 'submissiondefinitions';
|
||||
protected linkPath = 'submissiondefinitions';
|
||||
protected browseEndpoint = 'search/findByCollection';
|
||||
|
||||
constructor(
|
||||
|
@@ -8,7 +8,7 @@ import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
|
||||
@Injectable()
|
||||
export class SubmissionFormsConfigService extends ConfigService {
|
||||
protected linkName = 'submissionforms';
|
||||
protected linkPath = 'submissionforms';
|
||||
protected browseEndpoint = '';
|
||||
|
||||
constructor(
|
||||
|
@@ -8,7 +8,7 @@ import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
|
||||
@Injectable()
|
||||
export class SubmissionSectionsConfigService extends ConfigService {
|
||||
protected linkName = 'submissionsections';
|
||||
protected linkPath = 'submissionsections';
|
||||
protected browseEndpoint = '';
|
||||
|
||||
constructor(
|
||||
|
@@ -17,7 +17,9 @@ import { isNotEmpty } from '../shared/empty.util';
|
||||
import { ApiService } from '../shared/api.service';
|
||||
import { CollectionDataService } from './data/collection-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 { SearchResponseParsingService } from './data/search-response-parsing.service';
|
||||
import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service';
|
||||
import { HostWindowService } from '../shared/host-window.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 { RequestService } from './data/request.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 { NativeWindowFactory, NativeWindowService } from '../shared/window.service';
|
||||
import { BrowseService } from './browse/browse.service';
|
||||
@@ -67,7 +69,9 @@ const PROVIDERS = [
|
||||
RemoteDataBuildService,
|
||||
RequestService,
|
||||
ResponseCacheService,
|
||||
RootResponseParsingService,
|
||||
EndpointMapResponseParsingService,
|
||||
DebugResponseParsingService,
|
||||
SearchResponseParsingService,
|
||||
ServerResponseService,
|
||||
BrowseResponseParsingService,
|
||||
BrowseService,
|
||||
|
@@ -13,7 +13,7 @@ import { RequestService } from './request.service';
|
||||
|
||||
@Injectable()
|
||||
export class CollectionDataService extends ComColDataService<NormalizedCollection, Collection> {
|
||||
protected linkName = 'collections';
|
||||
protected linkPath = 'collections';
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
|
@@ -22,7 +22,7 @@ class NormalizedTestObject implements CacheableObject {
|
||||
}
|
||||
|
||||
class TestService extends ComColDataService<NormalizedTestObject, any> {
|
||||
protected linkName = LINK_NAME;
|
||||
protected linkPath = LINK_NAME;
|
||||
|
||||
constructor(
|
||||
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
|
||||
* the given scopeID and returning its HAL link with this
|
||||
* data-service's linkName
|
||||
* data-service's linkPath
|
||||
*
|
||||
* @param {string} scopeID
|
||||
* 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`))),
|
||||
successResponse
|
||||
.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))
|
||||
).distinctUntilChanged();
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ import { RequestService } from './request.service';
|
||||
|
||||
@Injectable()
|
||||
export class CommunityDataService extends ComColDataService<NormalizedCommunity, Community> {
|
||||
protected linkName = 'communities';
|
||||
protected linkPath = 'communities';
|
||||
protected cds = this;
|
||||
|
||||
constructor(
|
||||
|
@@ -19,7 +19,7 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
protected abstract requestService: RequestService;
|
||||
protected abstract rdbService: RemoteDataBuildService;
|
||||
protected abstract store: Store<CoreState>;
|
||||
protected abstract linkName: string;
|
||||
protected abstract linkPath: string;
|
||||
protected abstract EnvConfig: GlobalConfig;
|
||||
|
||||
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 { GLOBAL_CONFIG } from '../../../config';
|
||||
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 { ResponseParsingService } from './parsing.service';
|
||||
import { RestRequest } from './request.models';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
|
||||
@Injectable()
|
||||
export class RootResponseParsingService implements ResponseParsingService {
|
||||
export class EndpointMapResponseParsingService implements ResponseParsingService {
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig,
|
||||
) {
|
||||
@@ -21,7 +20,7 @@ export class RootResponseParsingService implements ResponseParsingService {
|
||||
for (const link of Object.keys(links)) {
|
||||
links[link] = links[link].href;
|
||||
}
|
||||
return new RootSuccessResponse(links, data.statusCode);
|
||||
return new EndpointMapSuccessResponse(links, data.statusCode);
|
||||
} else {
|
||||
return new ErrorResponse(
|
||||
Object.assign(
|
@@ -17,7 +17,7 @@ import { RequestService } from './request.service';
|
||||
|
||||
@Injectable()
|
||||
export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||
protected linkName = 'items';
|
||||
protected linkPath = 'items';
|
||||
|
||||
constructor(
|
||||
protected responseCache: ResponseCacheService,
|
||||
@@ -34,7 +34,7 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
|
||||
if (isEmpty(scopeID)) {
|
||||
return this.getEndpoint();
|
||||
} else {
|
||||
return this.bs.getBrowseURLFor('dc.date.issued', this.linkName)
|
||||
return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath)
|
||||
.filter((href: string) => isNotEmpty(href))
|
||||
.map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString())
|
||||
.distinctUntilChanged();
|
||||
|
@@ -4,7 +4,7 @@ import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||
import { DSOResponseParsingService } from './dso-response-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 { ConfigResponseParsingService } from './config-response-parsing.service';
|
||||
|
||||
@@ -140,14 +140,24 @@ export class FindAllRequest extends GetRequest {
|
||||
}
|
||||
}
|
||||
|
||||
export class RootEndpointRequest extends GetRequest {
|
||||
constructor(uuid: string, EnvConfig: GlobalConfig) {
|
||||
const href = new RESTURLCombiner(EnvConfig, '/').toString();
|
||||
super(uuid, href);
|
||||
export class EndpointMapRequest extends GetRequest {
|
||||
constructor(
|
||||
public uuid: string,
|
||||
public href: string,
|
||||
public body?: any
|
||||
) {
|
||||
super(uuid, href, body);
|
||||
}
|
||||
|
||||
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 */
|
||||
class TestService extends HALEndpointService {
|
||||
protected linkName = 'test';
|
||||
protected linkPath = 'test';
|
||||
|
||||
constructor(protected responseCache: ResponseCacheService,
|
||||
protected requestService: RequestService,
|
||||
@@ -29,7 +29,7 @@ describe('HALEndpointService', () => {
|
||||
|
||||
/* tslint:enable:no-shadowed-variable */
|
||||
|
||||
describe('getEndpointMap', () => {
|
||||
describe('getRootEndpointMap', () => {
|
||||
beforeEach(() => {
|
||||
responseCache = jasmine.createSpyObj('responseCache', {
|
||||
get: hot('--a-', {
|
||||
@@ -53,13 +53,13 @@ describe('HALEndpointService', () => {
|
||||
});
|
||||
|
||||
it('should configure a new RootEndpointRequest', () => {
|
||||
(service as any).getEndpointMap();
|
||||
(service as any).getRootEndpointMap();
|
||||
const expected = new RootEndpointRequest(requestService.generateRequestId(), envConfig);
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
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 });
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
@@ -74,18 +74,18 @@ describe('HALEndpointService', () => {
|
||||
envConfig
|
||||
);
|
||||
|
||||
spyOn(service as any, 'getEndpointMap').and
|
||||
spyOn(service as any, 'getRootEndpointMap').and
|
||||
.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 expected = cold('--b-', { b: endpointMap.test });
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should return undefined for a linkName that isn\'t in the endpoint map', () => {
|
||||
(service as any).linkName = 'unknown';
|
||||
it('should return undefined for a linkPath that isn\'t in the endpoint map', () => {
|
||||
(service as any).linkPath = 'unknown';
|
||||
const result = service.getEndpoint();
|
||||
const expected = cold('--b-', { b: undefined });
|
||||
expect(result).toBeObservable(expected);
|
||||
@@ -103,8 +103,8 @@ describe('HALEndpointService', () => {
|
||||
|
||||
});
|
||||
|
||||
it('should return undefined as long as getEndpointMap hasn\'t fired', () => {
|
||||
spyOn(service as any, 'getEndpointMap').and
|
||||
it('should return undefined as long as getRootEndpointMap hasn\'t fired', () => {
|
||||
spyOn(service as any, 'getRootEndpointMap').and
|
||||
.returnValue(hot('----'));
|
||||
|
||||
const result = service.isEnabledOnRestApi();
|
||||
@@ -112,8 +112,8 @@ describe('HALEndpointService', () => {
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should return true if the service\'s linkName is in the endpoint map', () => {
|
||||
spyOn(service as any, 'getEndpointMap').and
|
||||
it('should return true if the service\'s linkPath is in the endpoint map', () => {
|
||||
spyOn(service as any, 'getRootEndpointMap').and
|
||||
.returnValue(hot('--a-', { a: endpointMap }));
|
||||
|
||||
const result = service.isEnabledOnRestApi();
|
||||
@@ -121,11 +121,11 @@ describe('HALEndpointService', () => {
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should return false if the service\'s linkName isn\'t in the endpoint map', () => {
|
||||
spyOn(service as any, 'getEndpointMap').and
|
||||
it('should return false if the service\'s linkPath isn\'t in the endpoint map', () => {
|
||||
spyOn(service as any, 'getRootEndpointMap').and
|
||||
.returnValue(hot('--a-', { a: endpointMap }));
|
||||
|
||||
(service as any).linkName = 'unknown';
|
||||
(service as any).linkPath = 'unknown';
|
||||
const result = service.isEnabledOnRestApi();
|
||||
const expected = cold('b-c-', { b: undefined, c: false });
|
||||
expect(result).toBeObservable(expected);
|
||||
|
@@ -1,39 +1,62 @@
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { distinctUntilChanged, map, flatMap, startWith } from 'rxjs/operators';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { EndpointMap, RootSuccessResponse } from '../cache/response-cache.models';
|
||||
import { RootEndpointRequest } from '../data/request.models';
|
||||
import { EndpointMap, EndpointMapSuccessResponse } from '../cache/response-cache.models';
|
||||
import { EndpointMapRequest, RootEndpointRequest } from '../data/request.models';
|
||||
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 {
|
||||
protected abstract responseCache: ResponseCacheService;
|
||||
protected abstract requestService: RequestService;
|
||||
protected abstract linkName: string;
|
||||
protected abstract linkPath: string;
|
||||
protected abstract EnvConfig: GlobalConfig;
|
||||
|
||||
protected getEndpointMap(): Observable<EndpointMap> {
|
||||
const request = new RootEndpointRequest(this.requestService.generateRequestId(), this.EnvConfig);
|
||||
protected getRootHref(): string {
|
||||
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);
|
||||
return this.responseCache.get(request.href)
|
||||
.map((entry: ResponseCacheEntry) => entry.response)
|
||||
.filter((response: RootSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap))
|
||||
.map((response: RootSuccessResponse) => response.endpointMap)
|
||||
.filter((response: EndpointMapSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap))
|
||||
.map((response: EndpointMapSuccessResponse) => response.endpointMap)
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
public getEndpoint(): Observable<string> {
|
||||
return this.getEndpointMap()
|
||||
.map((map: EndpointMap) => map[this.linkName])
|
||||
.distinctUntilChanged();
|
||||
return this.getEndpointAt(...this.linkPath.split('/'));
|
||||
}
|
||||
|
||||
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> {
|
||||
return this.getEndpointMap()
|
||||
.map((map: EndpointMap) => isNotEmpty(map[this.linkName]))
|
||||
.startWith(undefined)
|
||||
.distinctUntilChanged();
|
||||
return this.getRootEndpointMap().pipe(
|
||||
// TODO this only works when there's no / in linkPath
|
||||
map((endpointMap: EndpointMap) => isNotEmpty(endpointMap[this.linkPath])),
|
||||
startWith(undefined),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user