Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div *ngIf="subCollectionsRD?.hasSucceeded" @fadeIn>
<h2>{{'community.sub-collection-list.head' | translate}}</h2>
<ul>
<li *ngFor="let collection of subCollectionsRD?.payload">
<li *ngFor="let collection of subCollectionsRD?.payload.page">
<p>
<span class="lead"><a [routerLink]="['/collections', collection.id]">{{collection.name}}</a></span><br>
<span class="text-muted">{{collection.shortDescription}}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Collection } from '../../core/shared/collection.model';
import { Community } from '../../core/shared/community.model';

import { fadeIn } from '../../shared/animations/fade';
import { PaginatedList } from '../../core/data/paginated-list';

@Component({
selector: 'ds-community-page-sub-collection-list',
Expand All @@ -15,7 +16,7 @@ import { fadeIn } from '../../shared/animations/fade';
})
export class CommunityPageSubCollectionListComponent implements OnInit {
@Input() community: Community;
subCollectionsRDObs: Observable<RemoteData<Collection[]>>;
subCollectionsRDObs: Observable<RemoteData<PaginatedList<Collection>>>;

ngOnInit(): void {
this.subCollectionsRDObs = this.community.collections;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<ds-metadata-field-wrapper [label]="label | translate">
<ds-metadata-field-wrapper *ngIf="hasSucceeded() | async" [label]="label | translate">
<div class="collections">
<a *ngFor="let collection of (collections | async); let last=last;" [routerLink]="['/collections', collection.id]">
<span>{{collection?.name}}</span><span *ngIf="!last" [innerHTML]="separator"></span>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { CollectionsComponent } from './collections.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { Collection } from '../../../core/shared/collection.model';
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
import { getMockRemoteDataBuildService } from '../../../shared/mocks/mock-remote-data-build.service';
import { Item } from '../../../core/shared/item.model';
import { Observable } from 'rxjs/Observable';
import { RemoteData } from '../../../core/data/remote-data';
import { TranslateModule } from '@ngx-translate/core';

let collectionsComponent: CollectionsComponent;
let fixture: ComponentFixture<CollectionsComponent>;

const mockCollection1: Collection = Object.assign(new Collection(), {
metadata: [
{
key: 'dc.description.abstract',
language: 'en_US',
value: 'Short description'
}]
});

const succeededMockItem: Item = Object.assign(new Item(), {owningCollection: Observable.of(new RemoteData(false, false, true, null, mockCollection1))});
const failedMockItem: Item = Object.assign(new Item(), {owningCollection: Observable.of(new RemoteData(false, false, false, null, mockCollection1))});

describe('CollectionsComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [ CollectionsComponent ],
providers: [
{ provide: RemoteDataBuildService, useValue: getMockRemoteDataBuildService()}
],

schemas: [ NO_ERRORS_SCHEMA ]
}).overrideComponent(CollectionsComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));

beforeEach(async(() => {
fixture = TestBed.createComponent(CollectionsComponent);
collectionsComponent = fixture.componentInstance;
collectionsComponent.label = 'test.test';
collectionsComponent.separator = '<br/>';

}));

describe('When the requested item request has succeeded', () => {
beforeEach(() => {
collectionsComponent.item = succeededMockItem;
fixture.detectChanges();
});

it('should show the collection', () => {
const collectionField = fixture.debugElement.query(By.css('ds-metadata-field-wrapper div.collections'));
expect(collectionField).not.toBeNull();
});
});

describe('When the requested item request has succeeded', () => {
beforeEach(() => {
collectionsComponent.item = failedMockItem;
fixture.detectChanges();
});

it('should not show the collection', () => {
const collectionField = fixture.debugElement.query(By.css('ds-metadata-field-wrapper div.collections'));
expect(collectionField).toBeNull();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,8 @@ export class CollectionsComponent implements OnInit {
this.collections = this.item.owner.map((rd: RemoteData<Collection>) => [rd.payload]);
}

hasSucceeded() {
return this.item.owner.map((rd: RemoteData<Collection>) => rd.hasSucceeded);
}

}
1 change: 0 additions & 1 deletion src/app/+search-page/paginated-search-options.model.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { SortOptions } from '../core/cache/models/sort-options.model';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { isNotEmpty } from '../shared/empty.util';
import { URLCombiner } from '../core/url-combiner/url-combiner';
import { SearchOptions } from './search-options.model';

export class PaginatedSearchOptions extends SearchOptions {
Expand Down
39 changes: 24 additions & 15 deletions src/app/core/cache/builders/remote-data-build.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { RemoteDataError } from '../../data/remote-data-error';
import { GetRequest } from '../../data/request.models';
import { RequestEntry } from '../../data/request.reducer';
import { RequestService } from '../../data/request.service';

import { NormalizedObject } from '../models/normalized-object.model';
import { ObjectCacheService } from '../object-cache.service';
import { DSOSuccessResponse, ErrorResponse } from '../response-cache.models';
import { ResponseCacheEntry } from '../response-cache.reducer';
import { ResponseCacheService } from '../response-cache.service';
import { getMapsTo, getRelationMetadata, getRelationships } from './build-decorators';
import { PageInfo } from '../../shared/page-info.model';
import {
getRequestFromSelflink,
getResourceLinksFromResponse,
Expand Down Expand Up @@ -96,7 +98,6 @@ export class RemoteDataBuildService {
error = new RemoteDataError(resEntry.response.statusCode, errorMessage);
}
}

return new RemoteData(
requestPending,
responsePending,
Expand All @@ -107,7 +108,7 @@ export class RemoteDataBuildService {
});
}

buildList<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<TDomain[] | PaginatedList<TDomain>>> {
buildList<TNormalized extends NormalizedObject, TDomain>(href$: string | Observable<string>): Observable<RemoteData<PaginatedList<TDomain>>> {
if (typeof href$ === 'string') {
href$ = Observable.of(href$);
}
Expand Down Expand Up @@ -144,11 +145,7 @@ export class RemoteDataBuildService {
);

const payload$ = Observable.combineLatest(tDomainList$, pageInfo$, (tDomainList, pageInfo) => {
if (hasValue(pageInfo)) {
return new PaginatedList(pageInfo, tDomainList);
} else {
return tDomainList;
}
return new PaginatedList(pageInfo, tDomainList);
});

return this.toRemoteDataObservable(requestEntry$, responseCache$, payload$);
Expand All @@ -160,35 +157,43 @@ export class RemoteDataBuildService {
const relationships = getRelationships(normalized.constructor) || [];

relationships.forEach((relationship: string) => {
let result;
if (hasValue(normalized[relationship])) {
const { resourceType, isList } = getRelationMetadata(normalized, relationship);
if (Array.isArray(normalized[relationship])) {
normalized[relationship].forEach((href: string) => {
const objectList = normalized[relationship].page || normalized[relationship];
if (typeof objectList !== 'string') {
objectList.forEach((href: string) => {
this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), href))
});

const rdArr = [];
normalized[relationship].forEach((href: string) => {
objectList.forEach((href: string) => {
rdArr.push(this.buildSingle(href));
});

if (isList) {
links[relationship] = this.aggregate(rdArr);
result = this.aggregate(rdArr);
} else if (rdArr.length === 1) {
links[relationship] = rdArr[0];
result = rdArr[0];
}
} else {
this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), normalized[relationship]));
this.requestService.configure(new GetRequest(this.requestService.generateRequestId(), objectList));

// The rest API can return a single URL to represent a list of resources (e.g. /items/:id/bitstreams)
// 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]);
result = this.buildList(objectList);
} else {
links[relationship] = this.buildSingle(normalized[relationship]);
result = this.buildSingle(objectList);
}
}

if (hasValue(normalized[relationship].page)) {
links[relationship] = this.aggregatePaginatedList(result, normalized[relationship].pageInfo);
} else {
links[relationship] = result;
}
}
});

Expand Down Expand Up @@ -249,4 +254,8 @@ export class RemoteDataBuildService {
})
}

aggregatePaginatedList<T>(input: Observable<RemoteData<T[]>>, pageInfo: PageInfo): Observable<RemoteData<PaginatedList<T>>> {
return input.map((rd) => Object.assign(rd, {payload: new PaginatedList(pageInfo, rd.payload)}));
}

}
34 changes: 34 additions & 0 deletions src/app/core/cache/it-to-uuid-serializer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { IDToUUIDSerializer } from './it-to-uuid-serializer';

describe('IDToUUIDSerializer', () => {
let serializer: IDToUUIDSerializer;
const prefix = 'test-prefix';

beforeEach(() => {
serializer = new IDToUUIDSerializer(prefix);
});

describe('Serialize', () => {
it('should return undefined', () => {
expect(serializer.Serialize('some-uuid')).toBeUndefined()
});
});

describe('Deserialize', () => {
describe('when ID is defined', () => {
it('should prepend the prefix to the ID', () => {
const id = 'some-id';
expect(serializer.Deserialize(id)).toBe(`${prefix}-${id}`);
});
});

describe('when ID is null or undefined', () => {
it('should return null or undefined', () => {
expect(serializer.Deserialize(null)).toBeNull();
expect(serializer.Deserialize(undefined)).toBeUndefined();
});
});

});

});
19 changes: 19 additions & 0 deletions src/app/core/cache/it-to-uuid-serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { hasValue } from '../../shared/empty.util';

export class IDToUUIDSerializer {
constructor(private prefix: string) {
}

Serialize(uuid: string): any {
return undefined; // ui-only uuid doesn't need to be sent back to the server
}

Deserialize(id: string): string {
if (hasValue(id)) {
return `${this.prefix}-${id}`;
} else {
return id;
}

}
}
35 changes: 35 additions & 0 deletions src/app/core/cache/models/normalized-bitstream-format.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { BitstreamFormat } from '../../shared/bitstream-format.model';

import { mapsTo } from '../builders/build-decorators';
import { IDToUUIDSerializer } from '../it-to-uuid-serializer';
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;

@autoserialize
id: string;

@autoserializeAs(new IDToUUIDSerializer('bitstream-format'), 'id')
uuid: string;
}
2 changes: 1 addition & 1 deletion src/app/core/cache/models/normalized-collection.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { autoserialize, inheritSerialization, autoserializeAs } from 'cerialize';
import { autoserialize, inheritSerialization } from 'cerialize';

import { NormalizedDSpaceObject } from './normalized-dspace-object.model';
import { Collection } from '../../shared/collection.model';
Expand Down
1 change: 1 addition & 0 deletions src/app/core/cache/models/normalized-item.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export class NormalizedItem extends NormalizedDSpaceObject {
/**
* The Collection that owns this Item
*/
@autoserialize
@relationship(ResourceType.Collection, false)
owningCollection: string;

Expand Down
8 changes: 8 additions & 0 deletions src/app/core/cache/models/normalized-object-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ 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';
import { NormalizedResourcePolicy } from './normalized-resource-policy.model';

export class NormalizedObjectFactory {
public static getConstructor(type: ResourceType): GenericConstructor<NormalizedObject> {
Expand All @@ -25,6 +27,12 @@ export class NormalizedObjectFactory {
case ResourceType.Community: {
return NormalizedCommunity
}
case ResourceType.BitstreamFormat: {
return NormalizedBitstreamFormat
}
case ResourceType.ResourcePolicy: {
return NormalizedResourcePolicy
}
default: {
return undefined;
}
Expand Down
23 changes: 23 additions & 0 deletions src/app/core/cache/models/normalized-resource-policy.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { autoserialize, autoserializeAs, inheritSerialization } from 'cerialize';
import { ResourcePolicy } from '../../shared/resource-policy.model';

import { mapsTo } from '../builders/build-decorators';
import { IDToUUIDSerializer } from '../it-to-uuid-serializer';
import { NormalizedObject } from './normalized-object.model';

@mapsTo(ResourcePolicy)
@inheritSerialization(NormalizedObject)
export class NormalizedResourcePolicy extends NormalizedObject {

@autoserialize
name: string;

@autoserializeAs(String, 'groupUUID')
group: string;

@autoserialize
id: string;

@autoserializeAs(new IDToUUIDSerializer('resource-policy'), 'id')
uuid: string;
}
1 change: 0 additions & 1 deletion src/app/core/cache/response-cache.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ describe('ResponseCacheService', () => {

let testObj: ResponseCacheEntry;
service.get(keys[1]).first().subscribe((entry) => {
console.log(entry);
testObj = entry;
});
expect(testObj.key).toEqual(keys[1]);
Expand Down
Loading