Merge branch 'master' into resolvers-branch-angular6

Conflicts:
	package.json
	src/app/+search-page/search-filters/search-filters.component.ts
	src/app/core/auth/auth.effects.ts
	src/app/core/auth/auth.service.ts
	src/app/core/auth/server-auth.service.ts
	src/app/core/data/comcol-data.service.ts
	src/app/core/data/community-data.service.ts
	src/app/core/data/data.service.ts
	src/app/core/data/item-data.service.ts
	src/app/core/shared/dspace-object.model.ts
	src/app/header/header.component.spec.ts
	src/app/shared/auth-nav-menu/auth-nav-menu.component.ts
	src/app/shared/testing/auth-service-stub.ts
	yarn.lock
This commit is contained in:
lotte
2018-10-08 12:28:08 +02:00
78 changed files with 2011 additions and 265 deletions

View File

@@ -7,6 +7,8 @@ import { GlobalConfig } from '../../../config/global-config.interface';
import { GenericConstructor } from '../shared/generic-constructor';
import { PaginatedList } from './paginated-list';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { ResourceType } from '../shared/resource-type';
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
function isObjectLevel(halObj: any) {
return isNotEmpty(halObj._links) && hasValue(halObj._links.self);
@@ -34,6 +36,7 @@ export abstract class BaseResponseParsingService {
} else if (Array.isArray(data)) {
return this.processArray(data, requestHref);
} else if (isObjectLevel(data)) {
data = this.fixBadEPersonRestResponse(data);
const object = this.deserialize(data);
if (isNotEmpty(data._embedded)) {
Object
@@ -53,6 +56,7 @@ export abstract class BaseResponseParsingService {
}
});
}
this.cache(object, requestHref);
return object;
}
@@ -145,4 +149,23 @@ export abstract class BaseResponseParsingService {
}
return obj[keys[0]];
}
// TODO Remove when https://jira.duraspace.org/browse/DS-4006 is fixed
// See https://github.com/DSpace/dspace-angular/issues/292
private fixBadEPersonRestResponse(obj: any): any {
if (obj.type === ResourceType.EPerson) {
const groups = obj.groups;
const normGroups = [];
if (isNotEmpty(groups)) {
groups.forEach((group) => {
const parts = ['eperson', 'groups', group.uuid];
const href = new RESTURLCombiner(this.EnvConfig, ...parts).toString();
normGroups.push(href);
}
)
}
return Object.assign({}, obj, { groups: normGroups });
}
return obj;
}
}

View File

@@ -0,0 +1,168 @@
import { getMockObjectCacheService } from '../../shared/mocks/mock-object-cache.service';
import { ErrorResponse, GenericSuccessResponse } from '../cache/response-cache.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { BrowseEntriesResponseParsingService } from './browse-entries-response-parsing.service';
import { BrowseEntriesRequest, BrowseItemsRequest } from './request.models';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
describe('BrowseItemsResponseParsingService', () => {
let service: BrowseItemsResponseParsingService;
beforeEach(() => {
service = new BrowseItemsResponseParsingService(undefined, getMockObjectCacheService());
});
describe('parse', () => {
const request = new BrowseItemsRequest('client/f5b4ccb8-fbb0-4548-b558-f234d9fdfad6', 'https://rest.api/discover/browses/author/items');
const validResponse = {
payload: {
_embedded: {
items: [
{
id: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
uuid: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
name: 'Development of Local Supply Chain : A Critical Link for Concentrated Solar Power in India',
handle: '10986/17472',
metadata: [
{
key: 'dc.creator',
value: 'World Bank',
language: null
}
],
inArchive: true,
discoverable: true,
withdrawn: false,
lastModified: '2018-05-25T09:32:58.005+0000',
type: 'item',
_links: {
bitstreams: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/bitstreams'
},
owningCollection: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/owningCollection'
},
templateItemOf: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/templateItemOf'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7'
}
}
},
{
id: '27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b',
uuid: '27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b',
name: 'Development of Local Supply Chain : The Missing Link for Concentrated Solar Power Projects in India',
handle: '10986/17475',
metadata: [
{
key: 'dc.creator',
value: 'World Bank',
language: null
}
],
inArchive: true,
discoverable: true,
withdrawn: false,
lastModified: '2018-05-25T09:33:42.526+0000',
type: 'item',
_links: {
bitstreams: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b/bitstreams'
},
owningCollection: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b/owningCollection'
},
templateItemOf: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b/templateItemOf'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/27c6f976-257c-4ad0-a0ef-c5e34ffe4d5b'
}
}
}
]
},
_links: {
first: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items?page=0&size=2'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items'
},
next: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items?page=1&size=2'
},
last: {
href: 'https://dspace7-internal.atmire.com/rest/api/discover/browses/author/items?page=7&size=2'
}
},
page: {
size: 2,
totalElements: 16,
totalPages: 8,
number: 0
}
},
statusCode: '200'
} as DSpaceRESTV2Response;
const invalidResponseNotAList = {
payload: {
id: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
uuid: 'd7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7',
name: 'Development of Local Supply Chain : A Critical Link for Concentrated Solar Power in India',
handle: '10986/17472',
metadata: [
{
key: 'dc.creator',
value: 'World Bank',
language: null
}
],
inArchive: true,
discoverable: true,
withdrawn: false,
lastModified: '2018-05-25T09:32:58.005+0000',
type: 'item',
_links: {
bitstreams: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/bitstreams'
},
owningCollection: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/owningCollection'
},
templateItemOf: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7/templateItemOf'
},
self: {
href: 'https://dspace7-internal.atmire.com/rest/api/core/items/d7b6bc6f-ff6c-444a-a0d3-0cd9b68043e7'
}
}
},
statusCode: '200'
} as DSpaceRESTV2Response;
const invalidResponseStatusCode = {
payload: {}, statusCode: '500'
} as DSpaceRESTV2Response;
it('should return a GenericSuccessResponse if data contains a valid browse items response', () => {
const response = service.parse(request, validResponse);
expect(response.constructor).toBe(GenericSuccessResponse);
});
it('should return an ErrorResponse if data contains an invalid browse entries response', () => {
const response = service.parse(request, invalidResponseNotAList);
expect(response.constructor).toBe(ErrorResponse);
});
it('should return an ErrorResponse if data contains a statuscode other than 200', () => {
const response = service.parse(request, invalidResponseStatusCode);
expect(response.constructor).toBe(ErrorResponse);
});
});
});

View File

@@ -0,0 +1,58 @@
import { Inject, Injectable } from '@angular/core';
import { GLOBAL_CONFIG } from '../../../config';
import { GlobalConfig } from '../../../config/global-config.interface';
import { isNotEmpty } from '../../shared/empty.util';
import { ObjectCacheService } from '../cache/object-cache.service';
import {
ErrorResponse,
GenericSuccessResponse,
RestResponse
} from '../cache/response-cache.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
import { BaseResponseParsingService } from './base-response-parsing.service';
import { ResponseParsingService } from './parsing.service';
import { RestRequest } from './request.models';
import { Item } from '../shared/item.model';
import { DSpaceObject } from '../shared/dspace-object.model';
/**
* A ResponseParsingService used to parse DSpaceRESTV2Response coming from the REST API to Browse Items (DSpaceObject[])
*/
@Injectable()
export class BrowseItemsResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected objectFactory = {
getConstructor: () => DSpaceObject
};
protected toCache = false;
constructor(
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected objectCache: ObjectCacheService,
) { super();
}
/**
* Parses data from the browse endpoint to a list of DSpaceObjects
* @param {RestRequest} request
* @param {DSpaceRESTV2Response} data
* @returns {RestResponse}
*/
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._embedded)
&& Array.isArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]])) {
const serializer = new DSpaceRESTv2Serializer(DSpaceObject);
const items = serializer.deserializeArray(data.payload._embedded[Object.keys(data.payload._embedded)[0]]);
return new GenericSuccessResponse(items, data.statusCode, this.processPageInfo(data.payload));
} else {
return new ErrorResponse(
Object.assign(
new Error('Unexpected response from browse endpoint'),
{ statusText: data.statusCode }
)
);
}
}
}

View File

@@ -9,7 +9,7 @@ import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { ComColDataService } from './comcol-data.service';
import { CommunityDataService } from './community-data.service';
import { FindByIDRequest } from './request.models';
import { FindAllOptions, FindByIDRequest } from './request.models';
import { RequestService } from './request.service';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
@@ -52,6 +52,10 @@ describe('ComColDataService', () => {
const EnvConfig = {} as GlobalConfig;
const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d';
const options = Object.assign(new FindAllOptions(), {
scopeID: scopeID
});
const communitiesEndpoint = 'https://rest.api/core/communities';
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`;
@@ -98,7 +102,7 @@ describe('ComColDataService', () => {
);
}
describe('getScopedEndpoint', () => {
describe('getBrowseEndpoint', () => {
beforeEach(() => {
scheduler = getTestScheduler();
});
@@ -112,7 +116,7 @@ describe('ComColDataService', () => {
const expected = new FindByIDRequest(requestService.generateRequestId(), communityEndpoint, scopeID);
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
scheduler.flush();
expect(requestService.configure).toHaveBeenCalledWith(expected);
@@ -128,13 +132,13 @@ describe('ComColDataService', () => {
});
it('should fetch the scope Community from the cache', () => {
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
scheduler.flush();
expect(objectCache.getByUUID).toHaveBeenCalledWith(scopeID);
});
it('should return the endpoint to fetch resources within the given scope', () => {
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--e-', { e: scopedEndpoint });
expect(result).toBeObservable(expected);
@@ -151,7 +155,7 @@ describe('ComColDataService', () => {
});
it('should throw an error', () => {
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--#-', undefined, new Error(`The Community with scope ${scopeID} couldn't be retrieved`));
expect(result).toBeObservable(expected);

View File

@@ -7,7 +7,7 @@ import { ResponseCacheEntry } from '../cache/response-cache.reducer';
import { CommunityDataService } from './community-data.service';
import { DataService } from './data.service';
import { FindByIDRequest } from './request.models';
import { FindAllOptions, FindByIDRequest } from './request.models';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DSOSuccessResponse } from '../cache/response-cache.models';
@@ -27,17 +27,18 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
* @return { Observable<string> }
* an Observable<string> containing the scoped URL
*/
public getScopedEndpoint(scopeID: string): Observable<string> {
if (isEmpty(scopeID)) {
public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
if (isEmpty(options.scopeID)) {
return this.halService.getEndpoint(this.linkPath);
} else {
const scopeCommunityHrefObs = this.cds.getEndpoint().pipe(
mergeMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, scopeID)),
first((href: string) => isNotEmpty(href)),
tap((href: string) => {
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, scopeID);
const scopeCommunityHrefObs = this.cds.getEndpoint()
.flatMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, options.scopeID))
.filter((href: string) => isNotEmpty(href))
.take(1)
.do((href: string) => {
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, options.scopeID);
this.requestService.configure(request);
}),);
});
// return scopeCommunityHrefObs.pipe(
// mergeMap((href: string) => this.responseCache.get(href)),
@@ -61,16 +62,15 @@ export abstract class ComColDataService<TNormalized extends NormalizedObject, TD
map((entry: ResponseCacheEntry) => entry.response));
const errorResponses = responses.pipe(
filter((response) => !response.isSuccessful),
mergeMap(() => observableThrowError(new Error(`The Community with scope ${scopeID} couldn't be retrieved`)))
mergeMap(() => observableThrowError(new Error(`The Community with scope ${options.scopeID} couldn't be retrieved`)))
);
const successResponses = responses.pipe(
filter((response) => response.isSuccessful),
mergeMap(() => this.objectCache.getByUUID(scopeID)),
mergeMap(() => this.objectCache.getByUUID(options.scopeID)),
map((nc: NormalizedCommunity) => nc._links[this.linkPath]),
filter((href) => isNotEmpty(href))
);
return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged());
}
}

View File

@@ -10,7 +10,7 @@ import { Observable } from 'rxjs';
import { FindAllOptions } from './request.models';
import { SortOptions, SortDirection } from '../cache/models/sort-options.model';
const LINK_NAME = 'test'
const endpoint = 'https://rest.api/core';
// tslint:disable:max-classes-per-file
class NormalizedTestObject extends NormalizedObject {
@@ -28,10 +28,9 @@ class TestService extends DataService<NormalizedTestObject, any> {
super();
}
public getScopedEndpoint(scope: string): Observable<string> {
throw new Error('getScopedEndpoint is abstract in DataService');
public getBrowseEndpoint(options: FindAllOptions): Observable<string> {
return Observable.of(endpoint);
}
}
describe('DataService', () => {
@@ -42,7 +41,6 @@ describe('DataService', () => {
const halService = {} as HALEndpointService;
const rdbService = {} as RemoteDataBuildService;
const store = {} as Store<CoreState>;
const endpoint = 'https://rest.api/core';
function initTestService(): TestService {
return new TestService(
@@ -50,7 +48,7 @@ describe('DataService', () => {
requestService,
rdbService,
store,
LINK_NAME,
endpoint,
halService
);
}
@@ -62,25 +60,17 @@ describe('DataService', () => {
it('should return an observable with the endpoint', () => {
options = {};
(service as any).getFindAllHref(endpoint).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(endpoint);
}
);
});
// getScopedEndpoint is not implemented in abstract DataService
it('should throw error if scopeID provided in options', () => {
options = { scopeID: 'somevalue' };
expect(() => { (service as any).getFindAllHref(endpoint, options) })
.toThrowError('getScopedEndpoint is abstract in DataService');
});
it('should include page in href if currentPage provided in options', () => {
options = { currentPage: 2 };
const expected = `${endpoint}?page=${options.currentPage - 1}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -89,7 +79,7 @@ describe('DataService', () => {
options = { elementsPerPage: 5 };
const expected = `${endpoint}?size=${options.elementsPerPage}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -99,7 +89,7 @@ describe('DataService', () => {
options = { sort: sortOptions};
const expected = `${endpoint}?sort=${sortOptions.field},${sortOptions.direction}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -108,7 +98,7 @@ describe('DataService', () => {
options = { startsWith: 'ab' };
const expected = `${endpoint}?startsWith=${options.startsWith}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
});
@@ -124,7 +114,7 @@ describe('DataService', () => {
const expected = `${endpoint}?page=${options.currentPage - 1}&size=${options.elementsPerPage}` +
`&sort=${sortOptions.field},${sortOptions.direction}&startsWith=${options.startsWith}`;
(service as any).getFindAllHref(endpoint, options).subscribe((value) => {
(service as any).getFindAllHref(options).subscribe((value) => {
expect(value).toBe(expected);
});
})

View File

@@ -1,7 +1,5 @@
import { filter, take, first } from 'rxjs/operators';
import {of as observableOf, Observable } from 'rxjs';
import {mergeMap, first, take, distinctUntilChanged, map, filter} from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -14,6 +12,8 @@ import { RemoteData } from './remote-data';
import { FindAllOptions, FindAllRequest, FindByIDRequest, GetRequest } from './request.models';
import { RequestService } from './request.service';
import { NormalizedObject } from '../cache/models/normalized-object.model';
import { promise } from 'selenium-webdriver';
import map = promise.map;
export abstract class DataService<TNormalized extends NormalizedObject, TDomain> {
protected abstract responseCache: ResponseCacheService;
@@ -23,17 +23,13 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
protected abstract linkPath: string;
protected abstract halService: HALEndpointService;
public abstract getScopedEndpoint(scope: string): Observable<string>
public abstract getBrowseEndpoint(options: FindAllOptions): Observable<string>
protected getFindAllHref(endpoint, options: FindAllOptions = {}): Observable<string> {
protected getFindAllHref(options: FindAllOptions = {}): Observable<string> {
let result: Observable<string>;
const args = [];
if (hasValue(options.scopeID)) {
result = this.getScopedEndpoint(options.scopeID).pipe(distinctUntilChanged());
} else {
result = observableOf(endpoint);
}
result = this.getBrowseEndpoint(options).distinctUntilChanged();
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
@@ -60,12 +56,11 @@ export abstract class DataService<TNormalized extends NormalizedObject, TDomain>
}
findAll(options: FindAllOptions = {}): Observable<RemoteData<PaginatedList<TDomain>>> {
const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(filter((href: string) => isNotEmpty(href)),
mergeMap((endpoint: string) => this.getFindAllHref(endpoint, options)),);
const hrefObs = this.getFindAllHref(options);
hrefObs.pipe(
filter((href: string) => hasValue(href)),
take(1),)
take(1))
.subscribe((href: string) => {
const request = new FindAllRequest(this.requestService.generateRequestId(), href, options);
this.requestService.configure(request);

View File

@@ -10,6 +10,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
import { DataService } from './data.service';
import { RemoteData } from './remote-data';
import { RequestService } from './request.service';
import { FindAllOptions } from './request.models';
/* tslint:disable:max-classes-per-file */
class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject> {
@@ -24,8 +25,8 @@ class DataServiceImpl extends DataService<NormalizedDSpaceObject, DSpaceObject>
super();
}
getScopedEndpoint(scope: string): Observable<string> {
return undefined;
getBrowseEndpoint(options: FindAllOptions): Observable<string> {
return this.halService.getEndpoint(this.linkPath);
}
getFindByIDHref(endpoint, resourceID): string {

View File

@@ -8,6 +8,7 @@ import { CoreState } from '../core.reducers';
import { ItemDataService } from './item-data.service';
import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { FindAllOptions } from './request.models';
describe('ItemDataService', () => {
let scheduler: TestScheduler;
@@ -20,6 +21,14 @@ describe('ItemDataService', () => {
const halEndpointService = {} as HALEndpointService;
const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
const options = Object.assign(new FindAllOptions(), {
scopeID: scopeID,
sort: {
field: '',
direction: undefined
}
});
const browsesEndpoint = 'https://rest.api/discover/browses';
const itemBrowseEndpoint = `${browsesEndpoint}/author/items`;
const scopedEndpoint = `${itemBrowseEndpoint}?scope=${scopeID}`;
@@ -46,16 +55,16 @@ describe('ItemDataService', () => {
);
}
describe('getScopedEndpoint', () => {
describe('getBrowseEndpoint', () => {
beforeEach(() => {
scheduler = getTestScheduler();
});
it('should return the endpoint to fetch Items within the given scope', () => {
it('should return the endpoint to fetch Items within the given scope and starting with the given string', () => {
bs = initMockBrowseService(true);
service = initTestService();
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--b-', { b: scopedEndpoint });
expect(result).toBeObservable(expected);
@@ -67,7 +76,7 @@ describe('ItemDataService', () => {
service = initTestService();
});
it('should throw an error', () => {
const result = service.getScopedEndpoint(scopeID);
const result = service.getBrowseEndpoint(options);
const expected = cold('--#-', undefined, browseError);
expect(result).toBeObservable(expected);

View File

@@ -1,11 +1,7 @@
import {distinctUntilChanged, map, filter} from 'rxjs/operators';
import { Inject, Injectable } from '@angular/core';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
import { isNotEmpty } from '../../shared/empty.util';
import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedItem } from '../cache/models/normalized-item.model';
@@ -17,6 +13,7 @@ import { URLCombiner } from '../url-combiner/url-combiner';
import { DataService } from './data.service';
import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { FindAllOptions } from './request.models';
@Injectable()
export class ItemDataService extends DataService<NormalizedItem, Item> {
@@ -32,15 +29,21 @@ export class ItemDataService extends DataService<NormalizedItem, Item> {
super();
}
public getScopedEndpoint(scopeID: string): Observable<string> {
if (isEmpty(scopeID)) {
return this.halService.getEndpoint(this.linkPath);
} else {
return this.bs.getBrowseURLFor('dc.date.issued', this.linkPath).pipe(
filter((href: string) => isNotEmpty(href)),
map((href: string) => new URLCombiner(href, `?scope=${scopeID}`).toString()),
distinctUntilChanged(),);
/**
* Get the endpoint for browsing items
* (When options.sort.field is empty, the default field to browse by will be 'dc.date.issued')
* @param {FindAllOptions} options
* @returns {Observable<string>}
*/
public getBrowseEndpoint(options: FindAllOptions = {}): Observable<string> {
let field = 'dc.date.issued';
if (options.sort && options.sort.field) {
field = options.sort.field;
}
return this.bs.getBrowseURLFor(field, this.linkPath)
.filter((href: string) => isNotEmpty(href))
.map((href: string) => new URLCombiner(href, `?scope=${options.scopeID}`).toString())
.distinctUntilChanged();
}
}

View File

@@ -12,6 +12,7 @@ import { AuthResponseParsingService } from '../auth/auth-response-parsing.servic
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { HttpHeaders } from '@angular/common/http';
import { IntegrationResponseParsingService } from '../integration/integration-response-parsing.service';
import { BrowseItemsResponseParsingService } from './browse-items-response-parsing-service';
/* tslint:disable:max-classes-per-file */
@@ -184,6 +185,12 @@ export class BrowseEntriesRequest extends GetRequest {
}
}
export class BrowseItemsRequest extends GetRequest {
getResponseParser(): GenericConstructor<ResponseParsingService> {
return BrowseItemsResponseParsingService;
}
}
export class ConfigRequest extends GetRequest {
constructor(uuid: string, href: string) {
super(uuid, href);