+
{{ 'search.sidebar.settings.rpp' | translate}}
diff --git a/src/app/+search-page/search-settings/search-settings.component.spec.ts b/src/app/+search-page/search-settings/search-settings.component.spec.ts
index 504bfbc2bf..2330b62669 100644
--- a/src/app/+search-page/search-settings/search-settings.component.spec.ts
+++ b/src/app/+search-page/search-settings/search-settings.component.spec.ts
@@ -3,7 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchSettingsComponent } from './search-settings.component';
import { Observable } from 'rxjs/Observable';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
-import { SortOptions } from '../../core/cache/models/sort-options.model';
+import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
@@ -22,7 +22,7 @@ describe('SearchSettingsComponent', () => {
pagination.id = 'search-results-pagination';
pagination.currentPage = 1;
pagination.pageSize = 10;
- const sort: SortOptions = new SortOptions();
+ const sort: SortOptions = new SortOptions('score', SortDirection.DESC);
const mockResults = [ 'test', 'data' ];
const searchServiceStub = {
searchOptions: { pagination: pagination, sort: sort },
diff --git a/src/app/+search-page/search-settings/search-settings.component.ts b/src/app/+search-page/search-settings/search-settings.component.ts
index ad6aeb21dd..145b58e27b 100644
--- a/src/app/+search-page/search-settings/search-settings.component.ts
+++ b/src/app/+search-page/search-settings/search-settings.component.ts
@@ -1,17 +1,18 @@
-import { Component, Input, OnInit } from '@angular/core';
+import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { SearchService } from '../search-service/search.service';
import { SearchOptions, ViewMode } from '../search-options.model';
import { SortDirection } from '../../core/cache/models/sort-options.model';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
+import { PaginatedSearchOptions } from '../paginated-search-options.model';
@Component({
selector: 'ds-search-settings',
styleUrls: ['./search-settings.component.scss'],
- templateUrl: './search-settings.component.html',
+ templateUrl: './search-settings.component.html'
})
export class SearchSettingsComponent implements OnInit {
- @Input() searchOptions: SearchOptions;
+ @Input() searchOptions: PaginatedSearchOptions;
/**
* Declare SortDirection enumeration to use it in the template
*/
@@ -21,8 +22,6 @@ export class SearchSettingsComponent implements OnInit {
*/
public pageSize;
@Input() public pageSizeOptions;
- public listPageSizeOptions: number[] = [5, 10, 20, 40, 60, 80, 100];
- public gridPageSizeOptions: number[] = [12, 24, 36, 48 , 50, 62, 74, 84];
private sub;
private scope: string;
@@ -48,11 +47,11 @@ export class SearchSettingsComponent implements OnInit {
this.scope = params.scope;
this.page = +params.page || this.searchOptions.pagination.currentPage;
this.pageSize = +params.pageSize || this.searchOptions.pagination.pageSize;
- this.direction = +params.sortDirection || this.searchOptions.sort.direction;
+ this.direction = params.sortDirection || this.searchOptions.sort.direction;
if (params.view === ViewMode.Grid) {
- this.pageSizeOptions = this.gridPageSizeOptions;
+ this.pageSizeOptions = this.pageSizeOptions;
} else {
- this.pageSizeOptions = this.listPageSizeOptions;
+ this.pageSizeOptions = this.pageSizeOptions;
}
});
}
diff --git a/src/app/core/browse/browse.service.spec.ts b/src/app/core/browse/browse.service.spec.ts
index 764107837b..2e9163fbac 100644
--- a/src/app/core/browse/browse.service.spec.ts
+++ b/src/app/core/browse/browse.service.spec.ts
@@ -3,11 +3,11 @@ import { getMockResponseCacheService } from '../../shared/mocks/mock-response-ca
import { BrowseService } from './browse.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { RequestService } from '../data/request.service';
-import { GlobalConfig } from '../../../config';
import { hot, cold, getTestScheduler } from 'jasmine-marbles';
import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseEndpointRequest } from '../data/request.models';
import { TestScheduler } from 'rxjs/Rx';
+import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
describe('BrowseService', () => {
let scheduler: TestScheduler;
@@ -15,8 +15,8 @@ describe('BrowseService', () => {
let responseCache: ResponseCacheService;
let requestService: RequestService;
- const envConfig = {} as GlobalConfig;
const browsesEndpointURL = 'https://rest.api/browses';
+ const halService: any = new HALEndpointServiceStub(browsesEndpointURL);
const browseDefinitions = [
Object.assign(new BrowseDefinition(), {
metadataBrowse: false,
@@ -91,7 +91,7 @@ describe('BrowseService', () => {
return new BrowseService(
responseCache,
requestService,
- envConfig
+ halService
);
}
@@ -106,16 +106,16 @@ describe('BrowseService', () => {
responseCache = initMockResponseCacheService(true);
requestService = getMockRequestService();
service = initTestService();
- spyOn(service, 'getEndpoint').and
+ spyOn(halService, 'getEndpoint').and
.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);
@@ -171,13 +171,13 @@ describe('BrowseService', () => {
responseCache = initMockResponseCacheService(true);
requestService = getMockRequestService();
service = initTestService();
- spyOn(service, 'getEndpoint').and
+ spyOn(halService, 'getEndpoint').and
.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);
});
@@ -188,13 +188,13 @@ describe('BrowseService', () => {
responseCache = initMockResponseCacheService(false);
requestService = getMockRequestService();
service = initTestService();
- spyOn(service, 'getEndpoint').and
+ spyOn(halService, 'getEndpoint').and
.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);
});
diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts
index a321e14706..2e99dcc0d3 100644
--- a/src/app/core/browse/browse.service.ts
+++ b/src/app/core/browse/browse.service.ts
@@ -12,8 +12,8 @@ import { BrowseDefinition } from '../shared/browse-definition.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
@Injectable()
-export class BrowseService extends HALEndpointService {
- protected linkName = 'browses';
+export class BrowseService {
+ protected linkPath = 'browses';
private static toSearchKeyArray(metadatumKey: string): string[] {
const keyParts = metadatumKey.split('.');
@@ -31,13 +31,12 @@ export class BrowseService extends HALEndpointService {
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
- @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig) {
- super();
+ protected halService: HALEndpointService) {
}
- getBrowseURLFor(metadatumKey: string, linkName: string): Observable
{
+ getBrowseURLFor(metadatumKey: string, linkPath: string): Observable {
const searchKeyArray = BrowseService.toSearchKeyArray(metadatumKey);
- return this.getEndpoint()
+ return this.halService.getEndpoint(this.linkPath)
.filter((href: string) => isNotEmpty(href))
.distinctUntilChanged()
.map((endpointURL: string) => new BrowseEndpointRequest(this.requestService.generateRequestId(), endpointURL))
@@ -59,10 +58,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];
}
})
);
diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts
index 9ed43c242b..ece80cf4ca 100644
--- a/src/app/core/cache/builders/remote-data-build.service.ts
+++ b/src/app/core/cache/builders/remote-data-build.service.ts
@@ -1,43 +1,44 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
-import { hasValue, isNotEmpty } from '../../../shared/empty.util';
+import { map, tap } from 'rxjs/operators';
+import { NormalizedSearchResult } from '../../../+search-page/normalized-search-result.model';
+import { SearchResult } from '../../../+search-page/search-result.model';
+import { SearchQueryResponse } from '../../../+search-page/search-service/search-query-response.model';
+import { hasValue, isEmpty, isNotEmpty } from '../../../shared/empty.util';
import { PaginatedList } from '../../data/paginated-list';
import { RemoteData } from '../../data/remote-data';
import { RemoteDataError } from '../../data/remote-data-error';
-import { GetRequest } from '../../data/request.models';
+import { GetRequest, RestRequest } from '../../data/request.models';
import { RequestEntry } from '../../data/request.reducer';
import { RequestService } from '../../data/request.service';
+import { DSpaceObject } from '../../shared/dspace-object.model';
import { GenericConstructor } from '../../shared/generic-constructor';
+import { NormalizedDSpaceObject } from '../models/normalized-dspace-object.model';
import { NormalizedObjectFactory } from '../models/normalized-object-factory';
import { CacheableObject } from '../object-cache.reducer';
import { ObjectCacheService } from '../object-cache.service';
-import { DSOSuccessResponse, ErrorResponse } from '../response-cache.models';
+import { DSOSuccessResponse, ErrorResponse, SearchSuccessResponse } from '../response-cache.models';
import { ResponseCacheEntry } from '../response-cache.reducer';
import { ResponseCacheService } from '../response-cache.service';
import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators';
+import { NormalizedObject } from '../models/normalized-object.model';
@Injectable()
export class RemoteDataBuildService {
- constructor(
- protected objectCache: ObjectCacheService,
- protected responseCache: ResponseCacheService,
- protected requestService: RequestService
- ) {
+ constructor(protected objectCache: ObjectCacheService,
+ protected responseCache: ResponseCacheService,
+ protected requestService: RequestService) {
}
- buildSingle(
- hrefObs: string | Observable,
- normalizedType: GenericConstructor
- ): Observable> {
+ buildSingle(hrefObs: string | Observable): Observable> {
if (typeof hrefObs === 'string') {
hrefObs = Observable.of(hrefObs);
}
-
const requestHrefObs = hrefObs.flatMap((href: string) =>
this.objectCache.getRequestHrefBySelfLink(href));
- const requestObs = Observable.race(
+ const requestEntryObs = Observable.race(
hrefObs.flatMap((href: string) => this.requestService.getByHref(href))
.filter((entry) => hasValue(entry)),
requestHrefObs.flatMap((requestHref) =>
@@ -53,14 +54,14 @@ export class RemoteDataBuildService {
// always use self link if that is cached, only if it isn't, get it via the response.
const payloadObs =
Observable.combineLatest(
- hrefObs.flatMap((href: string) => this.objectCache.getBySelfLink(href, normalizedType))
+ hrefObs.flatMap((href: string) => this.objectCache.getBySelfLink(href))
.startWith(undefined),
responseCacheObs
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).resourceSelfLinks)
.flatMap((resourceSelfLinks: string[]) => {
if (isNotEmpty(resourceSelfLinks)) {
- return this.objectCache.getBySelfLink(resourceSelfLinks[0], normalizedType);
+ return this.objectCache.getBySelfLink(resourceSelfLinks[0]);
} else {
return Observable.of(undefined);
}
@@ -80,12 +81,12 @@ export class RemoteDataBuildService {
})
.startWith(undefined)
.distinctUntilChanged();
- return this.toRemoteDataObservable(hrefObs, requestObs, responseCacheObs, payloadObs);
+ return this.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
}
- private toRemoteDataObservable(hrefObs: Observable, requestObs: Observable, responseCacheObs: Observable, payloadObs: Observable) {
- return Observable.combineLatest(hrefObs, requestObs, responseCacheObs.startWith(undefined), payloadObs,
- (href: string, reqEntry: RequestEntry, resEntry: ResponseCacheEntry, payload: T) => {
+ toRemoteDataObservable(requestEntryObs: Observable, responseCacheObs: Observable, payloadObs: Observable) {
+ return Observable.combineLatest(requestEntryObs, responseCacheObs.startWith(undefined), payloadObs,
+ (reqEntry: RequestEntry, resEntry: ResponseCacheEntry, payload: T) => {
const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true;
const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false;
let isSuccessful: boolean;
@@ -93,7 +94,9 @@ export class RemoteDataBuildService {
if (hasValue(resEntry) && hasValue(resEntry.response)) {
isSuccessful = resEntry.response.isSuccessful;
const errorMessage = isSuccessful === false ? (resEntry.response as ErrorResponse).errorMessage : undefined;
- error = new RemoteDataError(resEntry.response.statusCode, errorMessage);
+ if (hasValue(errorMessage)) {
+ error = new RemoteDataError(resEntry.response.statusCode, errorMessage);
+ }
}
return new RemoteData(
@@ -106,15 +109,12 @@ export class RemoteDataBuildService {
});
}
- buildList(
- hrefObs: string | Observable,
- normalizedType: GenericConstructor
- ): Observable>> {
+ buildList(hrefObs: string | Observable): Observable>> {
if (typeof hrefObs === 'string') {
hrefObs = Observable.of(hrefObs);
}
- const requestObs = hrefObs.flatMap((href: string) => this.requestService.getByHref(href))
+ const requestEntryObs = hrefObs.flatMap((href: string) => this.requestService.getByHref(href))
.filter((entry) => hasValue(entry));
const responseCacheObs = hrefObs.flatMap((href: string) => this.responseCache.get(href))
.filter((entry) => hasValue(entry));
@@ -123,7 +123,7 @@ export class RemoteDataBuildService {
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).resourceSelfLinks)
.flatMap((resourceUUIDs: string[]) => {
- return this.objectCache.getList(resourceUUIDs, normalizedType)
+ return this.objectCache.getList(resourceUUIDs)
.map((normList: TNormalized[]) => {
return normList.map((normalized: TNormalized) => {
return this.build(normalized);
@@ -154,10 +154,10 @@ export class RemoteDataBuildService {
}
});
- return this.toRemoteDataObservable(hrefObs, requestObs, responseCacheObs, payloadObs);
+ return this.toRemoteDataObservable(requestEntryObs, responseCacheObs, payloadObs);
}
- build(normalized: TNormalized): TDomain {
+ build(normalized: TNormalized): TDomain {
const links: any = {};
const relationships = getRelationships(normalized.constructor) || [];
@@ -165,7 +165,6 @@ export class RemoteDataBuildService {
relationships.forEach((relationship: string) => {
if (hasValue(normalized[relationship])) {
const { resourceType, isList } = getRelationMetadata(normalized, relationship);
- const resourceConstructor = NormalizedObjectFactory.getConstructor(resourceType);
if (Array.isArray(normalized[relationship])) {
normalized[relationship].forEach((href: string) => {
this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), href))
@@ -173,7 +172,7 @@ export class RemoteDataBuildService {
const rdArr = [];
normalized[relationship].forEach((href: string) => {
- rdArr.push(this.buildSingle(href, resourceConstructor));
+ rdArr.push(this.buildSingle(href));
});
if (isList) {
@@ -188,9 +187,9 @@ export class RemoteDataBuildService {
// in that case only 1 href will be stored in the normalized obj (so the isArray above fails),
// but it should still be built as a list
if (isList) {
- links[relationship] = this.buildList(normalized[relationship], resourceConstructor);
+ links[relationship] = this.buildList(normalized[relationship]);
} else {
- links[relationship] = this.buildSingle(normalized[relationship], resourceConstructor);
+ links[relationship] = this.buildSingle(normalized[relationship]);
}
}
}
@@ -201,6 +200,11 @@ export class RemoteDataBuildService {
}
aggregate(input: Array>>): Observable> {
+
+ if (isEmpty(input)) {
+ return Observable.of(new RemoteData(false, false, true, null, []));
+ }
+
return Observable.combineLatest(
...input,
(...arr: Array>) => {
diff --git a/src/app/core/cache/models/normalized-bitstream-format.model.ts b/src/app/core/cache/models/normalized-bitstream-format.model.ts
deleted file mode 100644
index bb8b049a1c..0000000000
--- a/src/app/core/cache/models/normalized-bitstream-format.model.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { inheritSerialization, autoserialize } from 'cerialize';
-
-import { mapsTo } from '../builders/build-decorators';
-
-import { BitstreamFormat } from '../../shared/bitstream-format.model';
-import { NormalizedObject } from './normalized-object.model';
-
-@mapsTo(BitstreamFormat)
-@inheritSerialization(NormalizedObject)
-export class NormalizedBitstreamFormat extends NormalizedObject {
-
- @autoserialize
- shortDescription: string;
-
- @autoserialize
- description: string;
-
- @autoserialize
- mimetype: string;
-
- @autoserialize
- supportLevel: number;
-
- @autoserialize
- internal: boolean;
-
- @autoserialize
- extensions: string;
-
-}
diff --git a/src/app/core/cache/models/normalized-dspace-object.model.ts b/src/app/core/cache/models/normalized-dspace-object.model.ts
index da42ea5a9b..92174c40f7 100644
--- a/src/app/core/cache/models/normalized-dspace-object.model.ts
+++ b/src/app/core/cache/models/normalized-dspace-object.model.ts
@@ -1,13 +1,16 @@
-import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
+import { autoserialize, autoserializeAs } from 'cerialize';
+import { DSpaceObject } from '../../shared/dspace-object.model';
import { Metadatum } from '../../shared/metadatum.model';
import { ResourceType } from '../../shared/resource-type';
+import { mapsTo } from '../builders/build-decorators';
import { NormalizedObject } from './normalized-object.model';
/**
- * An abstract model class for a DSpaceObject.
+ * An model class for a DSpaceObject.
*/
-export abstract class NormalizedDSpaceObject extends NormalizedObject {
+@mapsTo(DSpaceObject)
+export class NormalizedDSpaceObject extends NormalizedObject {
/**
* The link to the rest endpoint where this object can be found
diff --git a/src/app/core/cache/models/normalized-object-factory.ts b/src/app/core/cache/models/normalized-object-factory.ts
index 3c67b18b3e..5b13d55ac8 100644
--- a/src/app/core/cache/models/normalized-object-factory.ts
+++ b/src/app/core/cache/models/normalized-object-factory.ts
@@ -1,4 +1,3 @@
-import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
import { NormalizedBitstream } from './normalized-bitstream.model';
import { NormalizedBundle } from './normalized-bundle.model';
import { NormalizedItem } from './normalized-item.model';
@@ -7,7 +6,6 @@ import { GenericConstructor } from '../../shared/generic-constructor';
import { NormalizedCommunity } from './normalized-community.model';
import { ResourceType } from '../../shared/resource-type';
import { NormalizedObject } from './normalized-object.model';
-import { NormalizedBitstreamFormat } from './normalized-bitstream-format.model';
export class NormalizedObjectFactory {
public static getConstructor(type: ResourceType): GenericConstructor {
@@ -15,9 +13,6 @@ export class NormalizedObjectFactory {
case ResourceType.Bitstream: {
return NormalizedBitstream
}
- case ResourceType.BitstreamFormat: {
- return NormalizedBitstreamFormat
- }
case ResourceType.Bundle: {
return NormalizedBundle
}
diff --git a/src/app/core/cache/models/normalized-object.model.ts b/src/app/core/cache/models/normalized-object.model.ts
index b26bd90b2a..e98081d68a 100644
--- a/src/app/core/cache/models/normalized-object.model.ts
+++ b/src/app/core/cache/models/normalized-object.model.ts
@@ -1,5 +1,6 @@
import { CacheableObject } from '../object-cache.reducer';
import { autoserialize } from 'cerialize';
+import { ResourceType } from '../../shared/resource-type';
/**
* An abstract model class for a NormalizedObject.
*/
@@ -17,6 +18,9 @@ export abstract class NormalizedObject implements CacheableObject {
@autoserialize
uuid: string;
+ @autoserialize
+ type: ResourceType;
+
@autoserialize
_links: {
[name: string]: string
diff --git a/src/app/core/cache/models/sort-options.model.ts b/src/app/core/cache/models/sort-options.model.ts
index ad639bf853..247504a63a 100644
--- a/src/app/core/cache/models/sort-options.model.ts
+++ b/src/app/core/cache/models/sort-options.model.ts
@@ -1,11 +1,10 @@
export enum SortDirection {
- Ascending,
- Descending
+ ASC = 'ASC',
+ DESC = 'DESC'
}
export class SortOptions {
-
- constructor(public field: string = 'name', public direction: SortDirection = SortDirection.Ascending) {
+ constructor(public field: string, public direction: SortDirection) {
}
}
diff --git a/src/app/core/cache/object-cache.reducer.ts b/src/app/core/cache/object-cache.reducer.ts
index 39c623deed..3a1830e14a 100644
--- a/src/app/core/cache/object-cache.reducer.ts
+++ b/src/app/core/cache/object-cache.reducer.ts
@@ -4,6 +4,7 @@ import {
} from './object-cache.actions';
import { hasValue } from '../../shared/empty.util';
import { CacheEntry } from './cache-entry';
+import { ResourceType } from '../shared/resource-type';
export enum DirtyType {
Created = 'Created',
@@ -19,6 +20,7 @@ export enum DirtyType {
export interface CacheableObject {
uuid?: string;
self: string;
+ type?: ResourceType;
// isNew: boolean;
// dirtyType: DirtyType;
// hasDirtyAttributes: boolean;
diff --git a/src/app/core/cache/object-cache.service.spec.ts b/src/app/core/cache/object-cache.service.spec.ts
index 2cf7eebd0a..80a9121544 100644
--- a/src/app/core/cache/object-cache.service.spec.ts
+++ b/src/app/core/cache/object-cache.service.spec.ts
@@ -2,20 +2,10 @@ import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { ObjectCacheService } from './object-cache.service';
-import { CacheableObject } from './object-cache.reducer';
import { AddToObjectCacheAction, RemoveFromObjectCacheAction } from './object-cache.actions';
import { CoreState } from '../core.reducers';
-
-class TestClass implements CacheableObject {
- constructor(
- public self: string,
- public foo: string
- ) { }
-
- test(): string {
- return this.foo + this.self;
- }
-}
+import { ResourceType } from '../shared/resource-type';
+import { NormalizedItem } from './models/normalized-item.model';
describe('ObjectCacheService', () => {
let service: ObjectCacheService;
@@ -26,7 +16,7 @@ describe('ObjectCacheService', () => {
const msToLive = 900000;
const objectToCache = {
self: selfLink,
- foo: 'bar'
+ type: ResourceType.Item
};
const cacheEntry = {
data: objectToCache,
@@ -63,20 +53,20 @@ describe('ObjectCacheService', () => {
it('should return an observable of the cached object with the specified self link and type', () => {
spyOn(store, 'select').and.returnValue(Observable.of(cacheEntry));
- let testObj: any;
// due to the implementation of spyOn above, this subscribe will be synchronous
- service.getBySelfLink(selfLink, TestClass).take(1).subscribe((o) => testObj = o);
- expect(testObj.self).toBe(selfLink);
- expect(testObj.foo).toBe('bar');
- // this only works if testObj is an instance of TestClass
- expect(testObj.test()).toBe('bar' + selfLink);
+ service.getBySelfLink(selfLink).take(1).subscribe((o) => {
+ expect(o.self).toBe(selfLink);
+ // this only works if testObj is an instance of TestClass
+ expect(o instanceof NormalizedItem).toBeTruthy();
+ }
+ );
});
it('should not return a cached object that has exceeded its time to live', () => {
spyOn(store, 'select').and.returnValue(Observable.of(invalidCacheEntry));
let getObsHasFired = false;
- const subscription = service.getBySelfLink(selfLink, TestClass).subscribe((o) => getObsHasFired = true);
+ const subscription = service.getBySelfLink(selfLink).subscribe((o) => getObsHasFired = true);
expect(getObsHasFired).toBe(false);
subscription.unsubscribe();
});
@@ -84,16 +74,14 @@ describe('ObjectCacheService', () => {
describe('getList', () => {
it('should return an observable of the array of cached objects with the specified self link and type', () => {
- spyOn(service, 'getBySelfLink').and.returnValue(Observable.of(new TestClass(selfLink, 'bar')));
+ const item = new NormalizedItem();
+ item.self = selfLink;
+ spyOn(service, 'getBySelfLink').and.returnValue(Observable.of(item));
- let testObjs: any[];
- service.getList([selfLink, selfLink], TestClass).take(1).subscribe((arr) => testObjs = arr);
- expect(testObjs[0].self).toBe(selfLink);
- expect(testObjs[0].foo).toBe('bar');
- expect(testObjs[0].test()).toBe('bar' + selfLink);
- expect(testObjs[1].self).toBe(selfLink);
- expect(testObjs[1].foo).toBe('bar');
- expect(testObjs[1].test()).toBe('bar' + selfLink);
+ service.getList([selfLink, selfLink]).take(1).subscribe((arr) => {
+ expect(arr[0].self).toBe(selfLink);
+ expect(arr[0] instanceof NormalizedItem).toBeTruthy();
+ });
});
});
diff --git a/src/app/core/cache/object-cache.service.ts b/src/app/core/cache/object-cache.service.ts
index ae41c38fbe..9344f4d5f0 100644
--- a/src/app/core/cache/object-cache.service.ts
+++ b/src/app/core/cache/object-cache.service.ts
@@ -10,6 +10,9 @@ import { hasNoValue } from '../../shared/empty.util';
import { GenericConstructor } from '../shared/generic-constructor';
import { coreSelector, CoreState } from '../core.reducers';
import { pathSelector } from '../shared/selectors';
+import { Item } from '../shared/item.model';
+import { NormalizedObjectFactory } from './models/normalized-object-factory';
+import { NormalizedObject } from './models/normalized-object.model';
function selfLinkFromUuidSelector(uuid: string): MemoizedSelector {
return pathSelector(coreSelector, 'index', IndexName.OBJECT, uuid);
@@ -24,9 +27,8 @@ function entryFromSelfLinkSelector(selfLink: string): MemoizedSelector
- ) { }
+ constructor(private store: Store) {
+ }
/**
* Add an object to the cache
@@ -70,14 +72,17 @@ export class ObjectCacheService {
* @return Observable
* An observable of the requested object
*/
- getByUUID(uuid: string, type: GenericConstructor): Observable {
+ getByUUID(uuid: string): Observable {
return this.store.select(selfLinkFromUuidSelector(uuid))
- .flatMap((selfLink: string) => this.getBySelfLink(selfLink, type))
+ .flatMap((selfLink: string) => this.getBySelfLink(selfLink))
}
- getBySelfLink(selfLink: string, type: GenericConstructor): Observable {
+ getBySelfLink(selfLink: string): Observable {
return this.getEntry(selfLink)
- .map((entry: ObjectCacheEntry) => Object.assign(new type(), entry.data) as T);
+ .map((entry: ObjectCacheEntry) => {
+ const type: GenericConstructor= NormalizedObjectFactory.getConstructor(entry.data.type);
+ return Object.assign(new type(), entry.data) as T
+ });
}
private getEntry(selfLink: string): Observable {
@@ -116,9 +121,9 @@ export class ObjectCacheService {
* The type of the objects to get
* @return Observable>
*/
- getList(selfLinks: string[], type: GenericConstructor): Observable {
+ getList(selfLinks: string[]): Observable {
return Observable.combineLatest(
- selfLinks.map((selfLink: string) => this.getBySelfLink(selfLink, type))
+ selfLinks.map((selfLink: string) => this.getBySelfLink(selfLink))
);
}
diff --git a/src/app/core/cache/response-cache.models.ts b/src/app/core/cache/response-cache.models.ts
index 06fc26aa67..f061e78e6c 100644
--- a/src/app/core/cache/response-cache.models.ts
+++ b/src/app/core/cache/response-cache.models.ts
@@ -1,7 +1,10 @@
+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';
import { ConfigObject } from '../shared/config/config.model';
+import { FacetValue } from '../../+search-page/search-service/facet-value.model';
+import { SearchFilterConfig } from '../../+search-page/search-service/search-filter-config.model';
/* tslint:disable:max-classes-per-file */
export class RestResponse {
@@ -21,11 +24,52 @@ 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 FacetConfigSuccessResponse extends RestResponse {
+ constructor(
+ public results: SearchFilterConfig[],
+ public statusCode: string
+ ) {
+ super(true, statusCode);
+ }
+}
+
+export class FacetValueMap {
+ [name: string]: FacetValueSuccessResponse
+}
+
+export class FacetValueSuccessResponse extends RestResponse {
+ constructor(
+ public results: FacetValue[],
+ public statusCode: string,
+ public pageInfo?: PageInfo) {
+ super(true, statusCode);
+ }
+}
+
+export class FacetValueMapSuccessResponse extends RestResponse {
+ constructor(
+ public results: FacetValueMap,
+ public statusCode: string,
+ ) {
+ super(true, statusCode);
+ }
+}
+
+export class EndpointMap {
+ [linkPath: string]: string
+}
+
+export class EndpointMapSuccessResponse extends RestResponse {
constructor(
public endpointMap: EndpointMap,
public statusCode: string,
diff --git a/src/app/core/config/config.service.spec.ts b/src/app/core/config/config.service.spec.ts
index b0c364a86e..4b05d5c929 100644
--- a/src/app/core/config/config.service.spec.ts
+++ b/src/app/core/config/config.service.spec.ts
@@ -1,24 +1,24 @@
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/Rx';
-import { GlobalConfig } from '../../../config';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { ConfigService } from './config.service';
import { RequestService } from '../data/request.service';
import { ConfigRequest, FindAllOptions } from '../data/request.models';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
const LINK_NAME = 'test';
const BROWSE = 'search/findByCollection';
class TestService extends ConfigService {
- protected linkName = LINK_NAME;
+ protected linkPath = LINK_NAME;
protected browseEndpoint = BROWSE;
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
- protected EnvConfig: GlobalConfig
- ) {
+ protected halService: HALEndpointService) {
super();
}
}
@@ -28,8 +28,8 @@ describe('ConfigService', () => {
let service: TestService;
let responseCache: ResponseCacheService;
let requestService: RequestService;
+ let halService: any;
- const envConfig = {} as GlobalConfig;
const findOptions: FindAllOptions = new FindAllOptions();
const scopeName = 'traditional';
@@ -51,7 +51,7 @@ describe('ConfigService', () => {
return new TestService(
responseCache,
requestService,
- envConfig
+ halService
);
}
@@ -60,8 +60,7 @@ describe('ConfigService', () => {
requestService = getMockRequestService();
service = initTestService();
scheduler = getTestScheduler();
- spyOn(service, 'getEndpoint').and
- .returnValue(hot('--a-', { a: serviceEndpoint }));
+ halService = new HALEndpointServiceStub(configEndpoint);
});
describe('getConfigByHref', () => {
diff --git a/src/app/core/config/config.service.ts b/src/app/core/config/config.service.ts
index 9ad4684300..bb863ad46f 100644
--- a/src/app/core/config/config.service.ts
+++ b/src/app/core/config/config.service.ts
@@ -1,10 +1,7 @@
-import { Injectable } from '@angular/core';
-
import { Observable } from 'rxjs/Observable';
import { RequestService } from '../data/request.service';
import { ResponseCacheService } from '../cache/response-cache.service';
-import { GlobalConfig } from '../../../config/global-config.interface';
import { ConfigSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
import { ConfigRequest, FindAllOptions, RestRequest } from '../data/request.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
@@ -12,13 +9,13 @@ import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { ConfigData } from './config-data';
-export abstract class ConfigService extends HALEndpointService {
+export abstract class ConfigService {
protected request: ConfigRequest;
protected abstract responseCache: ResponseCacheService;
protected abstract requestService: RequestService;
- protected abstract linkName: string;
- protected abstract EnvConfig: GlobalConfig;
+ protected abstract linkPath: string;
protected abstract browseEndpoint: string;
+ protected abstract halService: HALEndpointService;
protected getConfig(request: RestRequest): Observable {
const [successResponse, errorResponse] = this.responseCache.get(request.href)
@@ -58,11 +55,7 @@ export abstract class ConfigService extends HALEndpointService {
}
if (hasValue(options.sort)) {
- let direction = 'asc';
- if (options.sort.direction === 1) {
- direction = 'desc';
- }
- args.push(`sort=${options.sort.field},${direction}`);
+ args.push(`sort=${options.sort.field},${options.sort.direction}`);
}
if (isNotEmpty(args)) {
@@ -72,7 +65,7 @@ export abstract class ConfigService extends HALEndpointService {
}
public getConfigAll(): Observable {
- return this.getEndpoint()
+ return this.halService.getEndpoint(this.linkPath)
.filter((href: string) => isNotEmpty(href))
.distinctUntilChanged()
.map((endpointURL: string) => new ConfigRequest(this.requestService.generateRequestId(), endpointURL))
@@ -89,7 +82,7 @@ export abstract class ConfigService extends HALEndpointService {
}
public getConfigByName(name: string): Observable {
- return this.getEndpoint()
+ return this.halService.getEndpoint(this.linkPath)
.map((endpoint: string) => this.getConfigByNameHref(endpoint, name))
.filter((href: string) => isNotEmpty(href))
.distinctUntilChanged()
@@ -100,7 +93,7 @@ export abstract class ConfigService extends HALEndpointService {
}
public getConfigBySearch(options: FindAllOptions = {}): Observable {
- return this.getEndpoint()
+ return this.halService.getEndpoint(this.linkPath)
.map((endpoint: string) => this.getConfigSearchHref(endpoint, options))
.filter((href: string) => isNotEmpty(href))
.distinctUntilChanged()
diff --git a/src/app/core/config/submission-definitions-config.service.ts b/src/app/core/config/submission-definitions-config.service.ts
index 4857569236..6cbe0c55b5 100644
--- a/src/app/core/config/submission-definitions-config.service.ts
+++ b/src/app/core/config/submission-definitions-config.service.ts
@@ -1,20 +1,19 @@
-import { Inject, Injectable } from '@angular/core';
+import { Injectable } from '@angular/core';
import { ConfigService } from './config.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { RequestService } from '../data/request.service';
-import { GLOBAL_CONFIG } from '../../../config';
-import { GlobalConfig } from '../../../config/global-config.interface';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
@Injectable()
export class SubmissionDefinitionsConfigService extends ConfigService {
- protected linkName = 'submissiondefinitions';
+ protected linkPath = 'submissiondefinitions';
protected browseEndpoint = 'search/findByCollection';
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
- @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig) {
+ protected halService: HALEndpointService) {
super();
}
diff --git a/src/app/core/config/submission-forms-config.service.ts b/src/app/core/config/submission-forms-config.service.ts
index 5e992146ee..27eac78218 100644
--- a/src/app/core/config/submission-forms-config.service.ts
+++ b/src/app/core/config/submission-forms-config.service.ts
@@ -1,20 +1,19 @@
-import { Inject, Injectable } from '@angular/core';
+import { Injectable } from '@angular/core';
import { ConfigService } from './config.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { RequestService } from '../data/request.service';
-import { GLOBAL_CONFIG } from '../../../config';
-import { GlobalConfig } from '../../../config/global-config.interface';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
@Injectable()
export class SubmissionFormsConfigService extends ConfigService {
- protected linkName = 'submissionforms';
+ protected linkPath = 'submissionforms';
protected browseEndpoint = '';
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
- @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig) {
+ protected halService: HALEndpointService) {
super();
}
diff --git a/src/app/core/config/submission-sections-config.service.ts b/src/app/core/config/submission-sections-config.service.ts
index 96a8557e9c..6d4d2ca825 100644
--- a/src/app/core/config/submission-sections-config.service.ts
+++ b/src/app/core/config/submission-sections-config.service.ts
@@ -1,20 +1,19 @@
-import { Inject, Injectable } from '@angular/core';
+import { Injectable } from '@angular/core';
import { ConfigService } from './config.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { RequestService } from '../data/request.service';
-import { GLOBAL_CONFIG } from '../../../config';
-import { GlobalConfig } from '../../../config/global-config.interface';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
@Injectable()
export class SubmissionSectionsConfigService extends ConfigService {
- protected linkName = 'submissionsections';
+ protected linkPath = 'submissionsections';
protected browseEndpoint = '';
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
- @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig) {
+ protected halService: HALEndpointService) {
super();
}
diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts
index 7cda10b4ae..ebb87bf1ee 100644
--- a/src/app/core/core.effects.ts
+++ b/src/app/core/core.effects.ts
@@ -8,5 +8,5 @@ export const coreEffects = [
ResponseCacheEffects,
RequestEffects,
ObjectCacheEffects,
- UUIDIndexEffects,
+ UUIDIndexEffects
];
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 768f05f24b..86abf87d62 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -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';
@@ -38,6 +40,10 @@ import { SubmissionDefinitionsConfigService } from './config/submission-definiti
import { SubmissionFormsConfigService } from './config/submission-forms-config.service';
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';
import { UUIDService } from './shared/uuid.service';
+import { HALEndpointService } from './shared/hal-endpoint.service';
+import { FacetValueResponseParsingService } from './data/facet-value-response-parsing.service';
+import { FacetValueMapResponseParsingService } from './data/facet-value-map-response-parsing.service';
+import { FacetConfigResponseParsingService } from './data/facet-config-response-parsing.service';
const IMPORTS = [
CommonModule,
@@ -59,6 +65,7 @@ const PROVIDERS = [
CollectionDataService,
DSOResponseParsingService,
DSpaceRESTv2Service,
+ HALEndpointService,
HostWindowService,
ItemDataService,
MetadataService,
@@ -67,7 +74,12 @@ const PROVIDERS = [
RemoteDataBuildService,
RequestService,
ResponseCacheService,
- RootResponseParsingService,
+ EndpointMapResponseParsingService,
+ FacetValueResponseParsingService,
+ FacetValueMapResponseParsingService,
+ FacetConfigResponseParsingService,
+ DebugResponseParsingService,
+ SearchResponseParsingService,
ServerResponseService,
BrowseResponseParsingService,
BrowseService,
diff --git a/src/app/core/data/base-response-parsing.service.ts b/src/app/core/data/base-response-parsing.service.ts
index d8a4221420..bde0857946 100644
--- a/src/app/core/data/base-response-parsing.service.ts
+++ b/src/app/core/data/base-response-parsing.service.ts
@@ -117,9 +117,14 @@ export abstract class BaseResponseParsingService {
this.objectCache.add(co, this.EnvConfig.cache.msToLive, requestHref);
}
- protected processPageInfo(pageObj: any): PageInfo {
- if (isNotEmpty(pageObj)) {
- return new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
+ processPageInfo(payload: any): PageInfo {
+ if (isNotEmpty(payload.page)) {
+ const pageObj = Object.assign({}, payload.page, {_links: payload._links});
+ const pageInfoObject = new DSpaceRESTv2Serializer(PageInfo).deserialize(pageObj);
+ if (pageInfoObject.currentPage >= 0) {
+ Object.assign(pageInfoObject, { currentPage: pageInfoObject.currentPage + 1 });
+ }
+ return pageInfoObject
} else {
return undefined;
}
diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts
index f9f581128b..7d1e463dbe 100644
--- a/src/app/core/data/collection-data.service.ts
+++ b/src/app/core/data/collection-data.service.ts
@@ -10,20 +10,21 @@ import { Collection } from '../shared/collection.model';
import { ComColDataService } from './comcol-data.service';
import { CommunityDataService } from './community-data.service';
import { RequestService } from './request.service';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
@Injectable()
export class CollectionDataService extends ComColDataService {
- protected linkName = 'collections';
+ protected linkPath = 'collections';
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store,
- @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
protected cds: CommunityDataService,
- protected objectCache: ObjectCacheService
+ protected objectCache: ObjectCacheService,
+ protected halService: HALEndpointService
) {
- super(NormalizedCollection);
+ super();
}
}
diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts
index fefe7d3730..b5727fb22f 100644
--- a/src/app/core/data/comcol-data.service.spec.ts
+++ b/src/app/core/data/comcol-data.service.spec.ts
@@ -5,7 +5,6 @@ import { GlobalConfig } from '../../../config';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
-import { CacheableObject } from '../cache/object-cache.reducer';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
@@ -13,16 +12,16 @@ import { ComColDataService } from './comcol-data.service';
import { CommunityDataService } from './community-data.service';
import { FindByIDRequest } from './request.models';
import { RequestService } from './request.service';
+import { NormalizedObject } from '../cache/models/normalized-object.model';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
const LINK_NAME = 'test';
/* tslint:disable:max-classes-per-file */
-class NormalizedTestObject implements CacheableObject {
- self: string;
+class NormalizedTestObject extends NormalizedObject {
}
class TestService extends ComColDataService {
- protected linkName = LINK_NAME;
constructor(
protected responseCache: ResponseCacheService,
@@ -31,9 +30,11 @@ class TestService extends ComColDataService {
protected store: Store,
protected EnvConfig: GlobalConfig,
protected cds: CommunityDataService,
- protected objectCache: ObjectCacheService
+ protected objectCache: ObjectCacheService,
+ protected halService: HALEndpointService,
+ protected linkPath: string
) {
- super(NormalizedTestObject);
+ super();
}
}
/* tslint:enable:max-classes-per-file */
@@ -45,6 +46,7 @@ describe('ComColDataService', () => {
let requestService: RequestService;
let cds: CommunityDataService;
let objectCache: ObjectCacheService;
+ const halService: any = {};
const rdbService = {} as RemoteDataBuildService;
const store = {} as Store;
@@ -91,7 +93,9 @@ describe('ComColDataService', () => {
store,
EnvConfig,
cds,
- objectCache
+ objectCache,
+ halService,
+ LINK_NAME
);
}
@@ -127,7 +131,7 @@ describe('ComColDataService', () => {
it('should fetch the scope Community from the cache', () => {
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
scheduler.flush();
- expect(objectCache.getByUUID).toHaveBeenCalledWith(scopeID, NormalizedCommunity);
+ expect(objectCache.getByUUID).toHaveBeenCalledWith(scopeID);
});
it('should return the endpoint to fetch resources within the given scope', () => {
@@ -155,24 +159,5 @@ describe('ComColDataService', () => {
});
});
- describe('if the scope is not specified', () => {
- beforeEach(() => {
- cds = initMockCommunityDataService();
- requestService = getMockRequestService();
- objectCache = initMockObjectCacheService();
- responseCache = initMockResponseCacheService(true);
- service = initTestService();
- });
-
- it('should return this.getEndpoint()', () => {
- spyOn(service, 'getEndpoint').and.returnValue(cold('--e-', { e: serviceEndpoint }));
-
- const result = service.getScopedEndpoint(undefined);
- const expected = cold('--f-', { f: serviceEndpoint });
-
- expect(result).toBeObservable(expected);
- });
- });
-
});
});
diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts
index 68981121c1..112afa0bc8 100644
--- a/src/app/core/data/comcol-data.service.ts
+++ b/src/app/core/data/comcol-data.service.ts
@@ -9,15 +9,18 @@ import { CommunityDataService } from './community-data.service';
import { DataService } from './data.service';
import { FindByIDRequest } from './request.models';
+import { NormalizedObject } from '../cache/models/normalized-object.model';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
-export abstract class ComColDataService extends DataService {
+export abstract class ComColDataService extends DataService {
protected abstract cds: CommunityDataService;
protected abstract objectCache: ObjectCacheService;
+ protected abstract halService: HALEndpointService;
/**
* 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
@@ -26,7 +29,7 @@ export abstract class ComColDataService {
if (isEmpty(scopeID)) {
- return this.getEndpoint();
+ return this.halService.getEndpoint(this.linkPath);
} else {
const scopeCommunityHrefObs = this.cds.getEndpoint()
.flatMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, scopeID))
@@ -47,8 +50,8 @@ export abstract class ComColDataService
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])
+ .flatMap((response: DSOSuccessResponse) => this.objectCache.getByUUID(scopeID))
+ .map((nc: NormalizedCommunity) => nc._links[this.linkPath])
.filter((href) => isNotEmpty(href))
).distinctUntilChanged();
}
diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts
index bbee96ab47..88ad3a5287 100644
--- a/src/app/core/data/community-data.service.ts
+++ b/src/app/core/data/community-data.service.ts
@@ -10,10 +10,11 @@ import { CoreState } from '../core.reducers';
import { Community } from '../shared/community.model';
import { ComColDataService } from './comcol-data.service';
import { RequestService } from './request.service';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
@Injectable()
export class CommunityDataService extends ComColDataService {
- protected linkName = 'communities';
+ protected linkPath = 'communities';
protected cds = this;
constructor(
@@ -21,9 +22,13 @@ export class CommunityDataService extends ComColDataService,
- @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
- protected objectCache: ObjectCacheService
+ protected objectCache: ObjectCacheService,
+ protected halService: HALEndpointService
) {
- super(NormalizedCommunity);
+ super();
+ }
+
+ getEndpoint() {
+ return this.halService.getEndpoint(this.linkPath);
}
}
diff --git a/src/app/core/data/config-response-parsing.service.ts b/src/app/core/data/config-response-parsing.service.ts
index 69be4bbc02..033c9ddc68 100644
--- a/src/app/core/data/config-response-parsing.service.ts
+++ b/src/app/core/data/config-response-parsing.service.ts
@@ -29,7 +29,7 @@ export class ConfigResponseParsingService extends BaseResponseParsingService imp
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
if (isNotEmpty(data.payload) && isNotEmpty(data.payload._links) && data.statusCode === '200') {
const configDefinition = this.process(data.payload, request.href);
- return new ConfigSuccessResponse(configDefinition[Object.keys(configDefinition)[0]], data.statusCode, this.processPageInfo(data.payload.page));
+ return new ConfigSuccessResponse(configDefinition[Object.keys(configDefinition)[0]], data.statusCode, this.processPageInfo(data.payload));
} else {
return new ErrorResponse(
Object.assign(
diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts
index 2d003d6fd1..f532ff05ba 100644
--- a/src/app/core/data/data.service.ts
+++ b/src/app/core/data/data.service.ts
@@ -1,32 +1,24 @@
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
-import { GlobalConfig } from '../../../config';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
-import { CacheableObject } from '../cache/object-cache.reducer';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
-import { GenericConstructor } from '../shared/generic-constructor';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { URLCombiner } from '../url-combiner/url-combiner';
import { PaginatedList } from './paginated-list';
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';
-export abstract class DataService extends HALEndpointService {
+export abstract class DataService {
protected abstract responseCache: ResponseCacheService;
protected abstract requestService: RequestService;
protected abstract rdbService: RemoteDataBuildService;
protected abstract store: Store;
- protected abstract linkName: string;
- protected abstract EnvConfig: GlobalConfig;
-
- constructor(
- protected normalizedResourceType: GenericConstructor,
- ) {
- super();
- }
+ protected abstract linkPath: string;
+ protected abstract halService: HALEndpointService;
public abstract getScopedEndpoint(scope: string): Observable
@@ -50,11 +42,7 @@ export abstract class DataService
}
if (hasValue(options.sort)) {
- let direction = 'asc';
- if (options.sort.direction === 1) {
- direction = 'desc';
- }
- args.push(`sort=${options.sort.field},${direction}`);
+ args.push(`sort=${options.sort.field},${options.sort.direction}`);
}
if (isNotEmpty(args)) {
@@ -65,7 +53,7 @@ export abstract class DataService
}
findAll(options: FindAllOptions = {}): Observable>> {
- const hrefObs = this.getEndpoint().filter((href: string) => isNotEmpty(href))
+ const hrefObs = this.halService.getEndpoint(this.linkPath).filter((href: string) => isNotEmpty(href))
.flatMap((endpoint: string) => this.getFindAllHref(endpoint, options));
hrefObs
@@ -76,7 +64,7 @@ export abstract class DataService
this.requestService.configure(request);
});
- return this.rdbService.buildList(hrefObs, this.normalizedResourceType) as Observable>>;
+ return this.rdbService.buildList(hrefObs) as Observable>>;
}
getFindByIDHref(endpoint, resourceID): string {
@@ -84,7 +72,7 @@ export abstract class DataService
}
findById(id: string): Observable> {
- const hrefObs = this.getEndpoint()
+ const hrefObs = this.halService.getEndpoint(this.linkPath)
.map((endpoint: string) => this.getFindByIDHref(endpoint, id));
hrefObs
@@ -95,12 +83,12 @@ export abstract class DataService
this.requestService.configure(request);
});
- return this.rdbService.buildSingle(hrefObs, this.normalizedResourceType);
+ return this.rdbService.buildSingle(hrefObs);
}
findByHref(href: string): Observable> {
this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), href));
- return this.rdbService.buildSingle(href, this.normalizedResourceType);
+ return this.rdbService.buildSingle(href);
}
// TODO implement, after the structure of the REST server's POST response is finalized
diff --git a/src/app/core/data/debug-response-parsing.service.ts b/src/app/core/data/debug-response-parsing.service.ts
new file mode 100644
index 0000000000..d530948559
--- /dev/null
+++ b/src/app/core/data/debug-response-parsing.service.ts
@@ -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;
+ }
+}
diff --git a/src/app/core/data/dso-response-parsing.service.ts b/src/app/core/data/dso-response-parsing.service.ts
index 11590d0431..9651eb3157 100644
--- a/src/app/core/data/dso-response-parsing.service.ts
+++ b/src/app/core/data/dso-response-parsing.service.ts
@@ -28,7 +28,7 @@ export class DSOResponseParsingService extends BaseResponseParsingService implem
parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
const processRequestDTO = this.process(data.payload, request.href);
const selfLinks = this.flattenSingleKeyObject(processRequestDTO).map((no) => no.self);
- return new DSOSuccessResponse(selfLinks, data.statusCode, this.processPageInfo(data.payload.page))
+ return new DSOSuccessResponse(selfLinks, data.statusCode, this.processPageInfo(data.payload))
}
}
diff --git a/src/app/core/data/root-response-parsing.service.ts b/src/app/core/data/endpoint-map-response-parsing.service.ts
similarity index 76%
rename from src/app/core/data/root-response-parsing.service.ts
rename to src/app/core/data/endpoint-map-response-parsing.service.ts
index a3e7fc22a3..b850e13932 100644
--- a/src/app/core/data/root-response-parsing.service.ts
+++ b/src/app/core/data/endpoint-map-response-parsing.service.ts
@@ -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(
diff --git a/src/app/core/data/facet-config-response-parsing.service.ts b/src/app/core/data/facet-config-response-parsing.service.ts
new file mode 100644
index 0000000000..b0d89fb03e
--- /dev/null
+++ b/src/app/core/data/facet-config-response-parsing.service.ts
@@ -0,0 +1,32 @@
+import { Inject, Injectable } from '@angular/core';
+import {
+ FacetConfigSuccessResponse,
+ RestResponse
+} from '../cache/response-cache.models';
+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 { SearchFilterConfig } from '../../+search-page/search-service/search-filter-config.model';
+import { BaseResponseParsingService } from './base-response-parsing.service';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { GlobalConfig } from '../../../config/global-config.interface';
+import { GLOBAL_CONFIG } from '../../../config';
+
+@Injectable()
+export class FacetConfigResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
+ objectFactory = {};
+ toCache = false;
+ constructor(
+ @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
+ protected objectCache: ObjectCacheService,
+ ) { super();
+ }
+ parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+
+ const config = data.payload._embedded.facets;
+ const serializer = new DSpaceRESTv2Serializer(SearchFilterConfig);
+ const facetConfig = serializer.deserializeArray(config);
+ return new FacetConfigSuccessResponse(facetConfig, data.statusCode);
+ }
+}
diff --git a/src/app/core/data/facet-value-map-response-parsing.service.ts b/src/app/core/data/facet-value-map-response-parsing.service.ts
new file mode 100644
index 0000000000..8588e4aa0b
--- /dev/null
+++ b/src/app/core/data/facet-value-map-response-parsing.service.ts
@@ -0,0 +1,46 @@
+import { Inject, Injectable } from '@angular/core';
+import {
+ FacetValueMap,
+ FacetValueMapSuccessResponse,
+ FacetValueSuccessResponse,
+ RestResponse
+} from '../cache/response-cache.models';
+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 { isNotEmpty } from '../../shared/empty.util';
+import { FacetValue } from '../../+search-page/search-service/facet-value.model';
+import { BaseResponseParsingService } from './base-response-parsing.service';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { GlobalConfig } from '../../../config/global-config.interface';
+import { GLOBAL_CONFIG } from '../../../config';
+
+@Injectable()
+export class FacetValueMapResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
+ objectFactory = {};
+ toCache = false;
+
+ constructor(
+ @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
+ protected objectCache: ObjectCacheService,
+ ) { super();
+ }
+
+ parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+
+ const payload = data.payload;
+ const facetMap: FacetValueMap = new FacetValueMap();
+
+ const serializer = new DSpaceRESTv2Serializer(FacetValue);
+ payload._embedded.facets.map((facet) => {
+ const values = facet._embedded.values.map((value) => {value.search = value._links.search.href; return value;});
+ const facetValues = serializer.deserializeArray(values);
+ const valuesResponse = new FacetValueSuccessResponse(facetValues, data.statusCode, this.processPageInfo(data.payload));
+ facetMap[facet.name] = valuesResponse;
+ });
+
+ return new FacetValueMapSuccessResponse(facetMap, data.statusCode);
+ }
+}
diff --git a/src/app/core/data/facet-value-response-parsing.service.ts b/src/app/core/data/facet-value-response-parsing.service.ts
new file mode 100644
index 0000000000..bc3f4e5368
--- /dev/null
+++ b/src/app/core/data/facet-value-response-parsing.service.ts
@@ -0,0 +1,38 @@
+import { Inject, Injectable } from '@angular/core';
+import {
+ FacetValueMap,
+ FacetValueMapSuccessResponse,
+ FacetValueSuccessResponse,
+ RestResponse
+} from '../cache/response-cache.models';
+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 { isNotEmpty } from '../../shared/empty.util';
+import { FacetValue } from '../../+search-page/search-service/facet-value.model';
+import { BaseResponseParsingService } from './base-response-parsing.service';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { GLOBAL_CONFIG } from '../../../config';
+import { GlobalConfig } from '../../../config/global-config.interface';
+
+@Injectable()
+export class FacetValueResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
+ objectFactory = {};
+ toCache = false;
+ constructor(
+ @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
+ protected objectCache: ObjectCacheService,
+ ) { super();
+ }
+ parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+ const payload = data.payload;
+
+ const serializer = new DSpaceRESTv2Serializer(FacetValue);
+ // const values = payload._embedded.values.map((value) => {value.search = value._links.search.href; return value;});
+
+ const facetValues = serializer.deserializeArray(payload._embedded.values);
+ return new FacetValueSuccessResponse(facetValues, data.statusCode, this.processPageInfo(data.payload));
+ }
+}
diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts
index 7d610bfaae..4d0dc8aec3 100644
--- a/src/app/core/data/item-data.service.spec.ts
+++ b/src/app/core/data/item-data.service.spec.ts
@@ -1,24 +1,23 @@
import { Store } from '@ngrx/store';
import { cold, getTestScheduler } from 'jasmine-marbles';
import { TestScheduler } from 'rxjs/Rx';
-import { GlobalConfig } from '../../../config/global-config.interface';
import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ResponseCacheService } from '../cache/response-cache.service';
import { CoreState } from '../core.reducers';
import { ItemDataService } from './item-data.service';
import { RequestService } from './request.service';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
describe('ItemDataService', () => {
let scheduler: TestScheduler;
let service: ItemDataService;
let bs: BrowseService;
-
const requestService = {} as RequestService;
const responseCache = {} as ResponseCacheService;
const rdbService = {} as RemoteDataBuildService;
const store = {} as Store;
- const EnvConfig = {} as GlobalConfig;
+ const halEndpointService = {} as HALEndpointService;
const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39';
const browsesEndpoint = 'https://rest.api/discover/browses';
@@ -42,8 +41,8 @@ describe('ItemDataService', () => {
requestService,
rdbService,
store,
- EnvConfig,
- bs
+ bs,
+ halEndpointService
);
}
@@ -74,21 +73,5 @@ describe('ItemDataService', () => {
expect(result).toBeObservable(expected);
});
});
-
- describe('if the scope is not specified', () => {
- beforeEach(() => {
- bs = initMockBrowseService(true);
- service = initTestService();
- spyOn(service, 'getEndpoint').and.returnValue(cold('--b-', { b: serviceEndpoint }))
- });
-
- it('should return this.getEndpoint()', () => {
- const result = service.getScopedEndpoint(undefined);
- const expected = cold('--c-', { c: serviceEndpoint });
-
- expect(result).toBeObservable(expected);
- });
- });
-
});
});
diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts
index 7e978e0879..6b0937d8e4 100644
--- a/src/app/core/data/item-data.service.ts
+++ b/src/app/core/data/item-data.service.ts
@@ -14,27 +14,27 @@ import { URLCombiner } from '../url-combiner/url-combiner';
import { DataService } from './data.service';
import { RequestService } from './request.service';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
@Injectable()
export class ItemDataService extends DataService {
- protected linkName = 'items';
+ protected linkPath = 'items';
constructor(
protected responseCache: ResponseCacheService,
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store,
- @Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
- private bs: BrowseService
- ) {
- super(NormalizedItem);
+ private bs: BrowseService,
+ protected halService: HALEndpointService) {
+ super();
}
public getScopedEndpoint(scopeID: string): Observable {
if (isEmpty(scopeID)) {
- return this.getEndpoint();
+ return this.halService.getEndpoint(this.linkPath);
} 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();
diff --git a/src/app/core/data/paginated-list.ts b/src/app/core/data/paginated-list.ts
index f1d076927d..21cc13f3fa 100644
--- a/src/app/core/data/paginated-list.ts
+++ b/src/app/core/data/paginated-list.ts
@@ -1,15 +1,17 @@
import { PageInfo } from '../shared/page-info.model';
+import { hasValue } from '../../shared/empty.util';
export class PaginatedList {
- constructor(
- private pageInfo: PageInfo,
- public page: T[]
- ) {
+ constructor(private pageInfo: PageInfo,
+ public page: T[]) {
}
get elementsPerPage(): number {
- return this.pageInfo.elementsPerPage;
+ if (hasValue(this.pageInfo)) {
+ return this.pageInfo.elementsPerPage;
+ }
+ return this.page.length;
}
set elementsPerPage(value: number) {
@@ -17,7 +19,10 @@ export class PaginatedList {
}
get totalElements(): number {
- return this.pageInfo.totalElements;
+ if (hasValue(this.pageInfo)) {
+ return this.pageInfo.totalElements;
+ }
+ return this.page.length;
}
set totalElements(value: number) {
@@ -25,7 +30,10 @@ export class PaginatedList {
}
get totalPages(): number {
- return this.pageInfo.totalPages;
+ if (hasValue(this.pageInfo)) {
+ return this.pageInfo.totalPages;
+ }
+ return 1;
}
set totalPages(value: number) {
@@ -33,10 +41,44 @@ export class PaginatedList {
}
get currentPage(): number {
- return this.pageInfo.currentPage;
+ if (hasValue(this.pageInfo)) {
+ return this.pageInfo.currentPage;
+ }
+ return 1;
}
set currentPage(value: number) {
this.pageInfo.currentPage = value;
}
+
+ get first(): string {
+ return this.pageInfo.first;
+ }
+
+ set first(first: string) {
+ this.pageInfo.first = first;
+ }
+
+ get prev(): string {
+ return this.pageInfo.prev;
+ }
+ set prev(prev: string) {
+ this.pageInfo.prev = prev;
+ }
+
+ get next(): string {
+ return this.pageInfo.next;
+ }
+
+ set next(next: string) {
+ this.pageInfo.next = next;
+ }
+
+ get last(): string {
+ return this.pageInfo.last;
+ }
+
+ set last(last: string) {
+ this.pageInfo.last = last;
+ }
}
diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts
index ee37f9c3d4..21df69b3a2 100644
--- a/src/app/core/data/request.models.ts
+++ b/src/app/core/data/request.models.ts
@@ -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,17 @@ 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 {
- return RootResponseParsingService;
+ return EndpointMapResponseParsingService;
}
}
diff --git a/src/app/core/data/search-response-parsing.service.ts b/src/app/core/data/search-response-parsing.service.ts
new file mode 100644
index 0000000000..c7456aa2f9
--- /dev/null
+++ b/src/app/core/data/search-response-parsing.service.ts
@@ -0,0 +1,61 @@
+import { Injectable } from '@angular/core';
+import { 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';
+import { Metadatum } from '../shared/metadatum.model';
+
+@Injectable()
+export class SearchResponseParsingService implements ResponseParsingService {
+ constructor(private dsoParser: DSOResponseParsingService) {
+ }
+
+ parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+ const payload = data.payload;
+ const hitHighlights = payload._embedded.objects
+ .map((object) => object.hitHighlights)
+ .map((hhObject) => {
+ if (hhObject) {
+ return Object.keys(hhObject).map((key) => Object.assign(new Metadatum(), {
+ key: key,
+ value: hhObject[key].join('...')
+ }))
+ } else {
+ return [];
+ }
+ });
+
+ const dsoSelfLinks = payload._embedded.objects
+ .filter((object) => hasValue(object._embedded))
+ .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
+ .filter((object) => hasValue(object._embedded))
+ .map((object, index) => Object.assign({}, object, {
+ dspaceObject: dsoSelfLinks[index],
+ hitHighlights: hitHighlights[index],
+ // 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
+ _embedded: undefined
+ }));
+ payload.objects = objects;
+ const deserialized = new DSpaceRESTv2Serializer(SearchQueryResponse).deserialize(payload);
+ return new SearchSuccessResponse(deserialized, data.statusCode, this.dsoParser.processPageInfo(data.payload));
+ }
+}
diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts
index 4182587cc7..f8f36a358e 100644
--- a/src/app/core/metadata/metadata.service.spec.ts
+++ b/src/app/core/metadata/metadata.service.spec.ts
@@ -33,7 +33,7 @@ import { Item } from '../../core/shared/item.model';
import { MockItem } from '../../shared/mocks/mock-item';
import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader';
import { BrowseService } from '../browse/browse.service';
-import { PageInfo } from '../shared/page-info.model';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
/* tslint:disable:max-classes-per-file */
@Component({
@@ -114,6 +114,7 @@ describe('MetadataService', () => {
{ provide: RequestService, useValue: requestService },
{ provide: RemoteDataBuildService, useValue: remoteDataBuildService },
{ provide: GLOBAL_CONFIG, useValue: ENV_CONFIG },
+ { provide: HALEndpointService, useValue: {}},
Meta,
Title,
ItemDataService,
diff --git a/src/app/core/shared/bitstream-format.model.ts b/src/app/core/shared/bitstream-format.model.ts
index c0f6be29c9..b85d9e2053 100644
--- a/src/app/core/shared/bitstream-format.model.ts
+++ b/src/app/core/shared/bitstream-format.model.ts
@@ -1,17 +1,24 @@
-import { DSpaceObject } from './dspace-object.model';
-export class BitstreamFormat extends DSpaceObject {
+import { autoserialize } from 'cerialize';
+export class BitstreamFormat {
+
+ @autoserialize
shortDescription: string;
+ @autoserialize
description: string;
+ @autoserialize
mimetype: string;
+ @autoserialize
supportLevel: number;
+ @autoserialize
internal: boolean;
+ @autoserialize
extensions: string;
}
diff --git a/src/app/core/shared/dspace-object.model.ts b/src/app/core/shared/dspace-object.model.ts
index 8f96f2485a..5e62e3e321 100644
--- a/src/app/core/shared/dspace-object.model.ts
+++ b/src/app/core/shared/dspace-object.model.ts
@@ -9,7 +9,7 @@ import { Observable } from 'rxjs/Observable';
/**
* An abstract model class for a DSpaceObject.
*/
-export abstract class DSpaceObject implements CacheableObject, ListableObject {
+export class DSpaceObject implements CacheableObject, ListableObject {
self: string;
diff --git a/src/app/core/shared/hal-endpoint.service.spec.ts b/src/app/core/shared/hal-endpoint.service.spec.ts
index a47bfd745c..0c2afe938b 100644
--- a/src/app/core/shared/hal-endpoint.service.spec.ts
+++ b/src/app/core/shared/hal-endpoint.service.spec.ts
@@ -2,9 +2,9 @@ import { cold, hot } from 'jasmine-marbles';
import { GlobalConfig } from '../../../config/global-config.interface';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { ResponseCacheService } from '../cache/response-cache.service';
-import { RootEndpointRequest } from '../data/request.models';
import { RequestService } from '../data/request.service';
import { HALEndpointService } from './hal-endpoint.service';
+import { EndpointMapRequest } from '../data/request.models';
describe('HALEndpointService', () => {
let service: HALEndpointService;
@@ -15,24 +15,12 @@ describe('HALEndpointService', () => {
const endpointMap = {
test: 'https://rest.api/test',
};
+ const linkPath = 'test';
- /* tslint:disable:no-shadowed-variable */
- class TestService extends HALEndpointService {
- protected linkName = 'test';
-
- constructor(protected responseCache: ResponseCacheService,
- protected requestService: RequestService,
- protected EnvConfig: GlobalConfig) {
- super();
- }
- }
-
- /* tslint:enable:no-shadowed-variable */
-
- describe('getEndpointMap', () => {
+ describe('getRootEndpointMap', () => {
beforeEach(() => {
responseCache = jasmine.createSpyObj('responseCache', {
- get: hot('--a-', {
+ get: hot('a-', {
a: {
response: { endpointMap: endpointMap }
}
@@ -45,57 +33,62 @@ describe('HALEndpointService', () => {
rest: { baseUrl: 'https://rest.api/' }
} as any;
- service = new TestService(
+ service = new HALEndpointService(
responseCache,
requestService,
envConfig
);
});
- it('should configure a new RootEndpointRequest', () => {
- (service as any).getEndpointMap();
- const expected = new RootEndpointRequest(requestService.generateRequestId(), envConfig);
+ it('should configure a new EndpointMapRequest', () => {
+ (service as any).getRootEndpointMap();
+ const expected = new EndpointMapRequest(requestService.generateRequestId(), envConfig.rest.baseUrl);
expect(requestService.configure).toHaveBeenCalledWith(expected);
});
it('should return an Observable of the endpoint map', () => {
- const result = (service as any).getEndpointMap();
- const expected = cold('--b-', { b: endpointMap });
+ const result = (service as any).getRootEndpointMap();
+ const expected = cold('b-', { b: endpointMap });
expect(result).toBeObservable(expected);
});
});
describe('getEndpoint', () => {
+
beforeEach(() => {
- service = new TestService(
+ envConfig = {
+ rest: { baseUrl: 'https://rest.api/' }
+ } as any;
+
+ service = new HALEndpointService(
responseCache,
requestService,
envConfig
);
-
- spyOn(service as any, 'getEndpointMap').and
- .returnValue(hot('--a-', { a: endpointMap }));
});
- it('should return the endpoint URL for the service\'s linkName', () => {
- const result = service.getEndpoint();
- const expected = cold('--b-', { b: endpointMap.test });
+ it('should return the endpoint URL for the service\'s linkPath', () => {
+ spyOn(service as any, 'getEndpointAt').and
+ .returnValue(hot('a-', { a: 'https://rest.api/test' }));
+ const result = service.getEndpoint(linkPath);
+
+ 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';
- const result = service.getEndpoint();
- const expected = cold('--b-', { b: undefined });
+ it('should return undefined for a linkPath that isn\'t in the endpoint map', () => {
+ spyOn(service as any, 'getEndpointAt').and
+ .returnValue(hot('a-', { a: undefined }));
+ const result = service.getEndpoint('unknown');
+ const expected = cold('b-', { b: undefined });
expect(result).toBeObservable(expected);
});
-
});
describe('isEnabledOnRestApi', () => {
beforeEach(() => {
- service = new TestService(
+ service = new HALEndpointService(
responseCache,
requestService,
envConfig
@@ -103,31 +96,29 @@ 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();
+ const result = service.isEnabledOnRestApi(linkPath);
const expected = cold('b---', { b: undefined });
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();
+ const result = service.isEnabledOnRestApi(linkPath);
const expected = cold('b-c-', { b: undefined, c: true });
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';
- const result = service.isEnabledOnRestApi();
- const expected = cold('b-c-', { b: undefined, c: false });
+ const result = service.isEnabledOnRestApi('unknown');
+ const expected = cold('b-c-', { b: undefined, c: false });
expect(result).toBeObservable(expected);
});
diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts
index 84587f1eea..3bedeb9915 100644
--- a/src/app/core/shared/hal-endpoint.service.ts
+++ b/src/app/core/shared/hal-endpoint.service.ts
@@ -1,39 +1,76 @@
import { Observable } from 'rxjs/Observable';
+import { distinctUntilChanged, map, flatMap, startWith, tap } 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 } from '../data/request.models';
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
-import { isNotEmpty } from '../../shared/empty.util';
+import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
+import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
+import { Inject, Injectable } from '@angular/core';
+import { GLOBAL_CONFIG } from '../../../config';
-export abstract class HALEndpointService {
- protected abstract responseCache: ResponseCacheService;
- protected abstract requestService: RequestService;
- protected abstract linkName: string;
- protected abstract EnvConfig: GlobalConfig;
+@Injectable()
+export class HALEndpointService {
- protected getEndpointMap(): Observable {
- const request = new RootEndpointRequest(this.requestService.generateRequestId(), this.EnvConfig);
+ constructor(private responseCache: ResponseCacheService,
+ private requestService: RequestService,
+ @Inject(GLOBAL_CONFIG) private EnvConfig: GlobalConfig) {
+ }
+
+ protected getRootHref(): string {
+ return new RESTURLCombiner(this.EnvConfig, '/').toString();
+ }
+
+ protected getRootEndpointMap(): Observable {
+ return this.getEndpointMapAt(this.getRootHref());
+ }
+
+ private getEndpointMapAt(href): Observable {
+ 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))
+ .map((response: EndpointMapSuccessResponse) => response.endpointMap)
.distinctUntilChanged();
}
- public getEndpoint(): Observable {
- return this.getEndpointMap()
- .map((map: EndpointMap) => map[this.linkName])
- .distinctUntilChanged();
+ public getEndpoint(linkPath: string): Observable {
+ return this.getEndpointAt(...linkPath.split('/'));
}
- public isEnabledOnRestApi(): Observable {
- return this.getEndpointMap()
- .map((map: EndpointMap) => isNotEmpty(map[this.linkName]))
- .startWith(undefined)
- .distinctUntilChanged();
+ private getEndpointAt(...path: string[]): Observable {
+ if (isEmpty(path)) {
+ path = ['/'];
+ }
+ let currentPath;
+ const pipeArguments = path
+ .map((subPath: string, index: number) => [
+ flatMap((href: string) => this.getEndpointMapAt(href)),
+ map((endpointMap: EndpointMap) => {
+ if (hasValue(endpointMap) && hasValue(endpointMap[subPath])) {
+ currentPath = endpointMap[subPath];
+ return endpointMap[subPath];
+ } else {
+ /*TODO remove if/else block once the rest response contains _links for facets*/
+ currentPath += '/' + subPath;
+ return currentPath;
+ }
+ }),
+ ])
+ .reduce((combined, thisElement) => [...combined, ...thisElement], []);
+ return Observable.of(this.getRootHref()).pipe(...pipeArguments, distinctUntilChanged());
+ }
+
+ public isEnabledOnRestApi(linkPath: string): Observable {
+ return this.getRootEndpointMap().pipe(
+ // TODO this only works when there's no / in linkPath
+ map((endpointMap: EndpointMap) => isNotEmpty(endpointMap[linkPath])),
+ startWith(undefined),
+ distinctUntilChanged()
+ )
}
}
diff --git a/src/app/core/shared/page-info.model.ts b/src/app/core/shared/page-info.model.ts
index 0cb5cac5b1..ba2af24dce 100644
--- a/src/app/core/shared/page-info.model.ts
+++ b/src/app/core/shared/page-info.model.ts
@@ -28,4 +28,15 @@ export class PageInfo {
@autoserializeAs(Number, 'number')
currentPage: number;
+ @autoserialize
+ last: string;
+
+ @autoserialize
+ next: string;
+
+ @autoserialize
+ prev: string;
+
+ @autoserialize
+ first: string;
}
diff --git a/src/app/core/shared/resource-type.ts b/src/app/core/shared/resource-type.ts
index f3554e18cf..1b73cb0bba 100644
--- a/src/app/core/shared/resource-type.ts
+++ b/src/app/core/shared/resource-type.ts
@@ -1,8 +1,5 @@
-/**
- * TODO replace with actual string enum after upgrade to TypeScript 2.4:
- * https://github.com/Microsoft/TypeScript/pull/15486
- */
export enum ResourceType {
+ DSpaceObject = 'dspaceobject',
Bundle = 'bundle',
Bitstream = 'bitstream',
BitstreamFormat = 'bitstreamformat',
diff --git a/src/app/shared/host-window.service.spec.ts b/src/app/shared/host-window.service.spec.ts
index 674d0e1332..41be3211e9 100644
--- a/src/app/shared/host-window.service.spec.ts
+++ b/src/app/shared/host-window.service.spec.ts
@@ -1,9 +1,10 @@
import { Store } from '@ngrx/store';
+import { cold, hot } from 'jasmine-marbles';
import { Observable } from 'rxjs/Observable';
import { AppState } from '../app.reducer';
import { HostWindowState } from './host-window.reducer';
-import { HostWindowService } from './host-window.service';
+import { GridBreakpoint, HostWindowService, WidthCategory } from './host-window.service';
describe('HostWindowService', () => {
let service: HostWindowService;
@@ -189,4 +190,76 @@ describe('HostWindowService', () => {
});
});
+ describe('widthCategory', () => {
+ beforeEach(() => {
+ service = new HostWindowService({} as Store);
+ });
+
+ it('should call getWithObs to get the current width', () => {
+ spyOn(service as any, 'getWidthObs').and
+ .returnValue(hot('a-', { a: GridBreakpoint.SM_MIN - 1 }));
+
+ const result = service.widthCategory;
+
+ expect((service as any).getWidthObs).toHaveBeenCalled();
+ });
+
+ it('should return XS if width < SM_MIN', () => {
+ spyOn(service as any, 'getWidthObs').and
+ .returnValue(hot('a-', { a: GridBreakpoint.SM_MIN - 1 }));
+
+ const result = service.widthCategory;
+
+ const expected = cold('b-', { b: WidthCategory.XS });
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should return SM if SM_MIN <= width < MD_MIN', () => {
+ spyOn(service as any, 'getWidthObs').and
+ .returnValue(hot('a-', {
+ a: GridBreakpoint.SM_MIN + Math.floor((GridBreakpoint.MD_MIN - GridBreakpoint.SM_MIN) / 2)
+ }));
+
+ const result = service.widthCategory;
+
+ const expected = cold('b-', { b: WidthCategory.SM });
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should return MD if MD_MIN <= width < LG_MIN', () => {
+ spyOn(service as any, 'getWidthObs').and
+ .returnValue(hot('a-', {
+ a: GridBreakpoint.MD_MIN + Math.floor((GridBreakpoint.LG_MIN - GridBreakpoint.MD_MIN) / 2)
+ }));
+
+ const result = service.widthCategory;
+
+ const expected = cold('b-', { b: WidthCategory.MD });
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should return LG if LG_MIN <= width < XL_MIN', () => {
+ spyOn(service as any, 'getWidthObs').and
+ .returnValue(hot('a-', {
+ a: GridBreakpoint.LG_MIN + Math.floor((GridBreakpoint.XL_MIN - GridBreakpoint.LG_MIN) / 2)
+ }));
+
+ const result = service.widthCategory;
+
+ const expected = cold('b-', { b: WidthCategory.LG });
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should return XL if width >= XL_MIN', () => {
+ spyOn(service as any, 'getWidthObs').and
+ .returnValue(hot('a-', { a: GridBreakpoint.XL_MIN + 1 }));
+
+ const result = service.widthCategory;
+
+ const expected = cold('b-', { b: WidthCategory.XL });
+ expect(result).toBeObservable(expected);
+ });
+
+ });
+
});
diff --git a/src/app/shared/host-window.service.ts b/src/app/shared/host-window.service.ts
index 6fa5a6b32b..13ecbe7538 100644
--- a/src/app/shared/host-window.service.ts
+++ b/src/app/shared/host-window.service.ts
@@ -1,3 +1,4 @@
+import { distinctUntilChanged, map } from 'rxjs/operators';
import { HostWindowState } from './host-window.reducer';
import { Injectable } from '@angular/core';
import { createSelector, Store } from '@ngrx/store';
@@ -8,11 +9,18 @@ import { AppState } from '../app.reducer';
// TODO: ideally we should get these from sass somehow
export enum GridBreakpoint {
- XS = 0,
- SM = 576,
- MD = 768,
- LG = 992,
- XL = 1200
+ SM_MIN = 576,
+ MD_MIN = 768,
+ LG_MIN = 992,
+ XL_MIN = 1200
+}
+
+export enum WidthCategory {
+ XS,
+ SM,
+ MD,
+ LG,
+ XL
}
const hostWindowStateSelector = (state: AppState) => state.hostWindow;
@@ -31,33 +39,57 @@ export class HostWindowService {
.filter((width) => hasValue(width));
}
+ get widthCategory(): Observable {
+ return this.getWidthObs().pipe(
+ map((width: number) => {
+ if (width < GridBreakpoint.SM_MIN) {
+ return WidthCategory.XS
+ } else if (width >= GridBreakpoint.SM_MIN && width < GridBreakpoint.MD_MIN) {
+ return WidthCategory.SM
+ } else if (width >= GridBreakpoint.MD_MIN && width < GridBreakpoint.LG_MIN) {
+ return WidthCategory.MD
+ } else if (width >= GridBreakpoint.LG_MIN && width < GridBreakpoint.XL_MIN) {
+ return WidthCategory.LG
+ } else {
+ return WidthCategory.XL
+ }
+ }),
+ distinctUntilChanged()
+ );
+ }
+
isXs(): Observable {
- return this.getWidthObs()
- .map((width) => width < GridBreakpoint.SM)
- .distinctUntilChanged();
+ return this.widthCategory.pipe(
+ map((widthCat: WidthCategory) => widthCat === WidthCategory.XS),
+ distinctUntilChanged()
+ );
}
isSm(): Observable {
- return this.getWidthObs()
- .map((width) => width >= GridBreakpoint.SM && width < GridBreakpoint.MD)
- .distinctUntilChanged();
+ return this.widthCategory.pipe(
+ map((widthCat: WidthCategory) => widthCat === WidthCategory.SM),
+ distinctUntilChanged()
+ );
}
isMd(): Observable {
- return this.getWidthObs()
- .map((width) => width >= GridBreakpoint.MD && width < GridBreakpoint.LG)
- .distinctUntilChanged();
+ return this.widthCategory.pipe(
+ map((widthCat: WidthCategory) => widthCat === WidthCategory.MD),
+ distinctUntilChanged()
+ );
}
isLg(): Observable {
- return this.getWidthObs()
- .map((width) => width >= GridBreakpoint.LG && width < GridBreakpoint.XL)
- .distinctUntilChanged();
+ return this.widthCategory.pipe(
+ map((widthCat: WidthCategory) => widthCat === WidthCategory.LG),
+ distinctUntilChanged()
+ );
}
isXl(): Observable {
- return this.getWidthObs()
- .map((width) => width >= GridBreakpoint.XL)
- .distinctUntilChanged();
+ return this.widthCategory.pipe(
+ map((widthCat: WidthCategory) => widthCat === WidthCategory.XL),
+ distinctUntilChanged()
+ );
}
}
diff --git a/src/app/shared/mocks/mock-request.service.ts b/src/app/shared/mocks/mock-request.service.ts
index ed8ffa028d..02d3e54282 100644
--- a/src/app/shared/mocks/mock-request.service.ts
+++ b/src/app/shared/mocks/mock-request.service.ts
@@ -1,8 +1,10 @@
import { RequestService } from '../../core/data/request.service';
+import { RequestEntry } from '../../core/data/request.reducer';
export function getMockRequestService(): RequestService {
return jasmine.createSpyObj('requestService', {
configure: () => false,
- generateRequestId: () => 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78'
+ generateRequestId: () => 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78',
+ getByHref: (uuid: string) => new RequestEntry()
});
}
diff --git a/src/app/shared/mocks/mock-response-cache.service.ts b/src/app/shared/mocks/mock-response-cache.service.ts
index 95b4e7aca0..ad1457c3eb 100644
--- a/src/app/shared/mocks/mock-response-cache.service.ts
+++ b/src/app/shared/mocks/mock-response-cache.service.ts
@@ -1,10 +1,12 @@
import { ResponseCacheService } from '../../core/cache/response-cache.service';
+import { ResponseCacheEntry } from '../../core/cache/response-cache.reducer';
+import { RestResponse } from '../../core/cache/response-cache.models';
export function getMockResponseCacheService(): ResponseCacheService {
- return jasmine.createSpyObj('ResponseCacheService', [
- 'add',
- 'get',
- 'has',
- ]);
+ return jasmine.createSpyObj('ResponseCacheService', {
+ add: (key: string, response: RestResponse, msToLive: number) => new ResponseCacheEntry(),
+ get: (key: string) => new ResponseCacheEntry(),
+ has: (key: string) => false,
+ });
}
diff --git a/src/app/shared/object-collection/shared/collection-search-result.model.ts b/src/app/shared/object-collection/shared/collection-search-result.model.ts
index 63b6a0d37a..5e9afc59e5 100644
--- a/src/app/shared/object-collection/shared/collection-search-result.model.ts
+++ b/src/app/shared/object-collection/shared/collection-search-result.model.ts
@@ -1,5 +1,7 @@
-import { Collection } from '../../../core/shared/collection.model';
import { SearchResult } from '../../../+search-page/search-result.model';
+import { Collection } from '../../../core/shared/collection.model';
+import { searchResultFor } from '../../../+search-page/search-service/search-result-element-decorator';
+@searchResultFor(Collection)
export class CollectionSearchResult extends SearchResult {
}
diff --git a/src/app/shared/object-collection/shared/community-search-result.model.ts b/src/app/shared/object-collection/shared/community-search-result.model.ts
index 79ea34b6cd..8e371c182c 100644
--- a/src/app/shared/object-collection/shared/community-search-result.model.ts
+++ b/src/app/shared/object-collection/shared/community-search-result.model.ts
@@ -1,5 +1,7 @@
-import { SearchResult } from '../../../+search-page/search-result.model';
import { Community } from '../../../core/shared/community.model';
+import { SearchResult } from '../../../+search-page/search-result.model';
+import { searchResultFor } from '../../../+search-page/search-service/search-result-element-decorator';
+@searchResultFor(Community)
export class CommunitySearchResult extends SearchResult {
}
diff --git a/src/app/shared/object-collection/shared/item-search-result.model.ts b/src/app/shared/object-collection/shared/item-search-result.model.ts
index d9af3539a0..f20b3db1e3 100644
--- a/src/app/shared/object-collection/shared/item-search-result.model.ts
+++ b/src/app/shared/object-collection/shared/item-search-result.model.ts
@@ -1,5 +1,7 @@
import { SearchResult } from '../../../+search-page/search-result.model';
import { Item } from '../../../core/shared/item.model';
+import { searchResultFor } from '../../../+search-page/search-service/search-result-element-decorator';
+@searchResultFor(Item)
export class ItemSearchResult extends SearchResult- {
}
diff --git a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html
index b1287212a3..9fecb51b9a 100644
--- a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html
+++ b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html
@@ -1,13 +1,13 @@
-
-
-
+
+
+
{{object.name}}
{{object.shortDescription}}
diff --git a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html
index b6f4c5c5d9..31a9e8ad3d 100644
--- a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html
+++ b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html
@@ -1,14 +1,14 @@
-
-
-
+
+
+
{{object.name}}
{{object.shortDescription}}
diff --git a/src/app/shared/object-grid/item-grid-element/item-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-grid-element.component.html
index 328bfc3bc9..cc2f2efdb1 100644
--- a/src/app/shared/object-grid/item-grid-element/item-grid-element.component.html
+++ b/src/app/shared/object-grid/item-grid-element/item-grid-element.component.html
@@ -1,6 +1,6 @@
-
+
@@ -16,7 +16,7 @@
{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}
diff --git a/src/app/shared/object-grid/object-grid.component.html b/src/app/shared/object-grid/object-grid.component.html
index fcf3a42662..9d1f8f5ea2 100644
--- a/src/app/shared/object-grid/object-grid.component.html
+++ b/src/app/shared/object-grid/object-grid.component.html
@@ -10,12 +10,14 @@
(sortDirectionChange)="onSortDirectionChange($event)"
(sortFieldChange)="onSortFieldChange($event)"
(paginationChange)="onPaginationChange($event)">
-
-
+
+
diff --git a/src/app/shared/object-grid/object-grid.component.scss b/src/app/shared/object-grid/object-grid.component.scss
index 1b9418be48..ff78634863 100644
--- a/src/app/shared/object-grid/object-grid.component.scss
+++ b/src/app/shared/object-grid/object-grid.component.scss
@@ -1,24 +1,26 @@
@import '../../../styles/variables';
@import '../../../styles/mixins';
+$ds-wrapper-grid-spacing: $spacer/2;
+
ds-wrapper-grid-element ::ng-deep {
div.thumbnail > img {
height: $card-thumbnail-height;
width: 100%;
}
div.card {
- margin-bottom: $spacer;
+ margin-top: $ds-wrapper-grid-spacing;
+ margin-bottom: $ds-wrapper-grid-spacing;
}
}
.card-columns {
- @include media-breakpoint-only(lg) {
- column-count: 3;
+ margin-left: -$ds-wrapper-grid-spacing;
+ margin-right: -$ds-wrapper-grid-spacing;
+
+ .card-column {
+ padding-left: $ds-wrapper-grid-spacing;
+ padding-right: $ds-wrapper-grid-spacing;
}
- @include media-breakpoint-only(sm) {
- column-count: 2;
- }
- @include media-breakpoint-only(xs) {
- column-count: 1;
- }
-}
\ No newline at end of file
+}
+
diff --git a/src/app/shared/object-grid/object-grid.component.spec.ts b/src/app/shared/object-grid/object-grid.component.spec.ts
index e69de29bb2..2ced28718a 100644
--- a/src/app/shared/object-grid/object-grid.component.spec.ts
+++ b/src/app/shared/object-grid/object-grid.component.spec.ts
@@ -0,0 +1,224 @@
+import { cold, hot } from 'jasmine-marbles';
+import { map } from 'rxjs/operators';
+import { WidthCategory } from '../host-window.service';
+import { ObjectGridComponent } from './object-grid.component';
+
+describe('ObjectGridComponent', () => {
+ const testObjects = [
+ { one: 1 },
+ { two: 2 },
+ { three: 3 },
+ { four: 4 },
+ { five: 5 },
+ { six: 6 },
+ { seven: 7 },
+ { eight: 8 },
+ { nine: 9 },
+ { ten: 10 }
+ ];
+ const mockRD = {
+ payload: {
+ page: testObjects
+ }
+ } as any;
+
+ describe('the number of columns', () => {
+
+ it('should be 3 for xl screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.XL }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: 3 });
+
+ const result = comp.columns$.pipe(
+ map((columns) => columns.length)
+ );
+
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should be 3 for lg screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.LG }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: 3 });
+
+ const result = comp.columns$.pipe(
+ map((columns) => columns.length)
+ );
+
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should be 2 for md screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.MD }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: 2 });
+
+ const result = comp.columns$.pipe(
+ map((columns) => columns.length)
+ );
+
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should be 2 for sm screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.SM }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: 2 });
+
+ const result = comp.columns$.pipe(
+ map((columns) => columns.length)
+ );
+
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should be 1 for xs screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.XS }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: 1 });
+
+ const result = comp.columns$.pipe(
+ map((columns) => columns.length)
+ );
+
+ expect(result).toBeObservable(expected);
+ });
+
+ });
+
+ describe('The ordering of the content', () => {
+ it('should be left to right for XL screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.XL }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: [
+ [testObjects[0], testObjects[3], testObjects[6], testObjects[9]],
+ [testObjects[1], testObjects[4], testObjects[7]],
+ [testObjects[2], testObjects[5], testObjects[8]]
+ ] });
+
+ const result = comp.columns$;
+
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should be left to right for LG screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.LG }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: [
+ [testObjects[0], testObjects[3], testObjects[6], testObjects[9]],
+ [testObjects[1], testObjects[4], testObjects[7]],
+ [testObjects[2], testObjects[5], testObjects[8]]
+ ] });
+
+ const result = comp.columns$;
+
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should be left to right for MD screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.MD }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: [
+ [testObjects[0], testObjects[2], testObjects[4], testObjects[6], testObjects[8]],
+ [testObjects[1], testObjects[3], testObjects[5], testObjects[7], testObjects[9]],
+ ] });
+
+ const result = comp.columns$;
+
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should be left to right for SM screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.SM }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: [
+ [testObjects[0], testObjects[2], testObjects[4], testObjects[6], testObjects[8]],
+ [testObjects[1], testObjects[3], testObjects[5], testObjects[7], testObjects[9]],
+ ] });
+
+ const result = comp.columns$;
+
+ expect(result).toBeObservable(expected);
+ });
+
+ it('should be top to bottom for XS screens', () => {
+ const hostWindowService = {
+ widthCategory: hot('a', { a: WidthCategory.XS }),
+ } as any;
+ const comp = new ObjectGridComponent(hostWindowService);
+
+ (comp as any)._objects$ = hot('b', { b: mockRD });
+
+ comp.ngOnInit();
+
+ const expected = cold('c', { c: [ testObjects ] });
+
+ const result = comp.columns$;
+
+ expect(result).toBeObservable(expected);
+ });
+ });
+});
diff --git a/src/app/shared/object-grid/object-grid.component.ts b/src/app/shared/object-grid/object-grid.component.ts
index a8f8ebb183..4c4add9b06 100644
--- a/src/app/shared/object-grid/object-grid.component.ts
+++ b/src/app/shared/object-grid/object-grid.component.ts
@@ -2,16 +2,21 @@ import {
ChangeDetectionStrategy,
Component,
EventEmitter,
- Input,
+ Input, OnInit,
Output,
ViewEncapsulation
} from '@angular/core';
+import { BehaviorSubject } from 'rxjs/BehaviorSubject';
+import { Observable } from 'rxjs/Observable';
+import { distinctUntilChanged, map } from 'rxjs/operators';
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { PaginatedList } from '../../core/data/paginated-list';
import { RemoteData } from '../../core/data/remote-data';
import { fadeIn } from '../animations/fade';
+import { hasNoValue, hasValue } from '../empty.util';
+import { HostWindowService, WidthCategory } from '../host-window.service';
import { ListableObject } from '../object-collection/shared/listable-object.model';
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
@@ -25,18 +30,18 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o
animations: [fadeIn]
})
-export class ObjectGridComponent {
+export class ObjectGridComponent implements OnInit {
@Input() config: PaginationComponentOptions;
@Input() sortConfig: SortOptions;
@Input() hideGear = false;
@Input() hidePagerWhenSinglePage = true;
- private _objects: RemoteData
>;
+ private _objects$: BehaviorSubject>>;
@Input() set objects(objects: RemoteData>) {
- this._objects = objects;
+ this._objects$.next(objects);
}
get objects() {
- return this._objects;
+ return this._objects$.getValue();
}
/**
@@ -77,6 +82,56 @@ export class ObjectGridComponent {
*/
@Output() sortFieldChange: EventEmitter = new EventEmitter();
data: any = {};
+ columns$: Observable
+
+ constructor(private hostWindow: HostWindowService) {
+ this._objects$ = new BehaviorSubject(undefined);
+ }
+
+ ngOnInit(): void {
+ const nbColumns$ = this.hostWindow.widthCategory.pipe(
+ map((widthCat: WidthCategory) => {
+ switch (widthCat) {
+ case WidthCategory.XL:
+ case WidthCategory.LG: {
+ return 3;
+ }
+ case WidthCategory.MD:
+ case WidthCategory.SM: {
+ return 2;
+ }
+ default: {
+ return 1;
+ }
+ }
+ }),
+ distinctUntilChanged()
+ ).startWith(3);
+
+ this.columns$ = Observable.combineLatest(
+ nbColumns$,
+ this._objects$,
+ (nbColumns, objects) => {
+ if (hasValue(objects) && hasValue(objects.payload) && hasValue(objects.payload.page)) {
+ const page = objects.payload.page;
+
+ const result = [];
+
+ page.forEach((obj: ListableObject, i: number) => {
+ const colNb = i % nbColumns;
+ let col = result[colNb];
+ if (hasNoValue(col)) {
+ col = [];
+ }
+ result[colNb] = [...col, obj];
+ });
+ return result;
+ } else {
+ return [];
+ }
+ });
+ }
+
onPageChange(event) {
this.pageChange.emit(event);
}
diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html
index d6b1bfb5f4..91548d945d 100644
--- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html
+++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html
@@ -1,13 +1,13 @@
-
-
-
+
+
+
{{dso.name}}
{{dso.shortDescription}}
diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.ts
index e5747a1243..7f8a3bb9fd 100644
--- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.ts
+++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.ts
@@ -1,11 +1,10 @@
import { Component } from '@angular/core';
import { renderElementsFor} from '../../../object-collection/shared/dso-element-decorator';
-
-import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
import { Collection } from '../../../../core/shared/collection.model';
import { ViewMode } from '../../../../+search-page/search-options.model';
+import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
@Component({
selector: 'ds-collection-search-result-grid-element',
diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html
index 8ff6874bff..95094a6fa1 100644
--- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html
+++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html
@@ -1,14 +1,14 @@
-
-
-
+
+
+
{{dso.name}}
{{dso.shortDescription}}
diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.ts b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.ts
index d08286ff2e..7c34207f5e 100644
--- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.ts
+++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.ts
@@ -1,5 +1,4 @@
import { Component } from '@angular/core';
-
import { Community } from '../../../../core/shared/community.model';
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html
index b185caa18f..1cf14587ad 100644
--- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html
+++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html
@@ -1,5 +1,5 @@
-
-
\ No newline at end of file
+
diff --git a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts
index 9a462c124e..746e700f05 100644
--- a/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts
+++ b/src/app/shared/object-list/search-result-list-element/collection-search-result/collection-search-result-list-element.component.ts
@@ -1,6 +1,7 @@
import { Component } from '@angular/core';
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
+
import { SearchResultListElementComponent } from '../search-result-list-element.component';
import { Collection } from '../../../../core/shared/collection.model';
import { ViewMode } from '../../../../+search-page/search-options.model';
diff --git a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts
index 5664e840e3..2ca89fc9c5 100644
--- a/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts
+++ b/src/app/shared/object-list/search-result-list-element/community-search-result/community-search-result-list-element.component.ts
@@ -1,10 +1,11 @@
import { Component } from '@angular/core';
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
-import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
+
import { SearchResultListElementComponent } from '../search-result-list-element.component';
import { Community } from '../../../../core/shared/community.model';
import { ViewMode } from '../../../../+search-page/search-options.model';
+import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
@Component({
selector: 'ds-community-search-result-list-element',
diff --git a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts
index 9675a58a1e..fd821997ad 100644
--- a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts
+++ b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts
@@ -1,12 +1,12 @@
import { Component, Inject } from '@angular/core';
+import { Observable } from 'rxjs/Observable';
import { SearchResult } from '../../../+search-page/search-result.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { Metadatum } from '../../../core/shared/metadatum.model';
-import { isEmpty, hasNoValue } from '../../empty.util';
+import { hasNoValue, isEmpty } from '../../empty.util';
import { ListableObject } from '../../object-collection/shared/listable-object.model';
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
-import { Observable } from 'rxjs/Observable';
import { TruncatableService } from '../../truncatable/truncatable.service';
@Component({
@@ -46,7 +46,7 @@ export class SearchResultListElementComponent, K exten
this.object.hitHighlights.some(
(md: Metadatum) => {
if (key === md.key) {
- result = md.value;
+ result = md.value;
return true;
}
}
diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html
index 062209f4bb..e974bb6eb0 100644
--- a/src/app/shared/pagination/pagination.component.html
+++ b/src/app/shared/pagination/pagination.component.html
@@ -12,7 +12,7 @@
-
+
diff --git a/src/app/shared/pagination/pagination.component.spec.ts b/src/app/shared/pagination/pagination.component.spec.ts
index a4b9e5fcea..48767cf582 100644
--- a/src/app/shared/pagination/pagination.component.spec.ts
+++ b/src/app/shared/pagination/pagination.component.spec.ts
@@ -41,7 +41,7 @@ import { MockRouter } from '../mocks/mock-router';
import { HostWindowService } from '../host-window.service';
import { EnumKeysPipe } from '../utils/enum-keys-pipe';
-import { SortOptions } from '../../core/cache/models/sort-options.model';
+import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { GLOBAL_CONFIG, ENV_CONFIG } from '../../../config';
@@ -271,7 +271,7 @@ describe('Pagination component', () => {
changePage(testFixture, 3);
tick();
- expect(routerStub.navigate).toHaveBeenCalledWith([], { queryParams: { pageId: 'test', page: 3, pageSize: 10, sortDirection: 0, sortField: 'name' } });
+ expect(routerStub.navigate).toHaveBeenCalledWith([], { queryParams: { pageId: 'test', page: 3, pageSize: 10, sortDirection: 'ASC', sortField: 'dc.title' }, queryParamsHandling: 'merge' });
}));
@@ -282,7 +282,7 @@ describe('Pagination component', () => {
changePageSize(testFixture, '20');
tick();
- expect(routerStub.navigate).toHaveBeenCalledWith([], { queryParams: { pageId: 'test', page: 1, pageSize: 20, sortDirection: 0, sortField: 'name' } });
+ expect(routerStub.navigate).toHaveBeenCalledWith([], { queryParams: { pageId: 'test', page: 1, pageSize: 20, sortDirection: 'ASC', sortField: 'dc.title' } , queryParamsHandling: 'merge' });
}));
it('should set correct values', fakeAsync(() => {
@@ -349,7 +349,7 @@ class TestComponent {
collection: string[] = [];
collectionSize: number;
paginationOptions = new PaginationComponentOptions();
- sortOptions = new SortOptions();
+ sortOptions = new SortOptions('dc.title', SortDirection.ASC);
constructor() {
this.collection = Array.from(new Array(100), (x, i) => `item ${i + 1}`);
diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts
index e2af2fd06c..faaf20ec79 100644
--- a/src/app/shared/pagination/pagination.component.ts
+++ b/src/app/shared/pagination/pagination.component.ts
@@ -144,7 +144,7 @@ export class PaginationComponent implements OnDestroy, OnInit {
/**
* Direction in which to sort: ascending or descending
*/
- public sortDirection: SortDirection = SortDirection.Ascending;
+ public sortDirection: SortDirection = SortDirection.ASC;
/**
* Name of the field that's used to sort by
@@ -336,7 +336,8 @@ export class PaginationComponent implements OnDestroy, OnInit {
*/
private updateRoute(params: {}) {
this.router.navigate([], {
- queryParams: Object.assign({}, this.currentQueryParams, params)
+ queryParams: Object.assign({}, this.currentQueryParams, params),
+ queryParamsHandling: 'merge'
});
}
@@ -399,8 +400,8 @@ export class PaginationComponent implements OnDestroy, OnInit {
}
const sortDirection = this.currentQueryParams.sortDirection;
- if (this.sortDirection !== +sortDirection) {
- this.setSortDirection(+sortDirection);
+ if (this.sortDirection !== sortDirection) {
+ this.setSortDirection(sortDirection);
}
const sortField = this.currentQueryParams.sortField;
diff --git a/src/app/shared/route.service.spec.ts b/src/app/shared/route.service.spec.ts
index 10bd147e1d..b134771b3e 100644
--- a/src/app/shared/route.service.spec.ts
+++ b/src/app/shared/route.service.spec.ts
@@ -67,50 +67,36 @@ describe('RouteService', () => {
});
});
- describe('addQueryParameterValue', () => {
- it('should return a list of values that contains the added value when a new value is added and the parameter did not exist yet', () => {
- service.addQueryParameterValue(nonExistingParamName, nonExistingParamValue).subscribe((params) => {
- expect(params[nonExistingParamName]).toContain(nonExistingParamValue);
+ describe('getQueryParameterValues', () => {
+ it('should return a list of values when the parameter exists', () => {
+ service.getQueryParameterValues(paramName2).subscribe((params) => {
+ expect(params).toEqual([paramValue2a, paramValue2b]);
});
});
- it('should return a list of values that contains the existing values and the added value when a new value is added and the parameter already has values', () => {
- service.addQueryParameterValue(paramName1, nonExistingParamValue).subscribe((params) => {
- const values = params[paramName1];
- expect(values).toContain(paramValue1);
- expect(values).toContain(nonExistingParamValue);
+
+ it('should return an empty array when the parameter does not exists', () => {
+ service.getQueryParameterValues(nonExistingParamName).subscribe((params) => {
+ expect(params).toEqual([]);
});
});
});
- describe('removeQueryParameterValue', () => {
- it('should return a list of values that does not contain the removed value when the parameter value exists', () => {
- service.removeQueryParameterValue(paramName2, paramValue2a).subscribe((params) => {
- const values = params[paramName2];
- expect(values).toContain(paramValue2b);
- expect(values).not.toContain(paramValue2a);
+ describe('getQueryParameterValue', () => {
+ it('should return a single value when the parameter exists', () => {
+ service.getQueryParameterValue(paramName1).subscribe((params) => {
+ expect(params).toEqual(paramValue1);
});
});
- it('should return a list of values that does contain all existing values when the removed parameter does not exist', () => {
- service.removeQueryParameterValue(paramName2, nonExistingParamValue).subscribe((params) => {
- const values = params[paramName2];
- expect(values).toContain(paramValue2a);
- expect(values).toContain(paramValue2b);
+ it('should return only the first value when the parameter exists', () => {
+ service.getQueryParameterValue(paramName2).subscribe((params) => {
+ expect(params).toEqual(paramValue2a);
});
});
- });
- describe('removeQueryParameter', () => {
- it('should return a list of values that does not contain any values for the parameter anymore when the parameter exists', () => {
- service.removeQueryParameter(paramName2).subscribe((params) => {
- const values = params[paramName2];
- expect(values).toEqual({});
- });
- });
- it('should return a list of values that does not contain any values for the parameter when the parameter does not exist', () => {
- service.removeQueryParameter(nonExistingParamName).subscribe((params) => {
- const values = params[nonExistingParamName];
- expect(values).toEqual({});
+ it('should return undefined when the parameter exists', () => {
+ service.getQueryParameterValue(nonExistingParamName).subscribe((params) => {
+ expect(params).toBeNull();
});
});
});
diff --git a/src/app/shared/route.service.ts b/src/app/shared/route.service.ts
index f24fa0d00d..9c2b64ede1 100644
--- a/src/app/shared/route.service.ts
+++ b/src/app/shared/route.service.ts
@@ -1,6 +1,9 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
-import { ActivatedRoute, convertToParamMap, Params, } from '@angular/router';
+import {
+ ActivatedRoute, convertToParamMap, NavigationExtras, Params,
+ Router,
+} from '@angular/router';
import { isNotEmpty } from './empty.util';
@Injectable()
@@ -10,46 +13,31 @@ export class RouteService {
}
getQueryParameterValues(paramName: string): Observable