mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
refactor done, todo fix tests
This commit is contained in:
@@ -91,7 +91,8 @@
|
||||
"sub-collections": "Loading sub-collections...",
|
||||
"items": "Loading items...",
|
||||
"item": "Loading item...",
|
||||
"objects": "Loading..."
|
||||
"objects": "Loading...",
|
||||
"search-results": "Loading search results..."
|
||||
},
|
||||
"error": {
|
||||
"default": "Error",
|
||||
@@ -101,6 +102,7 @@
|
||||
"sub-collections": "Error fetching sub-collections",
|
||||
"items": "Error fetching items",
|
||||
"item": "Error fetching item",
|
||||
"objects": "Error fetching objects"
|
||||
"objects": "Error fetching objects",
|
||||
"search-results": "Error fetching search results"
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
<div class="collection-page">
|
||||
<div *ngIf="collectionData.hasSucceeded | async" @fadeInOut>
|
||||
<div *ngIf="collectionData.payload | async; let collectionPayload">
|
||||
<div *ngIf="(collectionData | async)?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="(collectionData | async)?.payload; let collectionPayload">
|
||||
<!-- Collection Name -->
|
||||
<ds-comcol-page-header
|
||||
[name]="collectionPayload.name">
|
||||
</ds-comcol-page-header>
|
||||
<!-- Collection logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoData"
|
||||
[logo]="logoData.payload | async"
|
||||
[logo]="(logoData | async)?.payload"
|
||||
[alternateText]="'Collection Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
<!-- Introductionary text -->
|
||||
@@ -33,18 +33,19 @@
|
||||
</ds-comcol-page-content>
|
||||
</div>
|
||||
</div>
|
||||
<ds-error *ngIf="collectionData.hasFailed | async" message="{{'error.collection' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="collectionData.isLoading | async" message="{{'loading.collection' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="(collectionData | async)?.hasFailed" message="{{'error.collection' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="(collectionData | async)?.isLoading" message="{{'loading.collection' | translate}}"></ds-loading>
|
||||
<br>
|
||||
<div *ngIf="itemData.hasSucceeded | async" @fadeIn>
|
||||
<div *ngIf="itemData?.hasSucceeded" @fadeIn>
|
||||
<h2>{{'collection.page.browse.recent.head' | translate}}</h2>
|
||||
<ds-object-list
|
||||
[config]="paginationConfig"
|
||||
[sortConfig]="sortConfig"
|
||||
[objects]="itemData"
|
||||
[hideGear]="false">
|
||||
[hideGear]="false"
|
||||
(paginationChange)="onPaginationChange($event)">
|
||||
</ds-object-list>
|
||||
</div>
|
||||
<ds-error *ngIf="itemData.hasFailed | async" message="{{'error.items' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="itemData.isLoading | async" message="{{'loading.items' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="itemData?.hasFailed" message="{{'error.items' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="itemData?.isLoading" message="{{'loading.items' | translate}}"></ds-loading>
|
||||
</div>
|
||||
|
@@ -7,13 +7,13 @@ import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||
import { ItemDataService } from '../core/data/item-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
|
||||
import { MetadataService } from '../core/metadata/metadata.service';
|
||||
import { Bitstream } from '../core/shared/bitstream.model';
|
||||
|
||||
import { Collection } from '../core/shared/collection.model';
|
||||
import { Item } from '../core/shared/item.model';
|
||||
|
||||
import { MetadataService } from '../core/metadata/metadata.service';
|
||||
|
||||
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
@@ -29,9 +29,9 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp
|
||||
]
|
||||
})
|
||||
export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
collectionData: RemoteData<Collection>;
|
||||
collectionData: Observable<RemoteData<Collection>>;
|
||||
itemData: RemoteData<Item[]>;
|
||||
logoData: RemoteData<Bitstream>;
|
||||
logoData: Observable<RemoteData<Bitstream>>;
|
||||
paginationConfig: PaginationComponentOptions;
|
||||
sortConfig: SortOptions;
|
||||
private subs: Subscription[] = [];
|
||||
@@ -62,7 +62,10 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
this.collectionId = params.id;
|
||||
this.collectionData = this.collectionDataService.findById(this.collectionId);
|
||||
this.metadata.processRemoteData(this.collectionData);
|
||||
this.subs.push(this.collectionData.payload.subscribe((collection) => this.logoData = collection.logo));
|
||||
this.subs.push(this.collectionData
|
||||
.map((rd: RemoteData<Collection>) => rd.payload)
|
||||
.filter((collection: Collection) => hasValue(collection))
|
||||
.subscribe((collection: Collection) => this.logoData = collection.logo));
|
||||
|
||||
const page = +params.page || this.paginationConfig.currentPage;
|
||||
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
|
||||
@@ -84,12 +87,14 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
updatePage(searchOptions) {
|
||||
this.itemData = this.itemDataService.findAll({
|
||||
this.subs.push(this.itemDataService.findAll({
|
||||
scopeID: this.collectionId,
|
||||
currentPage: searchOptions.pagination.currentPage,
|
||||
elementsPerPage: searchOptions.pagination.pageSize,
|
||||
sort: searchOptions.sort
|
||||
});
|
||||
}).subscribe((rd: RemoteData<Item[]>) => {
|
||||
this.itemData = rd;
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -99,4 +104,17 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
||||
isNotEmpty(object: any) {
|
||||
return isNotEmpty(object);
|
||||
}
|
||||
|
||||
onPaginationChange(event) {
|
||||
this.updatePage({
|
||||
pagination: {
|
||||
currentPage: event.page,
|
||||
pageSize: event.pageSize
|
||||
},
|
||||
sort: {
|
||||
field: event.sortField,
|
||||
direction: event.sortDirection
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<div class="community-page" *ngIf="communityData.hasSucceeded | async" @fadeInOut>
|
||||
<div *ngIf="communityData.payload | async; let communityPayload">
|
||||
<div class="community-page" *ngIf="(communityData | async)?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="(communityData | async)?.payload; let communityPayload">
|
||||
<!-- Community name -->
|
||||
<ds-comcol-page-header [name]="communityPayload.name"></ds-comcol-page-header>
|
||||
<!-- Community logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoData"
|
||||
[logo]="logoData.payload | async"
|
||||
[logo]="(logoData | async)?.payload"
|
||||
[alternateText]="'Community Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
<!-- Introductionary text -->
|
||||
@@ -26,5 +26,5 @@
|
||||
<ds-community-page-sub-collection-list></ds-community-page-sub-collection-list>
|
||||
</div>
|
||||
</div>
|
||||
<ds-error *ngIf="communityData.hasFailed | async" message="{{'error.community' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="communityData.isLoading | async" message="{{'loading.community' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="(communityData | async)?.hasFailed" message="{{'error.community' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="(communityData | async)?.isLoading" message="{{'loading.community' | translate}}"></ds-loading>
|
||||
|
@@ -12,6 +12,7 @@ import { MetadataService } from '../core/metadata/metadata.service';
|
||||
|
||||
import { fadeInOut } from '../shared/animations/fade';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-page',
|
||||
@@ -21,8 +22,8 @@ import { hasValue } from '../shared/empty.util';
|
||||
animations: [fadeInOut]
|
||||
})
|
||||
export class CommunityPageComponent implements OnInit, OnDestroy {
|
||||
communityData: RemoteData<Community>;
|
||||
logoData: RemoteData<Bitstream>;
|
||||
communityData: Observable<RemoteData<Community>>;
|
||||
logoData: Observable<RemoteData<Bitstream>>;
|
||||
private subs: Subscription[] = [];
|
||||
|
||||
constructor(
|
||||
@@ -37,7 +38,10 @@ export class CommunityPageComponent implements OnInit, OnDestroy {
|
||||
this.route.params.subscribe((params: Params) => {
|
||||
this.communityData = this.communityDataService.findById(params.id);
|
||||
this.metadata.processRemoteData(this.communityData);
|
||||
this.subs.push(this.communityData.payload.subscribe((community) => this.logoData = community.logo));
|
||||
this.subs.push(this.communityData
|
||||
.map((rd: RemoteData<Community>) => rd.payload)
|
||||
.filter((community: Community) => hasValue(community))
|
||||
.subscribe((community: Community) => this.logoData = community.logo));
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<div *ngIf="subCollections.hasSucceeded | async" @fadeIn>
|
||||
<div *ngIf="(subCollections | async)?.hasSucceeded" @fadeIn>
|
||||
<h2>{{'community.sub-collection-list.head' | translate}}</h2>
|
||||
<ul>
|
||||
<li *ngFor="let collection of (subCollections.payload | async)">
|
||||
<li *ngFor="let collection of (subCollections | async)?.payload">
|
||||
<p>
|
||||
<span class="lead"><a [routerLink]="['/collections', collection.id]">{{collection.name}}</a></span><br>
|
||||
<span class="text-muted">{{collection.shortDescription}}</span>
|
||||
@@ -9,5 +9,5 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ds-error *ngIf="subCollections.hasFailed | async" message="{{'error.sub-collections' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="subCollections.isLoading | async" message="{{'loading.sub-collections' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="(subCollections | async)?.hasFailed" message="{{'error.sub-collections' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="(subCollections | async)?.isLoading" message="{{'loading.sub-collections' | translate}}"></ds-loading>
|
||||
|
@@ -5,6 +5,7 @@ import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
|
||||
import { fadeIn } from '../../shared/animations/fade';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-page-sub-collection-list',
|
||||
@@ -13,7 +14,7 @@ import { fadeIn } from '../../shared/animations/fade';
|
||||
animations:[fadeIn]
|
||||
})
|
||||
export class CommunityPageSubCollectionListComponent implements OnInit {
|
||||
subCollections: RemoteData<Collection[]>;
|
||||
subCollections: Observable<RemoteData<Collection[]>>;
|
||||
|
||||
constructor(private cds: CollectionDataService) {
|
||||
|
||||
|
@@ -1,13 +1,13 @@
|
||||
<div *ngIf="topLevelCommunities.hasSucceeded | async" @fadeInOut>
|
||||
<div *ngIf="(topLevelCommunities | async)?.hasSucceeded" @fadeInOut>
|
||||
<h2>{{'home.top-level-communities.head' | translate}}</h2>
|
||||
<p class="lead">{{'home.top-level-communities.help' | translate}}</p>
|
||||
<ds-object-list
|
||||
[config]="config"
|
||||
[sortConfig]="sortConfig"
|
||||
[objects]="topLevelCommunities"
|
||||
[objects]="topLevelCommunities | async"
|
||||
[hideGear]="true"
|
||||
(paginationChange)="updatePage($event)">
|
||||
</ds-object-list>
|
||||
</div>
|
||||
<ds-error *ngIf="topLevelCommunities.hasFailed | async" message="{{'error.top-level-communites' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="topLevelCommunities.isLoading | async" message="{{'loading.top-level-communities' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="(topLevelCommunities | async)?.hasFailed" message="{{'error.top-level-communites' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="(topLevelCommunities | async)?.isLoading" message="{{'loading.top-level-communities' | translate}}"></ds-loading>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
|
||||
@@ -16,7 +17,7 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
|
||||
animations: [fadeInOut]
|
||||
})
|
||||
export class TopLevelCommunityListComponent {
|
||||
topLevelCommunities: RemoteData<Community[]>;
|
||||
topLevelCommunities: Observable<RemoteData<Community[]>>;
|
||||
config: PaginationComponentOptions;
|
||||
sortConfig: SortOptions;
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import { Observable } from 'rxjs/Observable';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
|
||||
/**
|
||||
* This component renders the parent collections section of the item
|
||||
@@ -34,7 +35,7 @@ export class CollectionsComponent implements OnInit {
|
||||
// TODO: this should use parents, but the collections
|
||||
// for an Item aren't returned by the REST API yet,
|
||||
// only the owning collection
|
||||
this.collections = this.item.owner.payload.map((c) => [c]);
|
||||
this.collections = this.item.owner.map((rd: RemoteData<Collection>) => [rd.payload]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<div class="item-page" *ngIf="item.hasSucceeded | async" @fadeInOut>
|
||||
<div *ngIf="item.payload | async; let itemPayload">
|
||||
<div class="item-page" *ngIf="(item | async)?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="(item | async)?.payload; let itemPayload">
|
||||
<ds-item-page-title-field [item]="itemPayload"></ds-item-page-title-field>
|
||||
<div class="simple-view-link">
|
||||
<a class="btn btn-outline-primary col-4" [routerLink]="['/items/' + itemPayload.id]">
|
||||
@@ -19,5 +19,5 @@
|
||||
<ds-item-page-collections [item]="itemPayload"></ds-item-page-collections>
|
||||
</div>
|
||||
</div>
|
||||
<ds-error *ngIf="item.hasFailed | async" message="{{'error.item' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="item.isLoading | async" message="{{'loading.item' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="(item | async)?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="(item | async)?.isLoading" message="{{'loading.item' | translate}}"></ds-loading>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
@@ -13,6 +13,7 @@ import { Item } from '../../core/shared/item.model';
|
||||
import { MetadataService } from '../../core/metadata/metadata.service';
|
||||
|
||||
import { fadeInOut } from '../../shared/animations/fade';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
@@ -24,11 +25,12 @@ import { fadeInOut } from '../../shared/animations/fade';
|
||||
selector: 'ds-full-item-page',
|
||||
styleUrls: ['./full-item-page.component.scss'],
|
||||
templateUrl: './full-item-page.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [fadeInOut]
|
||||
})
|
||||
export class FullItemPageComponent extends ItemPageComponent implements OnInit {
|
||||
|
||||
item: RemoteData<Item>;
|
||||
item: Observable<RemoteData<Item>>;
|
||||
|
||||
metadata: Observable<Metadatum[]>;
|
||||
|
||||
@@ -43,7 +45,10 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit {
|
||||
|
||||
initialize(params) {
|
||||
super.initialize(params);
|
||||
this.metadata = this.item.payload.map((i) => i.metadata);
|
||||
this.metadata = this.item
|
||||
.map((rd: RemoteData<Item>) => rd.payload)
|
||||
.filter((item: Item) => hasValue(item))
|
||||
.map((item: Item) => item.metadata);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<div class="item-page" *ngIf="item.hasSucceeded | async" @fadeInOut>
|
||||
<div *ngIf="item.payload | async; let itemPayload">
|
||||
<div class="item-page" *ngIf="(item | async)?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="(item | async)?.payload; let itemPayload">
|
||||
<ds-item-page-title-field [item]="itemPayload"></ds-item-page-title-field>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-4">
|
||||
@@ -23,5 +23,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ds-error *ngIf="item.hasFailed | async" message="{{'error.item' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="item.isLoading | async" message="{{'loading.item' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="(item | async)?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="(item | async)?.isLoading" message="{{'loading.item' | translate}}"></ds-loading>
|
||||
|
@@ -11,6 +11,7 @@ import { Item } from '../../core/shared/item.model';
|
||||
import { MetadataService } from '../../core/metadata/metadata.service';
|
||||
|
||||
import { fadeInOut } from '../../shared/animations/fade';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
@@ -30,7 +31,7 @@ export class ItemPageComponent implements OnInit {
|
||||
|
||||
private sub: any;
|
||||
|
||||
item: RemoteData<Item>;
|
||||
item: Observable<RemoteData<Item>>;
|
||||
|
||||
thumbnail: Observable<Bitstream>;
|
||||
|
||||
@@ -52,7 +53,10 @@ export class ItemPageComponent implements OnInit {
|
||||
this.id = +params.id;
|
||||
this.item = this.items.findById(params.id);
|
||||
this.metadataService.processRemoteData(this.item);
|
||||
this.thumbnail = this.item.payload.flatMap((i) => i.getThumbnail());
|
||||
this.thumbnail = this.item
|
||||
.map((rd: RemoteData<Item>) => rd.payload)
|
||||
.filter((item: Item) => hasValue(item))
|
||||
.flatMap((item: Item) => item.getThumbnail());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<div class="search-page">
|
||||
<ds-search-form
|
||||
[query]="query"
|
||||
[scope]="scopeObject?.payload | async"
|
||||
[scope]="(scopeObject | async)?.payload"
|
||||
[currentParams]="currentParams"
|
||||
[scopes]="scopeList?.payload">
|
||||
[scopes]="(scopeList | async)?.payload">
|
||||
</ds-search-form>
|
||||
<ds-search-results [searchResults]="results" [searchConfig]="searchOptions"></ds-search-results>
|
||||
<ds-search-results [searchResults]="results | async" [searchConfig]="searchOptions"></ds-search-results>
|
||||
</div>
|
||||
|
@@ -9,7 +9,7 @@ import { Community } from '../core/shared/community.model';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||
|
||||
describe('SearchPageComponent', () => {
|
||||
fdescribe('SearchPageComponent', () => {
|
||||
let comp: SearchPageComponent;
|
||||
let fixture: ComponentFixture<SearchPageComponent>;
|
||||
let searchServiceObject: SearchService;
|
||||
@@ -27,8 +27,8 @@ describe('SearchPageComponent', () => {
|
||||
};
|
||||
const mockCommunityList = [];
|
||||
const communityDataServiceStub = {
|
||||
findAll: () => mockCommunityList,
|
||||
findById: () => new Community()
|
||||
findAll: () => Observable.of(mockCommunityList),
|
||||
findById: () => Observable.of(new Community())
|
||||
};
|
||||
|
||||
class RouterStub {
|
||||
|
@@ -1,15 +1,16 @@
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { SearchService } from './search-service/search.service';
|
||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { SearchResult } from './search-result.model';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { Community } from '../core/shared/community.model';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
import { isNotEmpty } from '../shared/empty.util';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
import { SearchOptions } from './search-options.model';
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { isNotEmpty } from '../shared/empty.util';
|
||||
import { Community } from '../core/shared/community.model';
|
||||
import { SearchResult } from './search-result.model';
|
||||
import { SearchService } from './search-service/search.service';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
@@ -21,7 +22,7 @@ import { Community } from '../core/shared/community.model';
|
||||
selector: 'ds-search-page',
|
||||
styleUrls: ['./search-page.component.scss'],
|
||||
templateUrl: './search-page.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
|
||||
@@ -29,11 +30,11 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
||||
private scope: string;
|
||||
|
||||
query: string;
|
||||
scopeObject: RemoteData<DSpaceObject>;
|
||||
results: RemoteData<Array<SearchResult<DSpaceObject>>>;
|
||||
scopeObject: Observable<RemoteData<DSpaceObject>>;
|
||||
results: Observable<RemoteData<Array<SearchResult<DSpaceObject>>>>;
|
||||
currentParams = {};
|
||||
searchOptions: SearchOptions;
|
||||
scopeList: RemoteData<Community[]>;
|
||||
scopeList: Observable<RemoteData<Community[]>>;
|
||||
|
||||
constructor(
|
||||
private service: SearchService,
|
||||
|
@@ -1,7 +1,11 @@
|
||||
<h2 *ngIf="(searchResults.payload | async)?.length > 0">{{ 'search.results.title' | translate }}</h2>
|
||||
<div *ngIf="searchResults?.hasSucceeded" @fadeIn>
|
||||
<h2 *ngIf="searchResults?.payload?.length > 0">{{ 'search.results.title' | translate }}</h2>
|
||||
<ds-object-list
|
||||
[config]="searchConfig.pagination"
|
||||
[sortConfig]="searchConfig.sort"
|
||||
[objects]="searchResults"
|
||||
[hideGear]="false">
|
||||
</ds-object-list>
|
||||
</div>
|
||||
<ds-loading *ngIf="searchResults?.isLoading" message="{{'loading.search-results' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="searchResults?.hasFailed" message="{{'error.search-results' | translate}}"></ds-error>
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { SearchResult } from '../search-result.model';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
||||
import { SearchOptions } from '../search-options.model';
|
||||
import { SearchResult } from '../search-result.model';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
@@ -12,6 +13,10 @@ import { SearchOptions } from '../search-options.model';
|
||||
@Component({
|
||||
selector: 'ds-search-results',
|
||||
templateUrl: './search-results.component.html',
|
||||
animations: [
|
||||
fadeIn,
|
||||
fadeInOut
|
||||
]
|
||||
})
|
||||
export class SearchResultsComponent {
|
||||
@Input() searchResults: RemoteData<Array<SearchResult<DSpaceObject>>>;
|
||||
|
@@ -80,7 +80,7 @@ export class SearchService {
|
||||
|
||||
}
|
||||
|
||||
search(query: string, scopeId?: string, searchOptions?: SearchOptions): RemoteData<Array<SearchResult<DSpaceObject>>> {
|
||||
search(query: string, scopeId?: string, searchOptions?: SearchOptions): Observable<RemoteData<Array<SearchResult<DSpaceObject>>>> {
|
||||
let self = `https://dspace7.4science.it/dspace-spring-rest/api/search?query=${query}`;
|
||||
if (hasValue(scopeId)) {
|
||||
self += `&scope=${scopeId}`;
|
||||
@@ -98,8 +98,8 @@ export class SearchService {
|
||||
self += `&sortField=${searchOptions.sort.field}`;
|
||||
}
|
||||
|
||||
const errorMessage = Observable.of(undefined);
|
||||
const statusCode = Observable.of('200');
|
||||
const errorMessage = undefined;
|
||||
const statusCode = '200';
|
||||
const returningPageInfo = new PageInfo();
|
||||
|
||||
if (isNotEmpty(searchOptions)) {
|
||||
@@ -110,19 +110,20 @@ export class SearchService {
|
||||
returningPageInfo.currentPage = 1;
|
||||
}
|
||||
|
||||
const itemsRD = this.itemDataService.findAll({
|
||||
const itemsObs = this.itemDataService.findAll({
|
||||
scopeID: scopeId,
|
||||
currentPage: returningPageInfo.currentPage,
|
||||
elementsPerPage: returningPageInfo.elementsPerPage
|
||||
});
|
||||
|
||||
const pageInfo = itemsRD.pageInfo.map((info: PageInfo) => {
|
||||
const totalElements = info.totalElements > 20 ? 20 : info.totalElements;
|
||||
return Object.assign({}, info, { totalElements: totalElements });
|
||||
});
|
||||
return itemsObs
|
||||
.filter((rd: RemoteData<Item[]>) => rd.hasSucceeded)
|
||||
.map((rd: RemoteData<Item[]>) => {
|
||||
|
||||
const payload = itemsRD.payload.map((items: Item[]) => {
|
||||
return shuffle(items)
|
||||
const totalElements = rd.pageInfo.totalElements > 20 ? 20 : rd.pageInfo.totalElements;
|
||||
const pageInfo = Object.assign({}, rd.pageInfo, { totalElements: totalElements });
|
||||
|
||||
const payload = shuffle(rd.payload)
|
||||
.map((item: Item, index: number) => {
|
||||
const mockResult: SearchResult<DSpaceObject> = new ItemSearchResult();
|
||||
mockResult.dspaceObject = item;
|
||||
@@ -132,40 +133,49 @@ export class SearchService {
|
||||
mockResult.hitHighlights = new Array(highlight);
|
||||
return mockResult;
|
||||
});
|
||||
});
|
||||
|
||||
return new RemoteData(
|
||||
Observable.of(self),
|
||||
itemsRD.isRequestPending,
|
||||
itemsRD.isResponsePending,
|
||||
itemsRD.hasSucceeded,
|
||||
self,
|
||||
rd.isRequestPending,
|
||||
rd.isResponsePending,
|
||||
rd.hasSucceeded,
|
||||
errorMessage,
|
||||
statusCode,
|
||||
pageInfo,
|
||||
payload
|
||||
)
|
||||
}).startWith(new RemoteData(
|
||||
'',
|
||||
true,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
));
|
||||
}
|
||||
|
||||
getConfig(): RemoteData<SearchFilterConfig[]> {
|
||||
const requestPending = Observable.of(false);
|
||||
const responsePending = Observable.of(false);
|
||||
const isSuccessful = Observable.of(true);
|
||||
const errorMessage = Observable.of(undefined);
|
||||
const statusCode = Observable.of('200');
|
||||
const returningPageInfo = Observable.of(new PageInfo());
|
||||
return new RemoteData(
|
||||
Observable.of('https://dspace7.4science.it/dspace-spring-rest/api/search'),
|
||||
getConfig(): Observable<RemoteData<SearchFilterConfig[]>> {
|
||||
const requestPending = false;
|
||||
const responsePending = false;
|
||||
const isSuccessful = true;
|
||||
const errorMessage = undefined;
|
||||
const statusCode = '200';
|
||||
const returningPageInfo = new PageInfo();
|
||||
return Observable.of(new RemoteData(
|
||||
'https://dspace7.4science.it/dspace-spring-rest/api/search',
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessful,
|
||||
errorMessage,
|
||||
statusCode,
|
||||
returningPageInfo,
|
||||
Observable.of(this.config)
|
||||
);
|
||||
this.config
|
||||
));
|
||||
}
|
||||
|
||||
getFacetValuesFor(searchFilterConfigName: string): RemoteData<FacetValue[]> {
|
||||
getFacetValuesFor(searchFilterConfigName: string): Observable<RemoteData<FacetValue[]>> {
|
||||
const values: FacetValue[] = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const value = searchFilterConfigName + ' ' + (i + 1);
|
||||
@@ -175,21 +185,21 @@ export class SearchService {
|
||||
search: 'https://dspace7.4science.it/dspace-spring-rest/api/search?f.' + searchFilterConfigName + '=' + encodeURI(value)
|
||||
});
|
||||
}
|
||||
const requestPending = Observable.of(false);
|
||||
const responsePending = Observable.of(false);
|
||||
const isSuccessful = Observable.of(true);
|
||||
const errorMessage = Observable.of(undefined);
|
||||
const statusCode = Observable.of('200');
|
||||
const returningPageInfo = Observable.of(new PageInfo());
|
||||
return new RemoteData(
|
||||
Observable.of('https://dspace7.4science.it/dspace-spring-rest/api/search'),
|
||||
const requestPending = false;
|
||||
const responsePending = false;
|
||||
const isSuccessful = true;
|
||||
const errorMessage = undefined;
|
||||
const statusCode = '200';
|
||||
const returningPageInfo = new PageInfo();
|
||||
return Observable.of(new RemoteData(
|
||||
'https://dspace7.4science.it/dspace-spring-rest/api/search',
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessful,
|
||||
errorMessage,
|
||||
statusCode,
|
||||
returningPageInfo,
|
||||
Observable.of(values)
|
||||
);
|
||||
values
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import {
|
||||
Inject,
|
||||
ViewEncapsulation,
|
||||
OnInit,
|
||||
HostListener
|
||||
HostListener, SimpleChanges, OnChanges
|
||||
} from '@angular/core';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@@ -26,7 +26,7 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../config';
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
export class AppComponent implements OnInit, OnChanges {
|
||||
|
||||
constructor(
|
||||
@Inject(GLOBAL_CONFIG) public config: GlobalConfig,
|
||||
@@ -71,4 +71,9 @@ export class AppComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
console.log('AppComponent: onchanges called', changes);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -72,30 +72,42 @@ describe('BrowseService', () => {
|
||||
})
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
|
||||
responseCache = jasmine.createSpyObj('responseCache', {
|
||||
function initMockResponseCacheService(isSuccessful: boolean) {
|
||||
return jasmine.createSpyObj('responseCache', {
|
||||
get: cold('b-', {
|
||||
b: {
|
||||
response: { browseDefinitions }
|
||||
response: {
|
||||
isSuccessful,
|
||||
browseDefinitions,
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
requestService = jasmine.createSpyObj('requestService', ['configure']);
|
||||
function initMockRequestService() {
|
||||
return jasmine.createSpyObj('requestService', ['configure']);
|
||||
}
|
||||
|
||||
service = new BrowseService(
|
||||
function initTestService() {
|
||||
return new BrowseService(
|
||||
responseCache,
|
||||
requestService,
|
||||
envConfig
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
});
|
||||
|
||||
describe('getBrowseURLFor', () => {
|
||||
|
||||
describe('when getEndpoint fires', () => {
|
||||
describe('if getEndpoint fires', () => {
|
||||
beforeEach(() => {
|
||||
responseCache = initMockResponseCacheService(true);
|
||||
requestService = initMockRequestService();
|
||||
service = initTestService();
|
||||
spyOn(service, 'getEndpoint').and
|
||||
.returnValue(hot('--a-', { a: browsesEndpointURL }));
|
||||
});
|
||||
@@ -122,27 +134,27 @@ describe('BrowseService', () => {
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should return undefined when the key doesn\'t match', () => {
|
||||
it('should throw an error when the key doesn\'t match', () => {
|
||||
const metadatumKey = 'dc.title'; // isn't in the definitions
|
||||
const linkName = 'items';
|
||||
|
||||
const result = service.getBrowseURLFor(metadatumKey, linkName);
|
||||
const expected = cold('c---', { c: undefined });
|
||||
const expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkName} on ${metadatumKey} isn't configured`));
|
||||
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should return undefined when the link doesn\'t match', () => {
|
||||
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 result = service.getBrowseURLFor(metadatumKey, linkName);
|
||||
const expected = cold('c---', { c: undefined });
|
||||
const expected = cold('c-#-', { c: undefined }, new Error(`A browse endpoint for ${linkName} on ${metadatumKey} isn't configured`));
|
||||
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should configure a new BrowseEndpointRequest', (done: DoneFn) => {
|
||||
it('should configure a new BrowseEndpointRequest', () => {
|
||||
const metadatumKey = 'dc.date.issued';
|
||||
const linkName = 'items';
|
||||
const expected = new BrowseEndpointRequest(browsesEndpointURL);
|
||||
@@ -150,17 +162,17 @@ describe('BrowseService', () => {
|
||||
scheduler.schedule(() => service.getBrowseURLFor(metadatumKey, linkName).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
done();
|
||||
}, 0);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when getEndpoint doesn\'t fire', () => {
|
||||
it('should return undefined as long as getEndpoint hasn\'t fired', () => {
|
||||
describe('if getEndpoint doesn\'t fire', () => {
|
||||
it('should return undefined', () => {
|
||||
responseCache = initMockResponseCacheService(true);
|
||||
requestService = initMockRequestService();
|
||||
service = initTestService();
|
||||
spyOn(service, 'getEndpoint').and
|
||||
.returnValue(hot('----'));
|
||||
|
||||
@@ -172,5 +184,22 @@ describe('BrowseService', () => {
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if the browses endpoint can\'t be retrieved', () => {
|
||||
it('should throw an error', () => {
|
||||
responseCache = initMockResponseCacheService(false);
|
||||
requestService = initMockRequestService();
|
||||
service = initTestService();
|
||||
spyOn(service, 'getEndpoint').and
|
||||
.returnValue(hot('--a-', { a: browsesEndpointURL }));
|
||||
|
||||
const metadatumKey = 'dc.date.issued';
|
||||
const linkName = 'items';
|
||||
|
||||
const result = service.getBrowseURLFor(metadatumKey, linkName);
|
||||
const expected = cold('c-#-', { c: undefined }, new Error(`Couldn't retrieve the browses endpoint`));
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { GLOBAL_CONFIG } from '../../../config';
|
||||
import { BrowseEndpointRequest, RestRequest } from '../data/request.models';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { BrowseSuccessResponse } from '../cache/response-cache.models';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { GLOBAL_CONFIG } from '../../../config';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { BrowseSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { ResponseCacheService } from '../cache/response-cache.service';
|
||||
import { BrowseEndpointRequest, RestRequest } from '../data/request.models';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
|
||||
@Injectable()
|
||||
export class BrowseService extends HALEndpointService {
|
||||
@@ -41,23 +41,32 @@ export class BrowseService extends HALEndpointService {
|
||||
.filter((href: string) => isNotEmpty(href))
|
||||
.distinctUntilChanged()
|
||||
.map((endpointURL: string) => new BrowseEndpointRequest(endpointURL))
|
||||
.do((request: RestRequest) => {
|
||||
setTimeout(() => {
|
||||
this.requestService.configure(request);
|
||||
}, 0);
|
||||
})
|
||||
.flatMap((request: RestRequest) => this.responseCache.get(request.href)
|
||||
.do((request: RestRequest) => this.requestService.configure(request))
|
||||
.flatMap((request: RestRequest) => {
|
||||
const [successResponse, errorResponse] = this.responseCache.get(request.href)
|
||||
.map((entry: ResponseCacheEntry) => entry.response)
|
||||
.filter((response: BrowseSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.browseDefinitions))
|
||||
.partition((response: RestResponse) => response.isSuccessful);
|
||||
|
||||
return Observable.merge(
|
||||
errorResponse.flatMap((response: ErrorResponse) =>
|
||||
Observable.throw(new Error(`Couldn't retrieve the browses endpoint`))),
|
||||
successResponse
|
||||
.filter((response: BrowseSuccessResponse) => isNotEmpty(response.browseDefinitions))
|
||||
.map((response: BrowseSuccessResponse) => response.browseDefinitions)
|
||||
.map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
|
||||
.find((def: BrowseDefinition) => {
|
||||
const matchingKeys = def.metadataKeys.find((key: string) => searchKeyArray.indexOf(key) >= 0);
|
||||
return isNotEmpty(matchingKeys);
|
||||
})
|
||||
).filter((def: BrowseDefinition) => isNotEmpty(def) && isNotEmpty(def._links))
|
||||
.map((def: BrowseDefinition) => def._links[linkName])
|
||||
).startWith(undefined)
|
||||
).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`);
|
||||
} else {
|
||||
return def._links[linkName];
|
||||
}
|
||||
})
|
||||
);
|
||||
}).startWith(undefined)
|
||||
.distinctUntilChanged();
|
||||
}
|
||||
|
||||
|
@@ -28,7 +28,7 @@ export class RemoteDataBuildService {
|
||||
buildSingle<TNormalized extends CacheableObject, TDomain>(
|
||||
hrefObs: string | Observable<string>,
|
||||
normalizedType: GenericConstructor<TNormalized>
|
||||
): RemoteData<TDomain> {
|
||||
): Observable<RemoteData<TDomain>> {
|
||||
if (typeof hrefObs === 'string') {
|
||||
hrefObs = Observable.of(hrefObs);
|
||||
}
|
||||
@@ -49,46 +49,8 @@ export class RemoteDataBuildService {
|
||||
requestHrefObs.flatMap((requestHref) => this.responseCache.get(requestHref)).filter((entry) => hasValue(entry))
|
||||
);
|
||||
|
||||
const requestPending = requestObs
|
||||
.map((entry: RequestEntry) => entry.requestPending)
|
||||
.startWith(true)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const responsePending = requestObs
|
||||
.map((entry: RequestEntry) => entry.responsePending)
|
||||
.startWith(false)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const isSuccessFul = responseCacheObs
|
||||
.map((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.startWith(false)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const errorMessage = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as ErrorResponse).errorMessage)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const statusCode = responseCacheObs
|
||||
.map((entry: ResponseCacheEntry) => entry.response.statusCode)
|
||||
.distinctUntilChanged();
|
||||
|
||||
/* tslint:disable:no-string-literal */
|
||||
const pageInfo = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => hasValue(entry.response) && hasValue(entry.response['pageInfo']))
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).pageInfo)
|
||||
.map((pInfo: PageInfo) => {
|
||||
if (isNotEmpty(pageInfo) && pInfo.currentPage >= 0) {
|
||||
return Object.assign({}, pInfo, {currentPage: pInfo.currentPage + 1});
|
||||
} else {
|
||||
return pInfo;
|
||||
}
|
||||
})
|
||||
.distinctUntilChanged();
|
||||
/* tslint:enable:no-string-literal */
|
||||
|
||||
// always use self link if that is cached, only if it isn't, get it via the response.
|
||||
const payload =
|
||||
const payloadObs =
|
||||
Observable.combineLatest(
|
||||
hrefObs.flatMap((href: string) => this.objectCache.getBySelfLink<TNormalized>(href, normalizedType))
|
||||
.startWith(undefined),
|
||||
@@ -114,10 +76,38 @@ export class RemoteDataBuildService {
|
||||
).filter((normalized) => hasValue(normalized))
|
||||
.map((normalized: TNormalized) => {
|
||||
return this.build<TNormalized, TDomain>(normalized);
|
||||
}).distinctUntilChanged();
|
||||
})
|
||||
.startWith(undefined)
|
||||
.distinctUntilChanged();
|
||||
return this.toRemoteDataObservable(hrefObs, requestObs, responseCacheObs, payloadObs);
|
||||
}
|
||||
|
||||
private toRemoteDataObservable<T>(hrefObs: Observable<string>, requestObs: Observable<RequestEntry>, responseCacheObs: Observable<ResponseCacheEntry>, payloadObs: Observable<T>) {
|
||||
return Observable.combineLatest(hrefObs, requestObs, responseCacheObs.startWith(undefined), payloadObs,
|
||||
(href: string, 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;
|
||||
let errorMessage: string;
|
||||
let statusCode: string;
|
||||
let pageInfo: PageInfo;
|
||||
if (hasValue(resEntry) && hasValue(resEntry.response)) {
|
||||
isSuccessFul = resEntry.response.isSuccessful;
|
||||
errorMessage = isSuccessFul === false ? (resEntry.response as ErrorResponse).errorMessage : undefined;
|
||||
statusCode = resEntry.response.statusCode;
|
||||
|
||||
if (hasValue((resEntry.response as DSOSuccessResponse).pageInfo)) {
|
||||
const resPageInfo = (resEntry.response as DSOSuccessResponse).pageInfo;
|
||||
if (isNotEmpty(resPageInfo) && resPageInfo.currentPage >= 0) {
|
||||
pageInfo = Object.assign({}, resPageInfo, { currentPage: resPageInfo.currentPage + 1 });
|
||||
} else {
|
||||
pageInfo = resPageInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RemoteData(
|
||||
hrefObs,
|
||||
href,
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessFul,
|
||||
@@ -126,12 +116,13 @@ export class RemoteDataBuildService {
|
||||
pageInfo,
|
||||
payload
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
buildList<TNormalized extends CacheableObject, TDomain>(
|
||||
hrefObs: string | Observable<string>,
|
||||
normalizedType: GenericConstructor<TNormalized>
|
||||
): RemoteData<TDomain[]> {
|
||||
): Observable<RemoteData<TDomain[]>> {
|
||||
if (typeof hrefObs === 'string') {
|
||||
hrefObs = Observable.of(hrefObs);
|
||||
}
|
||||
@@ -141,38 +132,7 @@ export class RemoteDataBuildService {
|
||||
const responseCacheObs = hrefObs.flatMap((href: string) => this.responseCache.get(href))
|
||||
.filter((entry) => hasValue(entry));
|
||||
|
||||
const requestPending = requestObs
|
||||
.map((entry: RequestEntry) => entry.requestPending)
|
||||
.startWith(true)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const responsePending = requestObs
|
||||
.map((entry: RequestEntry) => entry.responsePending)
|
||||
.startWith(false)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const isSuccessFul = responseCacheObs
|
||||
.map((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.startWith(false)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const errorMessage = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => !entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as ErrorResponse).errorMessage)
|
||||
.distinctUntilChanged();
|
||||
|
||||
const statusCode = responseCacheObs
|
||||
.map((entry: ResponseCacheEntry) => entry.response.statusCode)
|
||||
.distinctUntilChanged();
|
||||
|
||||
/* tslint:disable:no-string-literal */
|
||||
const pageInfo = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => hasValue(entry.response) && hasValue(entry.response['pageInfo']))
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).pageInfo)
|
||||
.distinctUntilChanged();
|
||||
/* tslint:enable:no-string-literal */
|
||||
|
||||
const payload = responseCacheObs
|
||||
const payloadObs = responseCacheObs
|
||||
.filter((entry: ResponseCacheEntry) => entry.response.isSuccessful)
|
||||
.map((entry: ResponseCacheEntry) => (entry.response as DSOSuccessResponse).resourceSelfLinks)
|
||||
.flatMap((resourceUUIDs: string[]) => {
|
||||
@@ -183,18 +143,10 @@ export class RemoteDataBuildService {
|
||||
});
|
||||
});
|
||||
})
|
||||
.startWith([])
|
||||
.distinctUntilChanged();
|
||||
|
||||
return new RemoteData(
|
||||
hrefObs,
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessFul,
|
||||
errorMessage,
|
||||
statusCode,
|
||||
pageInfo,
|
||||
payload
|
||||
);
|
||||
return this.toRemoteDataObservable(hrefObs, requestObs, responseCacheObs, payloadObs);
|
||||
}
|
||||
|
||||
build<TNormalized extends CacheableObject, TDomain>(normalized: TNormalized): TDomain {
|
||||
@@ -207,13 +159,9 @@ export class RemoteDataBuildService {
|
||||
const { resourceType, isList } = getRelationMetadata(normalized, relationship);
|
||||
const resourceConstructor = NormalizedObjectFactory.getConstructor(resourceType);
|
||||
if (Array.isArray(normalized[relationship])) {
|
||||
// without the setTimeout, the actions inside requestService.configure
|
||||
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
|
||||
setTimeout(() => {
|
||||
normalized[relationship].forEach((href: string) => {
|
||||
this.requestService.configure(new RestRequest(href))
|
||||
});
|
||||
}, 0);
|
||||
|
||||
const rdArr = [];
|
||||
normalized[relationship].forEach((href: string) => {
|
||||
@@ -226,11 +174,7 @@ export class RemoteDataBuildService {
|
||||
links[relationship] = rdArr[0];
|
||||
}
|
||||
} else {
|
||||
// without the setTimeout, the actions inside requestService.configure
|
||||
// are dispatched, but sometimes don't arrive. I'm unsure why atm.
|
||||
setTimeout(() => {
|
||||
this.requestService.configure(new RestRequest(normalized[relationship]));
|
||||
}, 0);
|
||||
|
||||
// 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),
|
||||
@@ -248,55 +192,46 @@ export class RemoteDataBuildService {
|
||||
return Object.assign(new domainModel(), normalized, links);
|
||||
}
|
||||
|
||||
aggregate<T>(input: Array<RemoteData<T>>): RemoteData<T[]> {
|
||||
const requestPending = Observable.combineLatest(
|
||||
...input.map((rd) => rd.isRequestPending),
|
||||
).map((...pendingArray) => pendingArray.every((e) => e === true))
|
||||
.distinctUntilChanged();
|
||||
aggregate<T>(input: Array<Observable<RemoteData<T>>>): Observable<RemoteData<T[]>> {
|
||||
return Observable.combineLatest(
|
||||
...input,
|
||||
(...arr: Array<RemoteData<T>>) => {
|
||||
const requestPending: boolean = arr
|
||||
.map((d: RemoteData<T>) => d.isRequestPending)
|
||||
.every((b: boolean) => b === true);
|
||||
|
||||
const responsePending = Observable.combineLatest(
|
||||
...input.map((rd) => rd.isResponsePending),
|
||||
).map((...pendingArray) => pendingArray.every((e) => e === true))
|
||||
.distinctUntilChanged();
|
||||
const responsePending: boolean = arr
|
||||
.map((d: RemoteData<T>) => d.isResponsePending)
|
||||
.every((b: boolean) => b === true);
|
||||
|
||||
const isSuccessFul = Observable.combineLatest(
|
||||
...input.map((rd) => rd.hasSucceeded),
|
||||
).map((...successArray) => successArray.every((e) => e === true))
|
||||
.distinctUntilChanged();
|
||||
const isSuccessFul: boolean = arr
|
||||
.map((d: RemoteData<T>) => d.hasSucceeded)
|
||||
.every((b: boolean) => b === true);
|
||||
|
||||
const errorMessage = Observable.combineLatest(
|
||||
...input.map((rd) => rd.errorMessage),
|
||||
).map((...errors) => errors
|
||||
.map((e, idx) => {
|
||||
const errorMessage: string = arr
|
||||
.map((d: RemoteData<T>) => d.errorMessage)
|
||||
.map((e: string, idx: number) => {
|
||||
if (hasValue(e)) {
|
||||
return `[${idx}]: ${e}`;
|
||||
}
|
||||
})
|
||||
.filter((e) => hasValue(e))
|
||||
.join(', ')
|
||||
);
|
||||
}).filter((e: string) => hasValue(e))
|
||||
.join(', ');
|
||||
|
||||
const statusCode = Observable.combineLatest(
|
||||
...input.map((rd) => rd.statusCode),
|
||||
).map((...statusCodes) => statusCodes
|
||||
.map((code, idx) => {
|
||||
if (hasValue(code)) {
|
||||
return `[${idx}]: ${code}`;
|
||||
const statusCode: string = arr
|
||||
.map((d: RemoteData<T>) => d.statusCode)
|
||||
.map((c: string, idx: number) => {
|
||||
if (hasValue(c)) {
|
||||
return `[${idx}]: ${c}`;
|
||||
}
|
||||
})
|
||||
.filter((c) => hasValue(c))
|
||||
.join(', ')
|
||||
);
|
||||
}).filter((c: string) => hasValue(c))
|
||||
.join(', ');
|
||||
|
||||
const pageInfo = Observable.of(undefined);
|
||||
const pageInfo = undefined;
|
||||
|
||||
const payload = Observable.combineLatest(...input.map((rd) => rd.payload)) as Observable<T[]>;
|
||||
const payload: T[] = arr.map((d: RemoteData<T>) => d.payload);
|
||||
|
||||
return new RemoteData(
|
||||
// This is an aggregated object, it doesn't necessarily correspond
|
||||
// to a single REST endpoint, so instead of a self link, use the
|
||||
// current time in ms for a somewhat unique id
|
||||
Observable.of(`${new Date().getTime()}`),
|
||||
`dspace-angular://aggregated/object/${new Date().getTime()}`,
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessFul,
|
||||
@@ -305,6 +240,7 @@ export class RemoteDataBuildService {
|
||||
pageInfo,
|
||||
payload
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
2
src/app/core/cache/response-cache.service.ts
vendored
2
src/app/core/cache/response-cache.service.ts
vendored
@@ -4,7 +4,7 @@ import { MemoizedSelector, Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { ResponseCacheEntry } from './response-cache.reducer';
|
||||
import { hasNoValue } from '../../shared/empty.util';
|
||||
import { hasNoValue, hasValue } from '../../shared/empty.util';
|
||||
import { ResponseCacheRemoveAction, ResponseCacheAddAction } from './response-cache.actions';
|
||||
import { RestResponse } from './response-cache.models';
|
||||
import { CoreState } from '../core.reducers';
|
||||
|
@@ -53,6 +53,7 @@ describe('ComColDataService', () => {
|
||||
const communitiesEndpoint = 'https://rest.api/core/communities';
|
||||
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
|
||||
const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`;
|
||||
const serviceEndpoint = `https://rest.api/core/${LINK_NAME}`;
|
||||
|
||||
function initMockCommunityDataService(): CommunityDataService {
|
||||
return jasmine.createSpyObj('responseCache', {
|
||||
@@ -102,7 +103,7 @@ describe('ComColDataService', () => {
|
||||
scheduler = getTestScheduler();
|
||||
});
|
||||
|
||||
it('should configure a new FindByIDRequest for the scope Community', (done: DoneFn) => {
|
||||
it('should configure a new FindByIDRequest for the scope Community', () => {
|
||||
cds = initMockCommunityDataService();
|
||||
requestService = initMockRequestService();
|
||||
objectCache = initMockObjectCacheService();
|
||||
@@ -114,10 +115,7 @@ describe('ComColDataService', () => {
|
||||
scheduler.schedule(() => service.getScopedEndpoint(scopeID).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
setTimeout(() => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
describe('if the scope Community can be found', () => {
|
||||
@@ -159,5 +157,25 @@ describe('ComColDataService', () => {
|
||||
expect(result).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if the scope is not specified', () => {
|
||||
beforeEach(() => {
|
||||
cds = initMockCommunityDataService();
|
||||
requestService = initMockRequestService();
|
||||
objectCache = initMockObjectCacheService();
|
||||
responseCache = initMockResponceCacheService(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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@@ -1,14 +1,14 @@
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { NormalizedCommunity } from '../cache/models/normalized-community.model';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { DSOSuccessResponse, RestResponse } from '../cache/response-cache.models';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { DSOSuccessResponse, ErrorResponse, RestResponse } from '../cache/response-cache.models';
|
||||
import { ResponseCacheEntry } from '../cache/response-cache.reducer';
|
||||
import { CommunityDataService } from './community-data.service';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { FindByIDRequest } from './request.models';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
|
||||
export abstract class ComColDataService<TNormalized extends CacheableObject, TDomain> extends DataService<TNormalized, TDomain> {
|
||||
protected abstract cds: CommunityDataService;
|
||||
@@ -25,15 +25,16 @@ export abstract class ComColDataService<TNormalized extends CacheableObject, TDo
|
||||
* an Observable<string> containing the scoped URL
|
||||
*/
|
||||
public getScopedEndpoint(scopeID: string): Observable<string> {
|
||||
if (isEmpty(scopeID)) {
|
||||
return this.getEndpoint();
|
||||
} else {
|
||||
const scopeCommunityHrefObs = this.cds.getEndpoint()
|
||||
.flatMap((endpoint: string) => this.cds.getFindByIDHref(endpoint, scopeID))
|
||||
.filter((href: string) => isNotEmpty(href))
|
||||
.take(1)
|
||||
.do((href: string) => {
|
||||
const request = new FindByIDRequest(href, scopeID);
|
||||
setTimeout(() => {
|
||||
this.requestService.configure(request);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
const [successResponse, errorResponse] = scopeCommunityHrefObs
|
||||
@@ -43,7 +44,7 @@ export abstract class ComColDataService<TNormalized extends CacheableObject, TDo
|
||||
.partition((response: RestResponse) => response.isSuccessful);
|
||||
|
||||
return Observable.merge(
|
||||
errorResponse.flatMap((response: DSOSuccessResponse) =>
|
||||
errorResponse.flatMap((response: ErrorResponse) =>
|
||||
Observable.throw(new Error(`The Community with scope ${scopeID} couldn't be retrieved`))),
|
||||
successResponse
|
||||
.flatMap((response: DSOSuccessResponse) => this.objectCache.getByUUID(scopeID, NormalizedCommunity))
|
||||
@@ -52,3 +53,4 @@ export abstract class ComColDataService<TNormalized extends CacheableObject, TDo
|
||||
).distinctUntilChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindAllOptions, FindAllRequest, FindByIDRequest, RestRequest } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||
|
||||
export abstract class DataService<TNormalized extends CacheableObject, TDomain> extends HALEndpointService {
|
||||
protected abstract responseCache: ResponseCacheService;
|
||||
@@ -29,11 +30,12 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
public abstract getScopedEndpoint(scope: string): Observable<string>
|
||||
|
||||
protected getFindAllHref(endpoint, options: FindAllOptions = {}): Observable<string> {
|
||||
let result;
|
||||
let result: Observable<string>;
|
||||
const args = [];
|
||||
|
||||
if (hasValue(options.scopeID)) {
|
||||
result = this.getScopedEndpoint(options.scopeID);
|
||||
result = this.getScopedEndpoint(options.scopeID).distinctUntilChanged();
|
||||
// result.take(1).subscribe((r) => console.log('result', r));
|
||||
} else {
|
||||
result = Observable.of(endpoint);
|
||||
}
|
||||
@@ -56,22 +58,22 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
}
|
||||
|
||||
if (isNotEmpty(args)) {
|
||||
return result.map((href: string) => `${href}?${args.join('&')}`);
|
||||
return result.map((href: string) => new URLCombiner(href, `?${args.join('&')}`).toString());
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
findAll(options: FindAllOptions = {}): RemoteData<TDomain[]> {
|
||||
const hrefObs = this.getEndpoint()
|
||||
findAll(options: FindAllOptions = {}): Observable<RemoteData<TDomain[]>> {
|
||||
const hrefObs = this.getEndpoint().filter((href: string) => isNotEmpty(href))
|
||||
.flatMap((endpoint: string) => this.getFindAllHref(endpoint, options));
|
||||
|
||||
hrefObs
|
||||
.filter((href: string) => hasValue(href))
|
||||
.take(1)
|
||||
.subscribe((href: string) => {
|
||||
const request = new FindAllRequest(href, options);
|
||||
setTimeout(() => {
|
||||
this.requestService.configure(request);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList<TNormalized, TDomain>(hrefObs, this.normalizedResourceType);
|
||||
@@ -81,27 +83,24 @@ export abstract class DataService<TNormalized extends CacheableObject, TDomain>
|
||||
return `${endpoint}/${resourceID}`;
|
||||
}
|
||||
|
||||
findById(id: string): RemoteData<TDomain> {
|
||||
findById(id: string): Observable<RemoteData<TDomain>> {
|
||||
const hrefObs = this.getEndpoint()
|
||||
.map((endpoint: string) => this.getFindByIDHref(endpoint, id));
|
||||
|
||||
hrefObs
|
||||
.filter((href: string) => hasValue(href))
|
||||
.take(1)
|
||||
.subscribe((href: string) => {
|
||||
const request = new FindByIDRequest(href, id);
|
||||
setTimeout(() => {
|
||||
this.requestService.configure(request);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
return this.rdbService.buildSingle<TNormalized, TDomain>(hrefObs, this.normalizedResourceType);
|
||||
}
|
||||
|
||||
findByHref(href: string): RemoteData<TDomain> {
|
||||
setTimeout(() => {
|
||||
findByHref(href: string): Observable<RemoteData<TDomain>> {
|
||||
this.requestService.configure(new RestRequest(href));
|
||||
}, 0);
|
||||
return this.rdbService.buildSingle<TNormalized, TDomain>(href, this.normalizedResourceType);
|
||||
// return this.rdbService.buildSingle(href));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -14,57 +14,48 @@ export enum RemoteDataState {
|
||||
*/
|
||||
export class RemoteData<T> {
|
||||
constructor(
|
||||
public self: Observable<string>,
|
||||
private requestPending: Observable<boolean>,
|
||||
private responsePending: Observable<boolean>,
|
||||
private isSuccessFul: Observable<boolean>,
|
||||
public errorMessage: Observable<string>,
|
||||
public statusCode: Observable<string>,
|
||||
public pageInfo: Observable<PageInfo>,
|
||||
public payload: Observable<T>
|
||||
public self: string,
|
||||
private requestPending: boolean,
|
||||
private responsePending: boolean,
|
||||
private isSuccessFul: boolean,
|
||||
public errorMessage: string,
|
||||
public statusCode: string,
|
||||
public pageInfo: PageInfo,
|
||||
public payload: T
|
||||
) {
|
||||
}
|
||||
|
||||
get state(): Observable<RemoteDataState> {
|
||||
return Observable.combineLatest(
|
||||
this.requestPending,
|
||||
this.responsePending,
|
||||
this.isSuccessFul,
|
||||
(requestPending, responsePending, isSuccessFul) => {
|
||||
if (requestPending) {
|
||||
return RemoteDataState.RequestPending
|
||||
} else if (responsePending) {
|
||||
return RemoteDataState.ResponsePending
|
||||
} else if (!isSuccessFul) {
|
||||
return RemoteDataState.Failed
|
||||
} else {
|
||||
get state(): RemoteDataState {
|
||||
if (this.isSuccessFul === true) {
|
||||
return RemoteDataState.Success
|
||||
} else if (this.isSuccessFul === false) {
|
||||
return RemoteDataState.Failed
|
||||
} else if (this.requestPending === true) {
|
||||
return RemoteDataState.RequestPending
|
||||
} else if (this.responsePending === true || this.isSuccessFul === undefined) {
|
||||
return RemoteDataState.ResponsePending
|
||||
}
|
||||
}
|
||||
).distinctUntilChanged();
|
||||
}
|
||||
|
||||
get isRequestPending(): Observable<boolean> {
|
||||
return this.state.map((state) => state === RemoteDataState.RequestPending).distinctUntilChanged();
|
||||
get isRequestPending(): boolean {
|
||||
return this.state === RemoteDataState.RequestPending;
|
||||
}
|
||||
|
||||
get isResponsePending(): Observable<boolean> {
|
||||
return this.state.map((state) => state === RemoteDataState.ResponsePending).distinctUntilChanged();
|
||||
get isResponsePending(): boolean {
|
||||
return this.state === RemoteDataState.ResponsePending;
|
||||
}
|
||||
|
||||
get isLoading(): Observable<boolean> {
|
||||
return this.state.map((state) => {
|
||||
return state === RemoteDataState.RequestPending
|
||||
|| state === RemoteDataState.ResponsePending
|
||||
}).distinctUntilChanged();
|
||||
get isLoading(): boolean {
|
||||
return this.state === RemoteDataState.RequestPending
|
||||
|| this.state === RemoteDataState.ResponsePending;
|
||||
}
|
||||
|
||||
get hasFailed(): Observable<boolean> {
|
||||
return this.state.map((state) => state === RemoteDataState.Failed).distinctUntilChanged();
|
||||
get hasFailed(): boolean {
|
||||
return this.state === RemoteDataState.Failed;
|
||||
}
|
||||
|
||||
get hasSucceeded(): Observable<boolean> {
|
||||
return this.state.map((state) => state === RemoteDataState.Success).distinctUntilChanged();
|
||||
get hasSucceeded(): boolean {
|
||||
return this.state === RemoteDataState.Success;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ function entryFromHrefSelector(href: string): MemoizedSelector<CoreState, Reques
|
||||
|
||||
@Injectable()
|
||||
export class RequestService {
|
||||
private requestsOnTheirWayToTheStore: string[] = [];
|
||||
|
||||
constructor(
|
||||
private objectCache: ObjectCacheService,
|
||||
@@ -31,6 +32,12 @@ export class RequestService {
|
||||
}
|
||||
|
||||
isPending(href: string): boolean {
|
||||
// first check requests that haven't made it to the store yet
|
||||
if (this.requestsOnTheirWayToTheStore.includes(href)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// then check the store
|
||||
let isPending = false;
|
||||
this.store.select(entryFromHrefSelector(href))
|
||||
.take(1)
|
||||
@@ -75,6 +82,24 @@ export class RequestService {
|
||||
if (!(isCached || isPending)) {
|
||||
this.store.dispatch(new RequestConfigureAction(request));
|
||||
this.store.dispatch(new RequestExecuteAction(request.href));
|
||||
this.trackRequestsOnTheirWayToTheStore(request.href);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ngrx action dispatches are asynchronous. But this.isPending needs to return true as soon as the
|
||||
* configure method for a request has been executed, otherwise certain requests will happen multiple times.
|
||||
*
|
||||
* This method will store the href of every request that gets configured in a local variable, and
|
||||
* remove it as soon as it can be found in the store.
|
||||
*/
|
||||
private trackRequestsOnTheirWayToTheStore(href: string) {
|
||||
this.requestsOnTheirWayToTheStore = [...this.requestsOnTheirWayToTheStore, href];
|
||||
this.store.select(entryFromHrefSelector(href))
|
||||
.filter((re: RequestEntry) => hasValue(re))
|
||||
.take(1)
|
||||
.subscribe((re: RequestEntry) => {
|
||||
this.requestsOnTheirWayToTheStore = this.requestsOnTheirWayToTheStore.filter((pendingHref: string) => pendingHref !== href)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -31,6 +31,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';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
@Component({
|
||||
@@ -175,33 +176,17 @@ describe('MetadataService', () => {
|
||||
expect(tagStore.get('description')[0].content).toEqual('This is a dummy item component for testing!');
|
||||
}));
|
||||
|
||||
const mockRemoteData = (mockItem: Item): RemoteData<Item> => {
|
||||
return new RemoteData<Item>(
|
||||
Observable.create((observer) => {
|
||||
observer.next('');
|
||||
}),
|
||||
Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
Observable.create((observer) => {
|
||||
observer.next(true);
|
||||
}),
|
||||
Observable.create((observer) => {
|
||||
observer.next('');
|
||||
}),
|
||||
Observable.create((observer) => {
|
||||
observer.next(200);
|
||||
}),
|
||||
Observable.create((observer) => {
|
||||
observer.next({});
|
||||
}),
|
||||
Observable.create((observer) => {
|
||||
observer.next(MockItem);
|
||||
})
|
||||
);
|
||||
const mockRemoteData = (mockItem: Item): Observable<RemoteData<Item>> => {
|
||||
return Observable.of(new RemoteData<Item>(
|
||||
'',
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
'',
|
||||
'200',
|
||||
{} as PageInfo,
|
||||
MockItem
|
||||
));
|
||||
}
|
||||
|
||||
const mockType = (mockItem: Item, type: string): Item => {
|
||||
|
@@ -25,6 +25,8 @@ import { Item } from '../shared/item.model';
|
||||
import { Metadatum } from '../shared/metadatum.model';
|
||||
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
|
||||
import { BitstreamFormat } from '../shared/bitstream-format.model';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
|
||||
@Injectable()
|
||||
export class MetadataService {
|
||||
@@ -64,8 +66,11 @@ export class MetadataService {
|
||||
});
|
||||
}
|
||||
|
||||
public processRemoteData(remoteData: RemoteData<CacheableObject>): void {
|
||||
remoteData.payload.take(1).subscribe((dspaceObject: DSpaceObject) => {
|
||||
public processRemoteData(remoteData: Observable<RemoteData<CacheableObject>>): void {
|
||||
remoteData.map((rd: RemoteData<CacheableObject>) => rd.payload)
|
||||
.filter((co: CacheableObject) => hasValue(co))
|
||||
.take(1)
|
||||
.subscribe((dspaceObject: DSpaceObject) => {
|
||||
if (!this.initialized) {
|
||||
this.initialize(dspaceObject);
|
||||
}
|
||||
@@ -268,7 +273,10 @@ export class MetadataService {
|
||||
// taking only two, fist one is empty array
|
||||
item.getFiles().take(2).subscribe((bitstreams: Bitstream[]) => {
|
||||
for (const bitstream of bitstreams) {
|
||||
bitstream.format.payload.take(1).subscribe((format) => {
|
||||
bitstream.format.take(1)
|
||||
.map((rd: RemoteData<BitstreamFormat>) => rd.payload)
|
||||
.filter((format: BitstreamFormat) => hasValue(format))
|
||||
.subscribe((format: BitstreamFormat) => {
|
||||
if (format.mimetype === 'application/pdf') {
|
||||
this.addMetaTag('citation_pdf_url', bitstream.content);
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { DSpaceObject } from './dspace-object.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Item } from './item.model';
|
||||
import { BitstreamFormat } from './bitstream-format.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
export class Bitstream extends DSpaceObject {
|
||||
|
||||
@@ -23,17 +24,17 @@ export class Bitstream extends DSpaceObject {
|
||||
/**
|
||||
* An array of Bitstream Format of this Bitstream
|
||||
*/
|
||||
format: RemoteData<BitstreamFormat>;
|
||||
format: Observable<RemoteData<BitstreamFormat>>;
|
||||
|
||||
/**
|
||||
* An array of Items that are direct parents of this Bitstream
|
||||
*/
|
||||
parents: RemoteData<Item[]>;
|
||||
parents: Observable<RemoteData<Item[]>>;
|
||||
|
||||
/**
|
||||
* The Bundle that owns this Bitstream
|
||||
*/
|
||||
owner: RemoteData<Item>;
|
||||
owner: Observable<RemoteData<Item>>;
|
||||
|
||||
/**
|
||||
* The URL to retrieve this Bitstream's file
|
||||
|
@@ -2,23 +2,24 @@ import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
export class Bundle extends DSpaceObject {
|
||||
/**
|
||||
* The primary bitstream of this Bundle
|
||||
*/
|
||||
primaryBitstream: RemoteData<Bitstream>;
|
||||
primaryBitstream: Observable<RemoteData<Bitstream>>;
|
||||
|
||||
/**
|
||||
* An array of Items that are direct parents of this Bundle
|
||||
*/
|
||||
parents: RemoteData<Item[]>;
|
||||
parents: Observable<RemoteData<Item[]>>;
|
||||
|
||||
/**
|
||||
* The Item that owns this Bundle
|
||||
*/
|
||||
owner: RemoteData<Item>;
|
||||
owner: Observable<RemoteData<Item>>;
|
||||
|
||||
bitstreams: RemoteData<Bitstream[]>
|
||||
bitstreams: Observable<RemoteData<Bitstream[]>>
|
||||
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Item } from './item.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
export class Collection extends DSpaceObject {
|
||||
|
||||
@@ -53,18 +54,18 @@ export class Collection extends DSpaceObject {
|
||||
/**
|
||||
* The Bitstream that represents the logo of this Collection
|
||||
*/
|
||||
logo: RemoteData<Bitstream>;
|
||||
logo: Observable<RemoteData<Bitstream>>;
|
||||
|
||||
/**
|
||||
* An array of Collections that are direct parents of this Collection
|
||||
*/
|
||||
parents: RemoteData<Collection[]>;
|
||||
parents: Observable<RemoteData<Collection[]>>;
|
||||
|
||||
/**
|
||||
* The Collection that owns this Collection
|
||||
*/
|
||||
owner: RemoteData<Collection>;
|
||||
owner: Observable<RemoteData<Collection>>;
|
||||
|
||||
items: RemoteData<Item[]>;
|
||||
items: Observable<RemoteData<Item[]>>;
|
||||
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ import { DSpaceObject } from './dspace-object.model';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { Collection } from './collection.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
export class Community extends DSpaceObject {
|
||||
|
||||
@@ -45,18 +46,18 @@ export class Community extends DSpaceObject {
|
||||
/**
|
||||
* The Bitstream that represents the logo of this Community
|
||||
*/
|
||||
logo: RemoteData<Bitstream>;
|
||||
logo: Observable<RemoteData<Bitstream>>;
|
||||
|
||||
/**
|
||||
* An array of Communities that are direct parents of this Community
|
||||
*/
|
||||
parents: RemoteData<DSpaceObject[]>;
|
||||
parents: Observable<RemoteData<DSpaceObject[]>>;
|
||||
|
||||
/**
|
||||
* The Community that owns this Community
|
||||
*/
|
||||
owner: RemoteData<Community>;
|
||||
owner: Observable<RemoteData<Community>>;
|
||||
|
||||
collections: RemoteData<Collection[]>;
|
||||
collections: Observable<RemoteData<Collection[]>>;
|
||||
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { ResourceType } from './resource-type';
|
||||
import { ListableObject } from '../../object-list/listable-object/listable-object.model';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
/**
|
||||
* An abstract model class for a DSpaceObject.
|
||||
@@ -40,12 +41,12 @@ export abstract class DSpaceObject implements CacheableObject, ListableObject {
|
||||
/**
|
||||
* An array of DSpaceObjects that are direct parents of this DSpaceObject
|
||||
*/
|
||||
parents: RemoteData<DSpaceObject[]>;
|
||||
parents: Observable<RemoteData<DSpaceObject[]>>;
|
||||
|
||||
/**
|
||||
* The DSpaceObject that owns this DSpaceObject
|
||||
*/
|
||||
owner: RemoteData<DSpaceObject>;
|
||||
owner: Observable<RemoteData<DSpaceObject>>;
|
||||
|
||||
/**
|
||||
* Find a metadata field by key and language
|
||||
|
@@ -51,13 +51,10 @@ describe('HALEndpointService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should configure a new RootEndpointRequest', (done) => {
|
||||
it('should configure a new RootEndpointRequest', () => {
|
||||
(service as any).getEndpointMap();
|
||||
const expected = new RootEndpointRequest(envConfig);
|
||||
setTimeout(() => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('should return an Observable of the endpoint map', () => {
|
||||
|
@@ -15,10 +15,9 @@ export abstract class HALEndpointService {
|
||||
|
||||
protected getEndpointMap(): Observable<EndpointMap> {
|
||||
const request = new RootEndpointRequest(this.EnvConfig);
|
||||
setTimeout(() => {
|
||||
this.requestService.configure(request);
|
||||
}, 0);
|
||||
return this.responseCache.get(request.href)
|
||||
// .do((entry: ResponseCacheEntry) => console.log('entry.response', entry.response))
|
||||
.map((entry: ResponseCacheEntry) => entry.response)
|
||||
.filter((response: RootSuccessResponse) => isNotEmpty(response) && isNotEmpty(response.endpointMap))
|
||||
.map((response: RootSuccessResponse) => response.endpointMap)
|
||||
|
@@ -103,23 +103,15 @@ describe('Item', () => {
|
||||
});
|
||||
|
||||
function createRemoteDataObject(object: any) {
|
||||
const self = Observable.of('');
|
||||
const requestPending = Observable.of(false);
|
||||
const responsePending = Observable.of(false);
|
||||
const isSuccessful = Observable.of(true);
|
||||
const errorMessage = Observable.of(undefined);
|
||||
const statusCode = Observable.of('200');
|
||||
const pageInfo = Observable.of(new PageInfo());
|
||||
const payload = Observable.of(object);
|
||||
return new RemoteData(
|
||||
self,
|
||||
requestPending,
|
||||
responsePending,
|
||||
isSuccessful,
|
||||
errorMessage,
|
||||
statusCode,
|
||||
pageInfo,
|
||||
payload
|
||||
);
|
||||
return Observable.of(new RemoteData(
|
||||
'',
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
undefined,
|
||||
'200',
|
||||
new PageInfo(),
|
||||
object
|
||||
));
|
||||
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@ import { DSpaceObject } from './dspace-object.model';
|
||||
import { Collection } from './collection.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Bitstream } from './bitstream.model';
|
||||
import { isNotEmpty } from '../../shared/empty.util';
|
||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||
|
||||
export class Item extends DSpaceObject {
|
||||
|
||||
@@ -36,18 +36,18 @@ export class Item extends DSpaceObject {
|
||||
/**
|
||||
* An array of Collections that are direct parents of this Item
|
||||
*/
|
||||
parents: RemoteData<Collection[]>;
|
||||
parents: Observable<RemoteData<Collection[]>>;
|
||||
|
||||
/**
|
||||
* The Collection that owns this Item
|
||||
*/
|
||||
owningCollection: RemoteData<Collection>;
|
||||
owningCollection: Observable<RemoteData<Collection>>;
|
||||
|
||||
get owner(): RemoteData<Collection> {
|
||||
get owner(): Observable<RemoteData<Collection>> {
|
||||
return this.owningCollection;
|
||||
}
|
||||
|
||||
bitstreams: RemoteData<Bitstream[]>;
|
||||
bitstreams: Observable<RemoteData<Bitstream[]>>;
|
||||
|
||||
/**
|
||||
* Retrieves the thumbnail of this item
|
||||
@@ -87,9 +87,14 @@ export class Item extends DSpaceObject {
|
||||
* @returns {Observable<Bitstream[]>} the bitstreams with the given bundleName
|
||||
*/
|
||||
getBitstreamsByBundleName(bundleName: string): Observable<Bitstream[]> {
|
||||
return this.bitstreams.payload.startWith([])
|
||||
return this.bitstreams
|
||||
.map((rd: RemoteData<Bitstream[]>) => rd.payload)
|
||||
.filter((bitstreams: Bitstream[]) => hasValue(bitstreams))
|
||||
.startWith([])
|
||||
.map((bitstreams) => {
|
||||
return bitstreams.filter((bitstream) => bitstream.bundleName === bundleName)
|
||||
return bitstreams
|
||||
.filter((bitstream) => hasValue(bitstream))
|
||||
.filter((bitstream) => bitstream.bundleName === bundleName)
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<ds-pagination
|
||||
[paginationOptions]="config"
|
||||
[pageInfoState]="pageInfo"
|
||||
[collectionSize]="(pageInfo | async)?.totalElements"
|
||||
[collectionSize]="pageInfo?.totalElements"
|
||||
[sortOptions]="sortConfig"
|
||||
[hideGear]="hideGear"
|
||||
[hidePagerWhenSinglePage]="hidePagerWhenSinglePage"
|
||||
@@ -10,11 +10,9 @@
|
||||
(sortDirectionChange)="onSortDirectionChange($event)"
|
||||
(sortFieldChange)="onSortDirectionChange($event)"
|
||||
(paginationChange)="onPaginationChange($event)">
|
||||
<ul *ngIf="objects.hasSucceeded | async" @fadeIn> <!--class="list-unstyled"-->
|
||||
<li *ngFor="let object of (objects.payload | async) | paginate: { itemsPerPage: (pageInfo | async)?.elementsPerPage, currentPage: (pageInfo | async)?.currentPage, totalItems: (pageInfo | async)?.totalElements }">
|
||||
<ul *ngIf="objects?.hasSucceeded"> <!--class="list-unstyled"-->
|
||||
<li *ngFor="let object of objects?.payload | paginate: { itemsPerPage: pageInfo?.elementsPerPage, currentPage: pageInfo?.currentPage, totalItems: pageInfo?.totalElements }">
|
||||
<ds-wrapper-list-element [object]="object"></ds-wrapper-list-element>
|
||||
</li>
|
||||
</ul>
|
||||
<ds-error *ngIf="objects.hasFailed | async" message="{{'error.objects' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="objects.isLoading | async" message="{{'loading.objects' | translate}}"></ds-loading>
|
||||
</ds-pagination>
|
||||
|
@@ -1,18 +1,12 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
@@ -20,6 +14,7 @@ import { PageInfo } from '../core/shared/page-info.model';
|
||||
import { ListableObject } from '../object-list/listable-object/listable-object.model';
|
||||
|
||||
import { fadeIn } from '../shared/animations/fade';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
|
||||
@@ -31,14 +26,35 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp
|
||||
templateUrl: './object-list.component.html',
|
||||
animations: [fadeIn]
|
||||
})
|
||||
export class ObjectListComponent implements OnChanges, OnInit {
|
||||
export class ObjectListComponent {
|
||||
|
||||
@Input() objects: RemoteData<ListableObject[]>;
|
||||
@Input() config: PaginationComponentOptions;
|
||||
@Input() sortConfig: SortOptions;
|
||||
@Input() hideGear = false;
|
||||
@Input() hidePagerWhenSinglePage = true;
|
||||
pageInfo: Observable<PageInfo>;
|
||||
private _objects: RemoteData<ListableObject[]>;
|
||||
pageInfo: PageInfo;
|
||||
@Input() set objects(objects: RemoteData<ListableObject[]>) {
|
||||
this._objects = objects;
|
||||
if (hasValue(objects)) {
|
||||
this.pageInfo = objects.pageInfo;
|
||||
}
|
||||
}
|
||||
get objects() {
|
||||
return this._objects;
|
||||
}
|
||||
|
||||
/**
|
||||
* An event fired when the page is changed.
|
||||
* Event's payload equals to the newly selected page.
|
||||
*/
|
||||
@Output() change: EventEmitter<{
|
||||
pagination: PaginationComponentOptions,
|
||||
sort: SortOptions
|
||||
}> = new EventEmitter<{
|
||||
pagination: PaginationComponentOptions,
|
||||
sort: SortOptions
|
||||
}>();
|
||||
|
||||
/**
|
||||
* An event fired when the page is changed.
|
||||
@@ -67,26 +83,6 @@ export class ObjectListComponent implements OnChanges, OnInit {
|
||||
@Output() sortFieldChange: EventEmitter<string> = new EventEmitter<string>();
|
||||
data: any = {};
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.objects && !changes.objects.isFirstChange()) {
|
||||
this.pageInfo = this.objects.pageInfo;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.pageInfo = this.objects.pageInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param route
|
||||
* Route is a singleton service provided by Angular.
|
||||
* @param router
|
||||
* Router is a singleton service provided by Angular.
|
||||
*/
|
||||
constructor(
|
||||
private cdRef: ChangeDetectorRef) {
|
||||
}
|
||||
|
||||
onPageChange(event) {
|
||||
this.pageChange.emit(event);
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core';
|
||||
selector: 'ds-pagenotfound',
|
||||
styleUrls: ['./pagenotfound.component.scss'],
|
||||
templateUrl: './pagenotfound.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
changeDetection: ChangeDetectionStrategy.Default
|
||||
})
|
||||
export class PageNotFoundComponent {
|
||||
constructor(responseService: ServerResponseService) {
|
||||
|
@@ -9,61 +9,27 @@ export const MockItem: Item = Object.assign(new Item(), {
|
||||
isArchived: true,
|
||||
isDiscoverable: true,
|
||||
isWithdrawn: false,
|
||||
bitstreams: {
|
||||
self: {
|
||||
_isScalar: true,
|
||||
value: '1507836003548',
|
||||
scheduler: null
|
||||
},
|
||||
requestPending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
responsePending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
isSuccessFul: Observable.create((observer) => {
|
||||
observer.next(true);
|
||||
}),
|
||||
errorMessage: Observable.create((observer) => {
|
||||
observer.next('');
|
||||
}),
|
||||
statusCode: Observable.create((observer) => {
|
||||
observer.next(202);
|
||||
}),
|
||||
pageInfo: Observable.create((observer) => {
|
||||
observer.next({});
|
||||
}),
|
||||
payload: Observable.create((observer) => {
|
||||
observer.next([
|
||||
bitstreams: Observable.of({
|
||||
self: 'dspace-angular://aggregated/object/1507836003548',
|
||||
requestPending: false,
|
||||
responsePending: false,
|
||||
isSuccessFul: true,
|
||||
errorMessage: '',
|
||||
statusCode: '202',
|
||||
pageInfo: {},
|
||||
payload: [
|
||||
{
|
||||
sizeBytes: 10201,
|
||||
content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content',
|
||||
format: {
|
||||
self: {
|
||||
_isScalar: true,
|
||||
value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10',
|
||||
scheduler: null
|
||||
},
|
||||
requestPending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
responsePending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
isSuccessFul: Observable.create((observer) => {
|
||||
observer.next(true);
|
||||
}),
|
||||
errorMessage: Observable.create((observer) => {
|
||||
observer.next('');
|
||||
}),
|
||||
statusCode: Observable.create((observer) => {
|
||||
observer.next(202);
|
||||
}),
|
||||
pageInfo: Observable.create((observer) => {
|
||||
observer.next({});
|
||||
}),
|
||||
payload: Observable.create((observer) => {
|
||||
observer.next({
|
||||
format: Observable.of({
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10',
|
||||
requestPending: false,
|
||||
responsePending: false,
|
||||
isSuccessFul: true,
|
||||
errorMessage: '',
|
||||
statusCode: '202',
|
||||
pageInfo: {},
|
||||
payload: {
|
||||
shortDescription: 'Microsoft Word XML',
|
||||
description: 'Microsoft Word XML',
|
||||
mimetype: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
@@ -71,9 +37,8 @@ export const MockItem: Item = Object.assign(new Item(), {
|
||||
internal: false,
|
||||
extensions: null,
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10'
|
||||
});
|
||||
})
|
||||
},
|
||||
}
|
||||
}),
|
||||
bundleName: 'ORIGINAL',
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713',
|
||||
id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713',
|
||||
@@ -91,32 +56,15 @@ export const MockItem: Item = Object.assign(new Item(), {
|
||||
{
|
||||
sizeBytes: 31302,
|
||||
content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content',
|
||||
format: {
|
||||
self: {
|
||||
_isScalar: true,
|
||||
value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4',
|
||||
scheduler: null
|
||||
},
|
||||
requestPending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
responsePending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
isSuccessFul: Observable.create((observer) => {
|
||||
observer.next(true);
|
||||
}),
|
||||
errorMessage: Observable.create((observer) => {
|
||||
observer.next('');
|
||||
}),
|
||||
statusCode: Observable.create((observer) => {
|
||||
observer.next(202);
|
||||
}),
|
||||
pageInfo: Observable.create((observer) => {
|
||||
observer.next({});
|
||||
}),
|
||||
payload: Observable.create((observer) => {
|
||||
observer.next({
|
||||
format: Observable.of({
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4',
|
||||
requestPending: false,
|
||||
responsePending: false,
|
||||
isSuccessFul: true,
|
||||
errorMessage: '',
|
||||
statusCode: '202',
|
||||
pageInfo: {},
|
||||
payload: {
|
||||
shortDescription: 'Adobe PDF',
|
||||
description: 'Adobe Portable Document Format',
|
||||
mimetype: 'application/pdf',
|
||||
@@ -124,9 +72,8 @@ export const MockItem: Item = Object.assign(new Item(), {
|
||||
internal: false,
|
||||
extensions: null,
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4'
|
||||
});
|
||||
})
|
||||
},
|
||||
}
|
||||
}),
|
||||
bundleName: 'ORIGINAL',
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28',
|
||||
id: '99b00f3c-1cc6-4689-8158-91965bee6b28',
|
||||
@@ -141,9 +88,8 @@ export const MockItem: Item = Object.assign(new Item(), {
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
})
|
||||
},
|
||||
]
|
||||
}),
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357',
|
||||
id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357',
|
||||
uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357',
|
||||
@@ -241,33 +187,15 @@ export const MockItem: Item = Object.assign(new Item(), {
|
||||
value: 'text'
|
||||
}
|
||||
],
|
||||
owningCollection: {
|
||||
self: {
|
||||
_isScalar: true,
|
||||
value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb',
|
||||
scheduler: null
|
||||
},
|
||||
requestPending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
responsePending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
isSuccessFul: Observable.create((observer) => {
|
||||
observer.next(true);
|
||||
}),
|
||||
errorMessage: Observable.create((observer) => {
|
||||
observer.next('');
|
||||
}),
|
||||
statusCode: Observable.create((observer) => {
|
||||
observer.next(202);
|
||||
}),
|
||||
pageInfo: Observable.create((observer) => {
|
||||
observer.next({});
|
||||
}),
|
||||
payload: Observable.create((observer) => {
|
||||
observer.next([]);
|
||||
})
|
||||
owningCollection: Observable.of({
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb',
|
||||
requestPending: false,
|
||||
responsePending: false,
|
||||
isSuccessFul: true,
|
||||
errorMessage: '',
|
||||
statusCode: '202',
|
||||
pageInfo: {},
|
||||
payload: []
|
||||
}
|
||||
})
|
||||
)});
|
||||
/* tslint:enable:no-shadowed-variable */
|
||||
|
@@ -1,11 +1,11 @@
|
||||
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="row">
|
||||
<div *ngIf="isNotEmpty(scopes | async)" class="col-12 col-sm-3">
|
||||
<div *ngIf="isNotEmpty(scopes)" class="col-12 col-sm-3">
|
||||
<select [(ngModel)]="selectedId" name="scope" class="form-control" aria-label="Search scope" [compareWith]="byId">
|
||||
<option value>{{'search.form.search_dspace' | translate}}</option>
|
||||
<option *ngFor="let scopeOption of scopes | async" [value]="scopeOption.id">{{scopeOption?.name ? scopeOption.name : 'search.form.search_dspace' | translate}}</option>
|
||||
<option *ngFor="let scopeOption of scopes" [value]="scopeOption.id">{{scopeOption?.name ? scopeOption.name : 'search.form.search_dspace' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div [ngClass]="{'col-sm-9': isNotEmpty(scopes | async)}" class="col-12">
|
||||
<div [ngClass]="{'col-sm-9': isNotEmpty(scopes)}" class="col-12">
|
||||
<div class="form-group input-group">
|
||||
<input type="text" [(ngModel)]="query" name="query" class="form-control" aria-label="Search input">
|
||||
<span class="input-group-btn">
|
||||
|
@@ -8,6 +8,7 @@ import { ResourceType } from '../../core/shared/resource-type';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
|
||||
describe('SearchFormComponent', () => {
|
||||
let comp: SearchFormComponent;
|
||||
@@ -30,7 +31,7 @@ describe('SearchFormComponent', () => {
|
||||
});
|
||||
|
||||
it('should display scopes when available with default and all scopes', () => {
|
||||
comp.scopes = Observable.of(objects);
|
||||
comp.scopes = objects;
|
||||
fixture.detectChanges();
|
||||
const select: HTMLElement = de.query(By.css('select')).nativeElement;
|
||||
expect(select).toBeDefined();
|
||||
@@ -64,7 +65,7 @@ describe('SearchFormComponent', () => {
|
||||
}));
|
||||
|
||||
it('should select correct scope option in scope select', fakeAsync(() => {
|
||||
comp.scopes = Observable.of(objects);
|
||||
comp.scopes = objects;
|
||||
fixture.detectChanges();
|
||||
|
||||
const testCommunity = objects[1];
|
||||
@@ -100,7 +101,7 @@ describe('SearchFormComponent', () => {
|
||||
// }));
|
||||
});
|
||||
|
||||
export const objects = [
|
||||
export const objects: DSpaceObject[] = [
|
||||
Object.assign(new Community(), {
|
||||
handle: '10673/11',
|
||||
logo: {
|
||||
|
@@ -20,7 +20,7 @@ export class SearchFormComponent {
|
||||
selectedId = '';
|
||||
// Optional existing search parameters
|
||||
@Input() currentParams: {};
|
||||
@Input() scopes: Observable<DSpaceObject[]>;
|
||||
@Input() scopes: DSpaceObject[];
|
||||
|
||||
@Input()
|
||||
set scope(dso: DSpaceObject) {
|
||||
|
Reference in New Issue
Block a user