mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'main' into CST-3620
This commit is contained in:
@@ -14,7 +14,8 @@ module.exports = function (config) {
|
||||
require('karma-mocha-reporter'),
|
||||
],
|
||||
client: {
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
clearContext: false, // leave Jasmine Spec Runner output visible in browser
|
||||
captureConsole: false
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/dspace-angular'),
|
||||
|
@@ -171,7 +171,7 @@ function ngApp(req, res) {
|
||||
} else {
|
||||
// If preboot is disabled, just serve the client
|
||||
console.log('Universal off, serving for direct CSR');
|
||||
res.sendFile(indexHtml);
|
||||
res.sendFile(DIST_FOLDER + '/index.html');
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,7 @@ import { FormBuilder } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
|
||||
import { filter, map, switchMap, take } from 'rxjs/operators';
|
||||
import { map, switchMap, take } from 'rxjs/operators';
|
||||
import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
|
||||
@@ -15,8 +15,8 @@ import { EpersonDtoModel } from '../../../core/eperson/models/eperson-dto.model'
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
getFirstCompletedRemoteData
|
||||
getFirstCompletedRemoteData,
|
||||
getAllSucceededRemoteData
|
||||
} from '../../../core/shared/operators';
|
||||
import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
@@ -39,7 +39,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* A list of all the current EPeople within the repository or the result of the search
|
||||
*/
|
||||
ePeople$: BehaviorSubject<RemoteData<PaginatedList<EPerson>>> = new BehaviorSubject<RemoteData<PaginatedList<EPerson>>>({} as any);
|
||||
ePeople$: BehaviorSubject<PaginatedList<EPerson>> = new BehaviorSubject(buildPaginatedList<EPerson>(new PageInfo(), []));
|
||||
/**
|
||||
* A BehaviorSubject with the list of EpersonDtoModel objects made from the EPeople in the repository or
|
||||
* as the result of the search
|
||||
@@ -72,6 +72,11 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
currentSearchQuery: string;
|
||||
currentSearchScope: string;
|
||||
|
||||
/**
|
||||
* The subscription for the search method
|
||||
*/
|
||||
searchSub: Subscription;
|
||||
|
||||
/**
|
||||
* List of subscriptions
|
||||
*/
|
||||
@@ -108,6 +113,29 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
this.isEPersonFormShown = true;
|
||||
}
|
||||
}));
|
||||
this.subs.push(this.ePeople$.pipe(
|
||||
switchMap((epeople: PaginatedList<EPerson>) => {
|
||||
if (epeople.pageInfo.totalElements > 0) {
|
||||
return combineLatest(...epeople.page.map((eperson) => {
|
||||
return this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined).pipe(
|
||||
map((authorized) => {
|
||||
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
||||
epersonDtoModel.ableToDelete = authorized;
|
||||
epersonDtoModel.eperson = eperson;
|
||||
return epersonDtoModel;
|
||||
})
|
||||
);
|
||||
})).pipe(map((dtos: EpersonDtoModel[]) => {
|
||||
return buildPaginatedList(epeople.pageInfo, dtos);
|
||||
}));
|
||||
} else {
|
||||
// if it's empty, simply forward the empty list
|
||||
return [epeople];
|
||||
}
|
||||
})).subscribe((value: PaginatedList<EpersonDtoModel>) => {
|
||||
this.ePeopleDto$.next(value);
|
||||
this.pageInfoState$.next(value.pageInfo);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,34 +166,21 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
this.currentSearchScope = scope;
|
||||
this.config.currentPage = 1;
|
||||
}
|
||||
this.subs.push(this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
|
||||
if (hasValue(this.searchSub)) {
|
||||
this.searchSub.unsubscribe();
|
||||
this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub);
|
||||
}
|
||||
this.searchSub = this.epersonService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
|
||||
currentPage: this.config.currentPage,
|
||||
elementsPerPage: this.config.pageSize
|
||||
}).subscribe((peopleRD) => {
|
||||
this.ePeople$.next(peopleRD);
|
||||
}).pipe(
|
||||
getAllSucceededRemoteData(),
|
||||
).subscribe((peopleRD) => {
|
||||
this.ePeople$.next(peopleRD.payload);
|
||||
this.pageInfoState$.next(peopleRD.payload.pageInfo);
|
||||
}
|
||||
));
|
||||
|
||||
this.subs.push(this.ePeople$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
switchMap((epeople) => {
|
||||
return combineLatest(...epeople.page.map((eperson) => {
|
||||
return this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(eperson) ? eperson.self : undefined).pipe(
|
||||
map((authorized) => {
|
||||
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
||||
epersonDtoModel.ableToDelete = authorized;
|
||||
epersonDtoModel.eperson = eperson;
|
||||
return epersonDtoModel;
|
||||
})
|
||||
);
|
||||
})).pipe(map((dtos: EpersonDtoModel[]) => {
|
||||
return buildPaginatedList(epeople.pageInfo, dtos);
|
||||
}));
|
||||
})).subscribe((value) => {
|
||||
this.ePeopleDto$.next(value);
|
||||
this.pageInfoState$.next(value.pageInfo);
|
||||
}));
|
||||
this.subs.push(this.searchSub);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,7 +239,8 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
|
||||
}
|
||||
});
|
||||
}}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -261,16 +277,16 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will ensure that the page gets reset and that the cache is cleared
|
||||
* This method will set everything to stale, which will cause the lists on this page to update.
|
||||
*/
|
||||
reset() {
|
||||
this.epersonService.getBrowseEndpoint().pipe(
|
||||
switchMap((href) => this.requestService.removeByHrefSubstring(href)),
|
||||
filter((isCached) => isCached),
|
||||
take(1)
|
||||
).subscribe(() => {
|
||||
this.cleanupSubscribes();
|
||||
this.initialisePage();
|
||||
).subscribe((href: string) => {
|
||||
this.requestService.setStaleByHrefSubstring(href).pipe(take(1)).subscribe(() => {
|
||||
this.epersonService.cancelEditEPerson();
|
||||
this.isEPersonFormShown = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -359,7 +359,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
const response = this.epersonService.updateEPerson(editedEperson);
|
||||
response.pipe(take(1)).subscribe((rd: RemoteData<EPerson>) => {
|
||||
response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<EPerson>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.edited.success', { name: editedEperson.name }));
|
||||
this.submitForm.emit(editedEperson);
|
||||
@@ -439,10 +439,10 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
||||
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
|
||||
if (confirm) {
|
||||
if (hasValue(eperson.id)) {
|
||||
this.epersonService.deleteEPerson(eperson).pipe(take(1)).subscribe((restResponse: RemoteData<NoContent>) => {
|
||||
this.epersonService.deleteEPerson(eperson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData<NoContent>) => {
|
||||
if (restResponse.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', { name: eperson.name }));
|
||||
this.reset();
|
||||
this.submitForm.emit();
|
||||
} else {
|
||||
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + eperson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
|
||||
}
|
||||
|
@@ -345,7 +345,7 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
||||
this.groupDataService.getActiveGroup().pipe(take(1)).subscribe((activeGroup: Group) => {
|
||||
if (activeGroup === null) {
|
||||
this.groupDataService.cancelEditGroup();
|
||||
this.groupDataService.findByHref(groupSelfLink, false, followLink('subgroups'), followLink('epersons'), followLink('object'))
|
||||
this.groupDataService.findByHref(groupSelfLink, false, false, followLink('subgroups'), followLink('epersons'), followLink('object'))
|
||||
.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload())
|
||||
|
@@ -24,10 +24,10 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ds-pagination *ngIf="(ePeopleSearch | async)?.payload?.totalElements > 0"
|
||||
<ds-pagination *ngIf="(searchResults$ | async)?.payload?.totalElements > 0"
|
||||
[paginationOptions]="configSearch"
|
||||
[pageInfoState]="(ePeopleSearch | async)?.payload"
|
||||
[collectionSize]="(ePeopleSearch | async)?.payload?.totalElements"
|
||||
[pageInfoState]="(searchResults$ | async)?.payload"
|
||||
[collectionSize]="(searchResults$ | async)?.payload?.totalElements"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true"
|
||||
(pageChange)="onPageChangeSearch($event)">
|
||||
@@ -42,7 +42,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let ePerson of (ePeopleSearch | async)?.payload?.page">
|
||||
<tr *ngFor="let ePerson of (searchResults$ | async)?.payload?.page">
|
||||
<td>{{ePerson.id}}</td>
|
||||
<td><a (click)="ePersonDataService.startEditingNewEPerson(ePerson)"
|
||||
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">{{ePerson.name}}</a></td>
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
</ds-pagination>
|
||||
|
||||
<div *ngIf="(ePeopleSearch | async)?.payload?.totalElements == 0 && searchDone"
|
||||
<div *ngIf="(searchResults$ | async)?.payload?.totalElements == 0 && searchDone"
|
||||
class="alert alert-info w-100 mb-2"
|
||||
role="alert">
|
||||
{{messagePrefix + '.no-items' | translate}}
|
||||
@@ -78,10 +78,10 @@
|
||||
|
||||
<h4>{{messagePrefix + '.headMembers' | translate}}</h4>
|
||||
|
||||
<ds-pagination *ngIf="(ePeopleMembersOfGroup | async)?.payload?.totalElements > 0"
|
||||
<ds-pagination *ngIf="(members$ | async)?.payload?.totalElements > 0"
|
||||
[paginationOptions]="config"
|
||||
[pageInfoState]="(ePeopleMembersOfGroup | async)?.payload"
|
||||
[collectionSize]="(ePeopleMembersOfGroup | async)?.payload?.totalElements"
|
||||
[pageInfoState]="(members$ | async)?.payload"
|
||||
[collectionSize]="(members$ | async)?.payload?.totalElements"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true"
|
||||
(pageChange)="onPageChange($event)">
|
||||
@@ -96,7 +96,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let ePerson of (ePeopleMembersOfGroup | async)?.payload?.page">
|
||||
<tr *ngFor="let ePerson of (members$ | async)?.payload?.page">
|
||||
<td>{{ePerson.id}}</td>
|
||||
<td><a (click)="ePersonDataService.startEditingNewEPerson(ePerson)"
|
||||
[routerLink]="[ePersonDataService.getEPeoplePageRouterLink()]">{{ePerson.name}}</a></td>
|
||||
@@ -116,7 +116,7 @@
|
||||
|
||||
</ds-pagination>
|
||||
|
||||
<div *ngIf="(ePeopleMembersOfGroup | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2"
|
||||
<div *ngIf="(members$ | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2"
|
||||
role="alert">
|
||||
{{messagePrefix + '.no-members-yet' | translate}}
|
||||
</div>
|
||||
|
@@ -2,7 +2,7 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||
import { Observable, of as observableOf, Subscription, BehaviorSubject } from 'rxjs';
|
||||
import { map, mergeMap, take } from 'rxjs/operators';
|
||||
import { PaginatedList } from '../../../../../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||
@@ -15,10 +15,18 @@ import {
|
||||
getFirstSucceededRemoteData,
|
||||
getFirstCompletedRemoteData
|
||||
} from '../../../../../core/shared/operators';
|
||||
import { hasValue } from '../../../../../shared/empty.util';
|
||||
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
|
||||
import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
|
||||
|
||||
/**
|
||||
* Keys to keep track of specific subscriptions
|
||||
*/
|
||||
enum SubKey {
|
||||
Members,
|
||||
ActiveGroup,
|
||||
SearchResults,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ds-members-list',
|
||||
templateUrl: './members-list.component.html'
|
||||
@@ -34,11 +42,11 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* EPeople being displayed in search result, initially all members, after search result of search
|
||||
*/
|
||||
ePeopleSearch: Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||
searchResults$: BehaviorSubject<RemoteData<PaginatedList<EPerson>>> = new BehaviorSubject(undefined);
|
||||
/**
|
||||
* List of EPeople members of currently active group being edited
|
||||
*/
|
||||
ePeopleMembersOfGroup: Observable<RemoteData<PaginatedList<EPerson>>>;
|
||||
members$: BehaviorSubject<RemoteData<PaginatedList<EPerson>>> = new BehaviorSubject(undefined);
|
||||
|
||||
/**
|
||||
* Pagination config used to display the list of EPeople that are result of EPeople search
|
||||
@@ -58,9 +66,9 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
/**
|
||||
* List of subscriptions
|
||||
* Map of active subscriptions
|
||||
*/
|
||||
subs: Subscription[] = [];
|
||||
subs: Map<SubKey, Subscription> = new Map();
|
||||
|
||||
// The search form
|
||||
searchForm;
|
||||
@@ -90,10 +98,10 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
||||
scope: 'metadata',
|
||||
query: '',
|
||||
}));
|
||||
this.subs.push(this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
||||
this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
||||
if (activeGroup != null) {
|
||||
this.groupBeingEdited = activeGroup;
|
||||
this.forceUpdateEPeople(activeGroup);
|
||||
this.retrieveMembers(this.config.currentPage);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -112,10 +120,40 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
||||
* @param event
|
||||
*/
|
||||
onPageChange(event) {
|
||||
this.ePeopleMembersOfGroup = this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, {
|
||||
currentPage: event,
|
||||
this.retrieveMembers(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the EPersons that are members of the group
|
||||
*
|
||||
* @param page the number of the page to retrieve
|
||||
* @private
|
||||
*/
|
||||
private retrieveMembers(page: number) {
|
||||
this.unsubFrom(SubKey.Members);
|
||||
this.subs.set(
|
||||
SubKey.Members,
|
||||
this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, {
|
||||
currentPage: page,
|
||||
elementsPerPage: this.config.pageSize
|
||||
});
|
||||
}
|
||||
).subscribe((rd: RemoteData<PaginatedList<EPerson>>) => {
|
||||
this.members$.next(rd);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from a subscription if it's still subscribed, and remove it from the map of
|
||||
* active subscriptions
|
||||
*
|
||||
* @param key The key of the subscription to unsubscribe from
|
||||
* @private
|
||||
*/
|
||||
private unsubFrom(key: SubKey) {
|
||||
if (this.subs.has(key)) {
|
||||
this.subs.get(key).unsubscribe();
|
||||
this.subs.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +165,6 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
||||
if (activeGroup != null) {
|
||||
const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson);
|
||||
this.showNotifications('deleteMember', response, ePerson.name, activeGroup);
|
||||
this.forceUpdateEPeople(activeGroup);
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||
}
|
||||
@@ -147,7 +184,6 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||
}
|
||||
});
|
||||
this.forceUpdateEPeople(this.groupBeingEdited, ePerson);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,8 +195,8 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
||||
mergeMap((group: Group) => {
|
||||
if (group != null) {
|
||||
return this.ePersonDataService.findAllByHref(group._links.epersons.href, {
|
||||
currentPage: 0,
|
||||
elementsPerPage: Number.MAX_SAFE_INTEGER
|
||||
currentPage: 1,
|
||||
elementsPerPage: 9999
|
||||
})
|
||||
.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
@@ -191,34 +227,23 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
||||
this.configSearch.currentPage = 1;
|
||||
}
|
||||
this.searchDone = true;
|
||||
this.ePeopleSearch = this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
|
||||
currentPage: this.configSearch.currentPage,
|
||||
elementsPerPage: this.configSearch.pageSize
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Force-update the list of EPeople by first clearing the cache related to EPeople, then performing
|
||||
* a new REST call
|
||||
* @param activeGroup Group currently being edited
|
||||
*/
|
||||
public forceUpdateEPeople(activeGroup: Group, ePersonToUpdate?: EPerson) {
|
||||
if (ePersonToUpdate != null) {
|
||||
this.ePersonDataService.clearLinkRequests(ePersonToUpdate._links.groups.href);
|
||||
}
|
||||
this.ePersonDataService.clearLinkRequests(activeGroup._links.epersons.href);
|
||||
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(activeGroup));
|
||||
this.ePeopleMembersOfGroup = this.ePersonDataService.findAllByHref(activeGroup._links.epersons.href, {
|
||||
this.unsubFrom(SubKey.SearchResults);
|
||||
this.subs.set(SubKey.SearchResults, this.ePersonDataService.searchByScope(this.currentSearchScope, this.currentSearchQuery, {
|
||||
currentPage: this.configSearch.currentPage,
|
||||
elementsPerPage: this.configSearch.pageSize
|
||||
});
|
||||
}).subscribe((rd: RemoteData<PaginatedList<EPerson>>) => {
|
||||
this.searchResults$.next(rd);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* unsub all subscriptions
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
for (const key of this.subs.keys()) {
|
||||
this.unsubFrom(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,6 +257,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
||||
response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<any>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject }));
|
||||
this.ePersonDataService.clearLinkRequests(activeGroup._links.epersons.href);
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject }));
|
||||
}
|
||||
|
@@ -18,10 +18,10 @@
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ds-pagination *ngIf="(groupsSearch | async)?.payload?.totalElements > 0"
|
||||
<ds-pagination *ngIf="(searchResults$ | async)?.payload?.totalElements > 0"
|
||||
[paginationOptions]="configSearch"
|
||||
[pageInfoState]="(groupsSearch | async)?.payload"
|
||||
[collectionSize]="(groupsSearch | async)?.payload?.totalElements"
|
||||
[pageInfoState]="(searchResults$ | async)?.payload"
|
||||
[collectionSize]="(searchResults$ | async)?.payload?.totalElements"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true"
|
||||
(pageChange)="onPageChangeSearch($event)">
|
||||
@@ -36,7 +36,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let group of (groupsSearch | async)?.payload?.page">
|
||||
<tr *ngFor="let group of (searchResults$ | async)?.payload?.page">
|
||||
<td>{{group.id}}</td>
|
||||
<td><a (click)="groupDataService.startEditingNewGroup(group)"
|
||||
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">{{group.name}}</a></td>
|
||||
@@ -65,17 +65,17 @@
|
||||
</div>
|
||||
</ds-pagination>
|
||||
|
||||
<div *ngIf="(groupsSearch | async)?.payload?.totalElements == 0 && searchDone" class="alert alert-info w-100 mb-2"
|
||||
<div *ngIf="(searchResults$ | async)?.payload?.totalElements == 0 && searchDone" class="alert alert-info w-100 mb-2"
|
||||
role="alert">
|
||||
{{messagePrefix + '.no-items' | translate}}
|
||||
</div>
|
||||
|
||||
<h4>{{messagePrefix + '.headSubgroups' | translate}}</h4>
|
||||
|
||||
<ds-pagination *ngIf="(subgroupsOfGroup | async)?.payload?.totalElements > 0"
|
||||
<ds-pagination *ngIf="(subGroups$ | async)?.payload?.totalElements > 0"
|
||||
[paginationOptions]="config"
|
||||
[pageInfoState]="(subgroupsOfGroup | async)?.payload"
|
||||
[collectionSize]="(subgroupsOfGroup | async)?.payload?.totalElements"
|
||||
[pageInfoState]="(subGroups$ | async)?.payload"
|
||||
[collectionSize]="(subGroups$ | async)?.payload?.totalElements"
|
||||
[hideGear]="true"
|
||||
[hidePagerWhenSinglePage]="true"
|
||||
(pageChange)="onPageChange($event)">
|
||||
@@ -90,7 +90,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let group of (subgroupsOfGroup | async)?.payload?.page">
|
||||
<tr *ngFor="let group of (subGroups$ | async)?.payload?.page">
|
||||
<td>{{group.id}}</td>
|
||||
<td><a (click)="groupDataService.startEditingNewGroup(group)"
|
||||
[routerLink]="[groupDataService.getGroupEditPageRouterLink(group)]">{{group.name}}</a></td>
|
||||
@@ -109,7 +109,7 @@
|
||||
</div>
|
||||
</ds-pagination>
|
||||
|
||||
<div *ngIf="(subgroupsOfGroup | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2"
|
||||
<div *ngIf="(subGroups$ | async)?.payload?.totalElements == 0" class="alert alert-info w-100 mb-2"
|
||||
role="alert">
|
||||
{{messagePrefix + '.no-subgroups-yet' | translate}}
|
||||
</div>
|
||||
|
@@ -1,12 +1,20 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, fakeAsync, flush, inject, TestBed, tick, waitForAsync } from '@angular/core/testing';
|
||||
import {
|
||||
ComponentFixture,
|
||||
fakeAsync,
|
||||
flush,
|
||||
inject,
|
||||
TestBed,
|
||||
tick,
|
||||
waitForAsync
|
||||
} from '@angular/core/testing';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { BrowserModule, By } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { Observable, of as observableOf, BehaviorSubject } from 'rxjs';
|
||||
import { RestResponse } from '../../../../../core/cache/response.models';
|
||||
import { buildPaginatedList, PaginatedList } from '../../../../../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||
@@ -17,12 +25,16 @@ import { FormBuilderService } from '../../../../../shared/form/builder/form-buil
|
||||
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
|
||||
import { GroupMock, GroupMock2 } from '../../../../../shared/testing/group-mock';
|
||||
import { SubgroupsListComponent } from './subgroups-list.component';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
||||
import {
|
||||
createSuccessfulRemoteDataObject$,
|
||||
createSuccessfulRemoteDataObject
|
||||
} from '../../../../../shared/remote-data.utils';
|
||||
import { RouterMock } from '../../../../../shared/mocks/router.mock';
|
||||
import { getMockFormBuilderService } from '../../../../../shared/mocks/form-builder-service.mock';
|
||||
import { getMockTranslateService } from '../../../../../shared/mocks/translate.service.mock';
|
||||
import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
|
||||
import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service.stub';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
describe('SubgroupsListComponent', () => {
|
||||
let component: SubgroupsListComponent;
|
||||
@@ -43,7 +55,7 @@ describe('SubgroupsListComponent', () => {
|
||||
ePersonDataServiceStub = {};
|
||||
groupsDataServiceStub = {
|
||||
activeGroup: activeGroup,
|
||||
subgroups: subgroups,
|
||||
subgroups$: new BehaviorSubject(subgroups),
|
||||
getActiveGroup(): Observable<Group> {
|
||||
return observableOf(this.activeGroup);
|
||||
},
|
||||
@@ -51,7 +63,11 @@ describe('SubgroupsListComponent', () => {
|
||||
return this.activeGroup;
|
||||
},
|
||||
findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||
return createSuccessfulRemoteDataObject$(buildPaginatedList<Group>(new PageInfo(), this.subgroups));
|
||||
return this.subgroups$.pipe(
|
||||
map((currentGroups: Group[]) => {
|
||||
return createSuccessfulRemoteDataObject(buildPaginatedList<Group>(new PageInfo(), currentGroups));
|
||||
})
|
||||
);
|
||||
},
|
||||
getGroupEditPageRouterLink(group: Group): string {
|
||||
return '/admin/access-control/groups/' + group.id;
|
||||
@@ -63,7 +79,7 @@ describe('SubgroupsListComponent', () => {
|
||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []));
|
||||
},
|
||||
addSubGroupToGroup(parentGroup, subgroup: Group): Observable<RestResponse> {
|
||||
this.subgroups = [...this.subgroups, subgroup];
|
||||
this.subgroups$.next([...this.subgroups$.getValue(), subgroup]);
|
||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||
},
|
||||
clearGroupsRequests() {
|
||||
@@ -73,11 +89,11 @@ describe('SubgroupsListComponent', () => {
|
||||
// empty
|
||||
},
|
||||
deleteSubGroupFromGroup(parentGroup, subgroup: Group): Observable<RestResponse> {
|
||||
this.subgroups = this.subgroups.find((group: Group) => {
|
||||
this.subgroups$.next(this.subgroups$.getValue().filter((group: Group) => {
|
||||
if (group.id !== subgroup.id) {
|
||||
return group;
|
||||
}
|
||||
});
|
||||
}));
|
||||
return observableOf(new RestResponse(true, 200, 'Success'));
|
||||
}
|
||||
};
|
||||
|
@@ -2,7 +2,7 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable, of as observableOf, Subscription } from 'rxjs';
|
||||
import { Observable, of as observableOf, Subscription, BehaviorSubject } from 'rxjs';
|
||||
import { map, mergeMap, take } from 'rxjs/operators';
|
||||
import { PaginatedList } from '../../../../../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||
@@ -13,9 +13,18 @@ import {
|
||||
getFirstSucceededRemoteData,
|
||||
getFirstCompletedRemoteData
|
||||
} from '../../../../../core/shared/operators';
|
||||
import { hasValue } from '../../../../../shared/empty.util';
|
||||
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
|
||||
import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
|
||||
import { NoContent } from '../../../../../core/shared/NoContent.model';
|
||||
|
||||
/**
|
||||
* Keys to keep track of specific subscriptions
|
||||
*/
|
||||
enum SubKey {
|
||||
Members,
|
||||
ActiveGroup,
|
||||
SearchResults,
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'ds-subgroups-list',
|
||||
@@ -32,16 +41,16 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Result of search groups, initially all groups
|
||||
*/
|
||||
groupsSearch: Observable<RemoteData<PaginatedList<Group>>>;
|
||||
searchResults$: BehaviorSubject<RemoteData<PaginatedList<Group>>> = new BehaviorSubject(undefined);
|
||||
/**
|
||||
* List of all subgroups of group being edited
|
||||
*/
|
||||
subgroupsOfGroup: Observable<RemoteData<PaginatedList<Group>>>;
|
||||
subGroups$: BehaviorSubject<RemoteData<PaginatedList<Group>>> = new BehaviorSubject(undefined);
|
||||
|
||||
/**
|
||||
* List of subscriptions
|
||||
* Map of active subscriptions
|
||||
*/
|
||||
subs: Subscription[] = [];
|
||||
subs: Map<SubKey, Subscription> = new Map();
|
||||
|
||||
/**
|
||||
* Pagination config used to display the list of groups that are result of groups search
|
||||
@@ -84,10 +93,10 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
||||
this.searchForm = this.formBuilder.group(({
|
||||
query: '',
|
||||
}));
|
||||
this.subs.push(this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
||||
this.subs.set(SubKey.ActiveGroup, this.groupDataService.getActiveGroup().subscribe((activeGroup: Group) => {
|
||||
if (activeGroup != null) {
|
||||
this.groupBeingEdited = activeGroup;
|
||||
this.forceUpdateGroups(activeGroup);
|
||||
this.retrieveSubGroups(this.config.currentPage);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -106,10 +115,26 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
||||
* @param event
|
||||
*/
|
||||
onPageChange(event) {
|
||||
this.subgroupsOfGroup = this.groupDataService.findAllByHref(this.groupBeingEdited._links.subgroups.href, {
|
||||
currentPage: event,
|
||||
this.retrieveSubGroups(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Subgroups that are members of the group
|
||||
*
|
||||
* @param page the number of the page to retrieve
|
||||
* @private
|
||||
*/
|
||||
private retrieveSubGroups(page: number) {
|
||||
this.unsubFrom(SubKey.Members);
|
||||
this.subs.set(
|
||||
SubKey.Members,
|
||||
this.groupDataService.findAllByHref(this.groupBeingEdited._links.subgroups.href, {
|
||||
currentPage: page,
|
||||
elementsPerPage: this.config.pageSize
|
||||
});
|
||||
}
|
||||
).subscribe((rd: RemoteData<PaginatedList<Group>>) => {
|
||||
this.subGroups$.next(rd);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,8 +149,8 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
||||
return observableOf(false);
|
||||
} else {
|
||||
return this.groupDataService.findAllByHref(activeGroup._links.subgroups.href, {
|
||||
currentPage: 0,
|
||||
elementsPerPage: Number.MAX_SAFE_INTEGER
|
||||
currentPage: 1,
|
||||
elementsPerPage: 9999
|
||||
})
|
||||
.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
@@ -162,7 +187,6 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
||||
if (activeGroup != null) {
|
||||
const response = this.groupDataService.deleteSubGroupFromGroup(activeGroup, subgroup);
|
||||
this.showNotifications('deleteSubgroup', response, subgroup.name, activeGroup);
|
||||
this.forceUpdateGroups(activeGroup);
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||
}
|
||||
@@ -186,7 +210,6 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||
}
|
||||
});
|
||||
this.forceUpdateGroups(this.groupBeingEdited);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,30 +224,37 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
||||
this.configSearch.currentPage = 1;
|
||||
}
|
||||
this.searchDone = true;
|
||||
this.groupsSearch = this.groupDataService.searchGroups(this.currentSearchQuery, {
|
||||
|
||||
this.unsubFrom(SubKey.SearchResults);
|
||||
this.subs.set(SubKey.SearchResults, this.groupDataService.searchGroups(this.currentSearchQuery, {
|
||||
currentPage: this.configSearch.currentPage,
|
||||
elementsPerPage: this.configSearch.pageSize
|
||||
});
|
||||
}).subscribe((rd: RemoteData<PaginatedList<Group>>) => {
|
||||
this.searchResults$.next(rd);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Force-update the list of groups by first clearing the cache of results of this active groups' subgroups, then performing a new REST call
|
||||
* @param activeGroup Group currently being edited
|
||||
* Unsubscribe from a subscription if it's still subscribed, and remove it from the map of
|
||||
* active subscriptions
|
||||
*
|
||||
* @param key The key of the subscription to unsubscribe from
|
||||
* @private
|
||||
*/
|
||||
public forceUpdateGroups(activeGroup: Group) {
|
||||
this.groupDataService.clearGroupLinkRequests(activeGroup._links.subgroups.href);
|
||||
this.router.navigateByUrl(this.groupDataService.getGroupEditPageRouterLink(activeGroup));
|
||||
this.subgroupsOfGroup = this.groupDataService.findAllByHref(activeGroup._links.subgroups.href, {
|
||||
currentPage: this.config.currentPage,
|
||||
elementsPerPage: this.config.pageSize
|
||||
});
|
||||
private unsubFrom(key: SubKey) {
|
||||
if (this.subs.has(key)) {
|
||||
this.subs.get(key).unsubscribe();
|
||||
this.subs.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* unsub all subscriptions
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||
for (const key of this.subs.keys()) {
|
||||
this.unsubFrom(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,10 +264,11 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
||||
* @param nameObject Object request was about
|
||||
* @param activeGroup Group currently being edited
|
||||
*/
|
||||
showNotifications(messageSuffix: string, response: Observable<RemoteData<Group>>, nameObject: string, activeGroup: Group) {
|
||||
showNotifications(messageSuffix: string, response: Observable<RemoteData<Group|NoContent>>, nameObject: string, activeGroup: Group) {
|
||||
response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<Group>) => {
|
||||
if (rd.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject }));
|
||||
this.groupDataService.clearGroupLinkRequests(activeGroup._links.subgroups.href);
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject }));
|
||||
}
|
||||
|
@@ -2,8 +2,13 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription, Observable, ObservedValueOf, of as observableOf } from 'rxjs';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest as observableCombineLatest,
|
||||
Subscription,
|
||||
Observable,
|
||||
of as observableOf
|
||||
} from 'rxjs';
|
||||
import { catchError, map, switchMap, take } from 'rxjs/operators';
|
||||
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
@@ -20,7 +25,8 @@ import { RouteService } from '../../../core/services/route.service';
|
||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
getFirstCompletedRemoteData
|
||||
getFirstCompletedRemoteData,
|
||||
getAllSucceededRemoteData
|
||||
} from '../../../core/shared/operators';
|
||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
@@ -70,6 +76,11 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
||||
// Current search in groups registry
|
||||
currentSearchQuery: string;
|
||||
|
||||
/**
|
||||
* The subscription for the search method
|
||||
*/
|
||||
searchSub: Subscription;
|
||||
|
||||
/**
|
||||
* List of subscriptions
|
||||
*/
|
||||
@@ -93,6 +104,30 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
this.search({ query: this.currentSearchQuery });
|
||||
|
||||
this.subs.push(this.groups$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
switchMap((groups: PaginatedList<Group>) => {
|
||||
return observableCombineLatest(groups.page.map((group: Group) => {
|
||||
return observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined),
|
||||
this.hasLinkedDSO(group)
|
||||
]).pipe(
|
||||
map(([isAuthorized, hasLinkedDSO]: boolean[]) => {
|
||||
const groupDtoModel: GroupDtoModel = new GroupDtoModel();
|
||||
groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO;
|
||||
groupDtoModel.group = group;
|
||||
return groupDtoModel;
|
||||
}
|
||||
)
|
||||
);
|
||||
})).pipe(map((dtos: GroupDtoModel[]) => {
|
||||
return buildPaginatedList(groups.pageInfo, dtos);
|
||||
}));
|
||||
})).subscribe((value: PaginatedList<GroupDtoModel>) => {
|
||||
this.groupsDto$.next(value);
|
||||
this.pageInfoState$.next(value.pageInfo);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,37 +150,20 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
||||
this.currentSearchQuery = query;
|
||||
this.config.currentPage = 1;
|
||||
}
|
||||
this.subs.push(this.groupService.searchGroups(this.currentSearchQuery.trim(), {
|
||||
if (hasValue(this.searchSub)) {
|
||||
this.searchSub.unsubscribe();
|
||||
this.subs = this.subs.filter((sub: Subscription) => sub !== this.searchSub);
|
||||
}
|
||||
this.searchSub = this.groupService.searchGroups(this.currentSearchQuery.trim(), {
|
||||
currentPage: this.config.currentPage,
|
||||
elementsPerPage: this.config.pageSize
|
||||
}).pipe(getFirstCompletedRemoteData())
|
||||
.subscribe((groupsRD: RemoteData<PaginatedList<Group>>) => {
|
||||
}).pipe(
|
||||
getAllSucceededRemoteData()
|
||||
).subscribe((groupsRD: RemoteData<PaginatedList<Group>>) => {
|
||||
this.groups$.next(groupsRD);
|
||||
this.pageInfoState$.next(groupsRD.payload.pageInfo);
|
||||
}
|
||||
));
|
||||
|
||||
this.subs.push(this.groups$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
switchMap((groups: PaginatedList<Group>) => {
|
||||
return observableCombineLatest(...groups.page.map((group: Group) => {
|
||||
return observableCombineLatest(
|
||||
this.authorizationService.isAuthorized(FeatureID.CanDelete, hasValue(group) ? group.self : undefined),
|
||||
this.hasLinkedDSO(group),
|
||||
(isAuthorized: ObservedValueOf<Observable<boolean>>, hasLinkedDSO: ObservedValueOf<Observable<boolean>>) => {
|
||||
const groupDtoModel: GroupDtoModel = new GroupDtoModel();
|
||||
groupDtoModel.ableToDelete = isAuthorized && !hasLinkedDSO;
|
||||
groupDtoModel.group = group;
|
||||
return groupDtoModel;
|
||||
}
|
||||
);
|
||||
})).pipe(map((dtos: GroupDtoModel[]) => {
|
||||
return buildPaginatedList(groups.pageInfo, dtos);
|
||||
}));
|
||||
})).subscribe((value: PaginatedList<GroupDtoModel>) => {
|
||||
this.groupsDto$.next(value);
|
||||
this.pageInfoState$.next(value.pageInfo);
|
||||
}));
|
||||
});
|
||||
this.subs.push(this.searchSub);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,16 +186,13 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will ensure that the page gets reset and that the cache is cleared
|
||||
* This method will set everything to stale, which will cause the lists on this page to update.
|
||||
*/
|
||||
reset() {
|
||||
this.groupService.getBrowseEndpoint().pipe(
|
||||
switchMap((href) => this.requestService.removeByHrefSubstring(href)),
|
||||
filter((isCached) => isCached),
|
||||
take(1)
|
||||
).subscribe(() => {
|
||||
this.cleanupSubscribes();
|
||||
this.search({ query: this.currentSearchQuery });
|
||||
).subscribe((href: string) => {
|
||||
this.requestService.setStaleByHrefSubstring(href);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -158,7 +158,6 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
|
||||
* Emit the updated/created field using the EventEmitter submitForm
|
||||
*/
|
||||
onSubmit() {
|
||||
this.registryService.clearMetadataFieldRequests().subscribe();
|
||||
this.registryService.getActiveMetadataField().pipe(take(1)).subscribe(
|
||||
(field) => {
|
||||
const values = {
|
||||
|
@@ -1,7 +1,13 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { RegistryService } from '../../../core/registry/registry.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, combineLatest, Observable, zip } from 'rxjs';
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest as observableCombineLatest,
|
||||
combineLatest,
|
||||
Observable,
|
||||
zip
|
||||
} from 'rxjs';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
@@ -11,9 +17,11 @@ import { NotificationsService } from '../../../shared/notifications/notification
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
||||
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import {
|
||||
getFirstCompletedRemoteData,
|
||||
getFirstSucceededRemoteDataPayload
|
||||
} from '../../../core/shared/operators';
|
||||
import { toFindListOptions } from '../../../shared/pagination/pagination.utils';
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||
|
||||
@Component({
|
||||
@@ -89,8 +97,9 @@ export class MetadataSchemaComponent implements OnInit {
|
||||
this.metadataFields$ = combineLatest(this.metadataSchema$, this.needsUpdate$).pipe(
|
||||
switchMap(([schema, update]: [MetadataSchema, boolean]) => {
|
||||
if (update) {
|
||||
return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config), true, followLink('schema'));
|
||||
this.needsUpdate$.next(false);
|
||||
}
|
||||
return this.registryService.getMetadataFieldsBySchema(schema, toFindListOptions(this.config), !update, true);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -100,6 +109,7 @@ export class MetadataSchemaComponent implements OnInit {
|
||||
* a new REST call
|
||||
*/
|
||||
public forceUpdateFields() {
|
||||
this.registryService.clearMetadataFieldRequests();
|
||||
this.needsUpdate$.next(true);
|
||||
}
|
||||
|
||||
@@ -159,7 +169,6 @@ export class MetadataSchemaComponent implements OnInit {
|
||||
* Delete all the selected metadata fields
|
||||
*/
|
||||
deleteFields() {
|
||||
this.registryService.clearMetadataFieldRequests().subscribe();
|
||||
this.registryService.getSelectedMetadataFields().pipe(take(1)).subscribe(
|
||||
(fields) => {
|
||||
const tasks$ = [];
|
||||
@@ -173,6 +182,8 @@ export class MetadataSchemaComponent implements OnInit {
|
||||
const failedResponses = responses.filter((response: RemoteData<NoContent>) => response.hasFailed);
|
||||
if (successResponses.length > 0) {
|
||||
this.showNotification(true, successResponses.length);
|
||||
this.registryService.clearMetadataFieldRequests();
|
||||
|
||||
}
|
||||
if (failedResponses.length > 0) {
|
||||
this.showNotification(false, failedResponses.length);
|
||||
|
@@ -23,7 +23,7 @@ export class BitstreamPageResolver implements Resolve<RemoteData<Bitstream>> {
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Bitstream>> {
|
||||
return this.bitstreamService.findById(route.params.id, false, ...this.followLinks)
|
||||
return this.bitstreamService.findById(route.params.id, true, false, ...this.followLinks)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
@@ -35,7 +35,7 @@ export class BitstreamPageResolver implements Resolve<RemoteData<Bitstream>> {
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Bitstream>[] {
|
||||
return [
|
||||
followLink('bundle', undefined, true, followLink('item')),
|
||||
followLink('bundle', undefined, true, true, true, followLink('item')),
|
||||
followLink('format')
|
||||
];
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2>{{'collection.edit.item-mapper.head' | translate}}</h2>
|
||||
<p [innerHTML]="'collection.edit.item-mapper.collection' | translate:{ name: (collectionRD$ | async)?.payload?.name }" id="collection-name"></p>
|
||||
<p [innerHTML]="'collection.edit.item-mapper.collection' | translate:{ name: (collectionName$ |async) }" id="collection-name"></p>
|
||||
<p>{{'collection.edit.item-mapper.description' | translate}}</p>
|
||||
|
||||
<ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset">
|
||||
|
@@ -6,7 +6,6 @@ import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { SearchFormComponent } from '../../shared/search-form/search-form.component';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||
import { RouterStub } from '../../shared/testing/router.stub';
|
||||
import { SearchServiceStub } from '../../shared/testing/search-service.stub';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
@@ -28,7 +27,7 @@ import { ItemSelectComponent } from '../../shared/object-select/item-select/item
|
||||
import { ObjectSelectService } from '../../shared/object-select/object-select.service';
|
||||
import { ObjectSelectServiceStub } from '../../shared/testing/object-select-service.stub';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { of as observableOf, of } from 'rxjs';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
import { RouteService } from '../../core/services/route.service';
|
||||
import { ErrorComponent } from '../../shared/error/error.component';
|
||||
import { LoadingComponent } from '../../shared/loading/loading.component';
|
||||
@@ -57,13 +56,16 @@ describe('CollectionItemMapperComponent', () => {
|
||||
id: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4',
|
||||
name: 'test-collection',
|
||||
_links: {
|
||||
mappedItems: {
|
||||
href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4/mappedItems'
|
||||
},
|
||||
self: {
|
||||
href: 'https://rest.api/collections/ce41d451-97ed-4a9c-94a1-7de34f16a9f4'
|
||||
}
|
||||
}
|
||||
});
|
||||
const mockCollectionRD: RemoteData<Collection> = createSuccessfulRemoteDataObject(mockCollection);
|
||||
const mockSearchOptions = of(new PaginatedSearchOptions({
|
||||
const mockSearchOptions = observableOf(new PaginatedSearchOptions({
|
||||
pagination: Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'search-page-configuration',
|
||||
pageSize: 10,
|
||||
@@ -82,25 +84,37 @@ describe('CollectionItemMapperComponent', () => {
|
||||
const searchConfigServiceStub = {
|
||||
paginatedSearchOptions: mockSearchOptions
|
||||
};
|
||||
const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([]));
|
||||
const itemDataServiceStub = {
|
||||
mapToCollection: () => createSuccessfulRemoteDataObject$({})
|
||||
mapToCollection: () => createSuccessfulRemoteDataObject$({}),
|
||||
findAllByHref: () => observableOf(emptyList)
|
||||
};
|
||||
const activatedRouteStub = {
|
||||
parent: {
|
||||
data: observableOf({
|
||||
dso: mockCollectionRD
|
||||
})
|
||||
},
|
||||
snapshot: {
|
||||
queryParamMap: new Map([
|
||||
['query', 'test'],
|
||||
])
|
||||
}
|
||||
};
|
||||
const activatedRouteStub = new ActivatedRouteStub({}, { dso: mockCollectionRD });
|
||||
const translateServiceStub = {
|
||||
get: () => of('test-message of collection ' + mockCollection.name),
|
||||
get: () => observableOf('test-message of collection ' + mockCollection.name),
|
||||
onLangChange: new EventEmitter(),
|
||||
onTranslationChange: new EventEmitter(),
|
||||
onDefaultLangChange: new EventEmitter()
|
||||
};
|
||||
const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([]));
|
||||
const searchServiceStub = Object.assign(new SearchServiceStub(), {
|
||||
search: () => of(emptyList),
|
||||
search: () => observableOf(emptyList),
|
||||
/* tslint:disable:no-empty */
|
||||
clearDiscoveryRequests: () => {}
|
||||
/* tslint:enable:no-empty */
|
||||
});
|
||||
const collectionDataServiceStub = {
|
||||
getMappedItems: () => of(emptyList),
|
||||
getMappedItems: () => observableOf(emptyList),
|
||||
/* tslint:disable:no-empty */
|
||||
clearMappedItemsRequests: () => {}
|
||||
/* tslint:enable:no-empty */
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
|
||||
import { ChangeDetectionStrategy, Component, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
@@ -10,7 +11,8 @@ import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import {
|
||||
getRemoteDataPayload,
|
||||
getFirstSucceededRemoteData,
|
||||
toDSpaceObjectListRD
|
||||
toDSpaceObjectListRD,
|
||||
getFirstCompletedRemoteData, getAllSucceededRemoteData
|
||||
} from '../../core/shared/operators';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
||||
@@ -52,12 +54,13 @@ export class CollectionItemMapperComponent implements OnInit {
|
||||
* A view on the tabset element
|
||||
* Used to switch tabs programmatically
|
||||
*/
|
||||
@ViewChild('tabs') tabs;
|
||||
@ViewChild('tabs', {static: false}) tabs;
|
||||
|
||||
/**
|
||||
* The collection to map items to
|
||||
*/
|
||||
collectionRD$: Observable<RemoteData<Collection>>;
|
||||
collectionName$: Observable<string>;
|
||||
|
||||
/**
|
||||
* Search options
|
||||
@@ -101,11 +104,21 @@ export class CollectionItemMapperComponent implements OnInit {
|
||||
private notificationsService: NotificationsService,
|
||||
private itemDataService: ItemDataService,
|
||||
private collectionDataService: CollectionDataService,
|
||||
private translateService: TranslateService) {
|
||||
private translateService: TranslateService,
|
||||
private dsoNameService: DSONameService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.collectionRD$ = this.route.data.pipe(map((data) => data.dso)).pipe(getFirstSucceededRemoteData()) as Observable<RemoteData<Collection>>;
|
||||
this.collectionRD$ = this.route.parent.data.pipe(
|
||||
map((data) => data.dso as RemoteData<Collection>),
|
||||
getFirstSucceededRemoteData()
|
||||
);
|
||||
|
||||
this.collectionName$ = this.collectionRD$.pipe(
|
||||
map((rd: RemoteData<Collection>) => {
|
||||
return this.dsoNameService.getName(rd.payload);
|
||||
})
|
||||
);
|
||||
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
|
||||
this.loadItemLists();
|
||||
}
|
||||
@@ -123,16 +136,18 @@ export class CollectionItemMapperComponent implements OnInit {
|
||||
);
|
||||
this.collectionItemsRD$ = collectionAndOptions$.pipe(
|
||||
switchMap(([collectionRD, options, shouldUpdate]) => {
|
||||
if (shouldUpdate) {
|
||||
return this.collectionDataService.getMappedItems(collectionRD.payload.id, Object.assign(options, {
|
||||
sort: this.defaultSortOptions
|
||||
}),followLink('owningCollection'));
|
||||
if (shouldUpdate === true) {
|
||||
this.shouldUpdate$.next(false);
|
||||
}
|
||||
return this.itemDataService.findAllByHref(collectionRD.payload._links.mappedItems.href, Object.assign(options, {
|
||||
sort: this.defaultSortOptions
|
||||
}),!shouldUpdate, false, followLink('owningCollection')).pipe(
|
||||
getAllSucceededRemoteData()
|
||||
);
|
||||
})
|
||||
);
|
||||
this.mappedItemsRD$ = collectionAndOptions$.pipe(
|
||||
switchMap(([collectionRD, options, shouldUpdate]) => {
|
||||
if (shouldUpdate) {
|
||||
return this.searchService.search(Object.assign(new PaginatedSearchOptions(options), {
|
||||
query: this.buildQuery(collectionRD.payload.id, options.query),
|
||||
scope: undefined,
|
||||
@@ -142,7 +157,6 @@ export class CollectionItemMapperComponent implements OnInit {
|
||||
toDSpaceObjectListRD(),
|
||||
startWith(undefined)
|
||||
);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -157,8 +171,17 @@ export class CollectionItemMapperComponent implements OnInit {
|
||||
getFirstSucceededRemoteData(),
|
||||
map((collectionRD: RemoteData<Collection>) => collectionRD.payload),
|
||||
switchMap((collection: Collection) =>
|
||||
observableCombineLatest(ids.map((id: string) =>
|
||||
remove ? this.itemDataService.removeMappingFromCollection(id, collection.id) : this.itemDataService.mapToCollection(id, collection._links.self.href)
|
||||
observableCombineLatest(ids.map((id: string) => {
|
||||
if (remove) {
|
||||
return this.itemDataService.removeMappingFromCollection(id, collection.id).pipe(
|
||||
getFirstCompletedRemoteData()
|
||||
);
|
||||
} else {
|
||||
return this.itemDataService.mapToCollection(id, collection._links.self.href).pipe(
|
||||
getFirstCompletedRemoteData()
|
||||
);
|
||||
}
|
||||
}
|
||||
))
|
||||
)
|
||||
);
|
||||
@@ -186,6 +209,7 @@ export class CollectionItemMapperComponent implements OnInit {
|
||||
successMessages.subscribe(([head, content]) => {
|
||||
this.notificationsService.success(head, content);
|
||||
});
|
||||
this.shouldUpdate$.next(true);
|
||||
}
|
||||
if (unsuccessful.length > 0) {
|
||||
const unsuccessMessages = observableCombineLatest(
|
||||
@@ -197,8 +221,6 @@ export class CollectionItemMapperComponent implements OnInit {
|
||||
this.notificationsService.error(head, content);
|
||||
});
|
||||
}
|
||||
// Force an update on all lists and switch back to the first tab
|
||||
this.shouldUpdate$.next(true);
|
||||
this.switchToFirstTab();
|
||||
});
|
||||
}
|
||||
|
@@ -9,7 +9,6 @@ import { CreateCollectionPageGuard } from './create-collection-page/create-colle
|
||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||
import { EditItemTemplatePageComponent } from './edit-item-template-page/edit-item-template-page.component';
|
||||
import { ItemTemplatePageResolver } from './edit-item-template-page/item-template-page.resolver';
|
||||
import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component';
|
||||
import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
||||
import { LinkService } from '../core/cache/builders/link.service';
|
||||
@@ -65,12 +64,6 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
path: '',
|
||||
component: CollectionPageComponent,
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: '/edit/mapper',
|
||||
component: CollectionItemMapperComponent,
|
||||
pathMatch: 'full',
|
||||
canActivate: [AuthenticatedGuard]
|
||||
}
|
||||
],
|
||||
data: {
|
||||
|
@@ -23,7 +23,7 @@ export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
|
||||
return this.collectionService.findById(route.params.id, false, followLink('logo')).pipe(
|
||||
return this.collectionService.findById(route.params.id, true, false, followLink('logo')).pipe(
|
||||
getFirstCompletedRemoteData()
|
||||
);
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CollectionItemMapperComponent } from '../collection-item-mapper/collection-item-mapper.component';
|
||||
import { EditCollectionPageComponent } from './edit-collection-page.component';
|
||||
import { CollectionMetadataComponent } from './collection-metadata/collection-metadata.component';
|
||||
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
|
||||
@@ -86,7 +87,12 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit
|
||||
data: { title: 'collection.edit.tabs.authorizations.title', showBreadcrumbs: true }
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'mapper',
|
||||
component: CollectionItemMapperComponent,
|
||||
data: { title: 'collection.edit.tabs.item-mapper.title', showBreadcrumbs: true }
|
||||
},
|
||||
]
|
||||
}
|
||||
])
|
||||
|
@@ -23,7 +23,7 @@ export class ItemTemplatePageResolver implements Resolve<RemoteData<Item>> {
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||
return this.itemTemplateService.findByCollectionID(route.params.id, false, followLink('templateItemOf')).pipe(
|
||||
return this.itemTemplateService.findByCollectionID(route.params.id, true, false, followLink('templateItemOf')).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
|
||||
return this.communityService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
followLink('logo'),
|
||||
followLink('subcommunities'),
|
||||
|
@@ -6,14 +6,18 @@ import { CoreModule } from '../core/core.module';
|
||||
import { ImportExternalRoutingModule } from './import-external-routing.module';
|
||||
import { SubmissionModule } from '../submission/submission.module';
|
||||
import { ImportExternalPageComponent } from './import-external-page.component';
|
||||
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
|
||||
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
SharedModule.withEntryComponents(),
|
||||
CoreModule.forRoot(),
|
||||
ImportExternalRoutingModule,
|
||||
SubmissionModule,
|
||||
JournalEntitiesModule.withEntryComponents(),
|
||||
ResearchEntitiesModule.withEntryComponents()
|
||||
],
|
||||
declarations: [
|
||||
ImportExternalPageComponent
|
||||
|
@@ -1,16 +1,22 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import {
|
||||
FieldUpdate,
|
||||
FieldUpdates
|
||||
} from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ActivatedRoute, Router, Data } from '@angular/router';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { first, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { ITEM_PAGE_LINKS_TO_FOLLOW } from '../../item-page.resolver';
|
||||
import { getAllSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-abstract-item-update',
|
||||
@@ -19,7 +25,7 @@ import { environment } from '../../../../environments/environment';
|
||||
/**
|
||||
* Abstract component for managing object updates of an item
|
||||
*/
|
||||
export class AbstractItemUpdateComponent extends AbstractTrackableComponent implements OnInit {
|
||||
export class AbstractItemUpdateComponent extends AbstractTrackableComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* The item to display the edit page for
|
||||
*/
|
||||
@@ -30,6 +36,12 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
||||
*/
|
||||
updates$: Observable<FieldUpdates>;
|
||||
|
||||
/**
|
||||
* A subscription that checks when the item is deleted in cache and reloads the item by sending a new request
|
||||
* This is used to update the item in cache after bitstreams are deleted
|
||||
*/
|
||||
itemUpdateSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
public itemService: ItemDataService,
|
||||
public objectUpdatesService: ObjectUpdatesService,
|
||||
@@ -45,14 +57,20 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
||||
* Initialize common properties between item-update components
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
observableCombineLatest(this.route.data, this.route.parent.data).pipe(
|
||||
map(([data, parentData]) => Object.assign({}, data, parentData)),
|
||||
map((data) => data.dso),
|
||||
first(),
|
||||
map((data: RemoteData<Item>) => data.payload)
|
||||
).subscribe((item: Item) => {
|
||||
this.item = item;
|
||||
this.itemUpdateSubscription = observableCombineLatest([this.route.data, this.route.parent.data]).pipe(
|
||||
map(([data, parentData]: [Data, Data]) => Object.assign({}, data, parentData)),
|
||||
map((data: any) => data.dso),
|
||||
tap((rd: RemoteData<Item>) => {
|
||||
this.item = rd.payload;
|
||||
}),
|
||||
switchMap((rd: RemoteData<Item>) => {
|
||||
return this.itemService.findByHref(rd.payload._links.self.href, true, true, ...ITEM_PAGE_LINKS_TO_FOLLOW);
|
||||
}),
|
||||
getAllSucceededRemoteData()
|
||||
).subscribe((rd: RemoteData<Item>) => {
|
||||
this.item = rd.payload;
|
||||
this.postItemInit();
|
||||
this.initializeUpdates();
|
||||
});
|
||||
|
||||
this.discardTimeOut = environment.item.edit.undoTimeout;
|
||||
@@ -72,6 +90,12 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
||||
this.initializeUpdates();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (hasValue(this.itemUpdateSubscription)) {
|
||||
this.itemUpdateSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to perform after the item has been initialized
|
||||
* Abstract method: Should be overwritten in the sub class
|
||||
|
@@ -90,6 +90,11 @@ import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
|
||||
path: 'versionhistory',
|
||||
component: ItemVersionHistoryComponent,
|
||||
data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true }
|
||||
},
|
||||
{
|
||||
path: 'mapper',
|
||||
component: ItemCollectionMapperComponent,
|
||||
data: { title: 'item.edit.tabs.item-mapper.title', showBreadcrumbs: true }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -79,7 +79,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
|
||||
getFirstSucceededRemoteDataWithNotEmptyPayload(),
|
||||
map((item: Item) => this.linkService.resolveLink(
|
||||
item,
|
||||
followLink('bundles', new FindListOptions(), true, followLink('bitstreams'))
|
||||
followLink('bundles', new FindListOptions(), true, true, true, followLink('bitstreams'))
|
||||
))
|
||||
) as Observable<Item>;
|
||||
|
||||
|
@@ -134,6 +134,7 @@ describe('ItemBitstreamsComponent', () => {
|
||||
});
|
||||
itemService = Object.assign({
|
||||
getBitstreams: () => createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2])),
|
||||
findByHref: () => createSuccessfulRemoteDataObject$(item),
|
||||
findById: () => createSuccessfulRemoteDataObject$(item),
|
||||
getBundles: () => createSuccessfulRemoteDataObject$(createPaginatedList([bundle]))
|
||||
});
|
||||
|
@@ -12,11 +12,13 @@ import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||
import { Bundle } from '../../../core/shared/bundle.model';
|
||||
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import {
|
||||
FieldUpdate,
|
||||
FieldUpdates
|
||||
} from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
|
||||
import { BundleDataService } from '../../../core/data/bundle-data.service';
|
||||
@@ -93,14 +95,6 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
||||
super(itemService, objectUpdatesService, router, notificationsService, translateService, route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up and initialize all fields
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
this.initializeItemUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to perform after the item has been initialized
|
||||
*/
|
||||
@@ -119,25 +113,6 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
||||
this.notificationsPrefix = 'item.edit.bitstreams.notifications.';
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the item (and view) when it's removed in the request cache
|
||||
* Also re-initialize the original fields and updates
|
||||
*/
|
||||
initializeItemUpdate(): void {
|
||||
this.itemUpdateSubscription = this.requestService.hasByHref$(this.item.self).pipe(
|
||||
filter((exists: boolean) => !exists),
|
||||
switchMap(() => this.itemService.findById(this.item.uuid)),
|
||||
getFirstSucceededRemoteData(),
|
||||
).subscribe((itemRD: RemoteData<Item>) => {
|
||||
if (hasValue(itemRD)) {
|
||||
this.item = itemRD.payload;
|
||||
this.postItemInit();
|
||||
this.initializeOriginalFields();
|
||||
this.initializeUpdates();
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the current changes
|
||||
@@ -274,7 +249,6 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
||||
*/
|
||||
reset() {
|
||||
this.refreshItemCache();
|
||||
this.initializeItemUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2>{{'item.edit.item-mapper.head' | translate}}</h2>
|
||||
<p [innerHTML]="'item.edit.item-mapper.item' | translate:{ name: (itemRD$ | async)?.payload?.name }" id="item-name"></p>
|
||||
<p [innerHTML]="'item.edit.item-mapper.item' | translate:{ name: (itemName$ | async) }" id="item-name"></p>
|
||||
<p>{{'item.edit.item-mapper.description' | translate}}</p>
|
||||
|
||||
<ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset">
|
||||
|
@@ -7,7 +7,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { of } from 'rxjs';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
@@ -26,7 +26,6 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
|
||||
import { PaginationComponent } from '../../../shared/pagination/pagination.component';
|
||||
import { SearchFormComponent } from '../../../shared/search-form/search-form.component';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service.stub';
|
||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||
import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service.stub';
|
||||
@@ -59,7 +58,7 @@ describe('ItemCollectionMapperComponent', () => {
|
||||
name: 'test-item'
|
||||
});
|
||||
const mockItemRD: RemoteData<Item> = createSuccessfulRemoteDataObject(mockItem);
|
||||
const mockSearchOptions = of(new PaginatedSearchOptions({
|
||||
const mockSearchOptions = observableOf(new PaginatedSearchOptions({
|
||||
pagination: Object.assign(new PaginationComponentOptions(), {
|
||||
id: 'search-page-configuration',
|
||||
pageSize: 10,
|
||||
@@ -81,20 +80,30 @@ describe('ItemCollectionMapperComponent', () => {
|
||||
const itemDataServiceStub = {
|
||||
mapToCollection: () => createSuccessfulRemoteDataObject$({}),
|
||||
removeMappingFromCollection: () => createSuccessfulRemoteDataObject$({}),
|
||||
getMappedCollections: () => of(mockCollectionsRD),
|
||||
getMappedCollectionsEndpoint: () => observableOf('rest/api/mappedCollectionsEndpoint'),
|
||||
getMappedCollections: () => observableOf(mockCollectionsRD),
|
||||
/* tslint:disable:no-empty */
|
||||
clearMappedCollectionsRequests: () => {}
|
||||
/* tslint:enable:no-empty */
|
||||
};
|
||||
const collectionDataServiceStub = {
|
||||
findAllByHref: () => observableOf(mockCollectionsRD)
|
||||
};
|
||||
const searchServiceStub = Object.assign(new SearchServiceStub(), {
|
||||
search: () => of(mockCollectionsRD),
|
||||
search: () => observableOf(mockCollectionsRD),
|
||||
/* tslint:disable:no-empty */
|
||||
clearDiscoveryRequests: () => {}
|
||||
/* tslint:enable:no-empty */
|
||||
});
|
||||
const activatedRouteStub = new ActivatedRouteStub({}, { dso: mockItemRD });
|
||||
const activatedRouteStub = {
|
||||
parent: {
|
||||
data: observableOf({
|
||||
dso: mockItemRD
|
||||
})
|
||||
}
|
||||
};
|
||||
const translateServiceStub = {
|
||||
get: () => of('test-message of item ' + mockItem.name),
|
||||
get: () => observableOf('test-message of item ' + mockItem.name),
|
||||
onLangChange: new EventEmitter(),
|
||||
onTranslationChange: new EventEmitter(),
|
||||
onDefaultLangChange: new EventEmitter()
|
||||
@@ -114,7 +123,7 @@ describe('ItemCollectionMapperComponent', () => {
|
||||
{ provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() },
|
||||
{ provide: TranslateService, useValue: translateServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||
{ provide: CollectionDataService, useValue: {} }
|
||||
{ provide: CollectionDataService, useValue: collectionDataServiceStub }
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
|
||||
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
@@ -11,15 +12,16 @@ import {
|
||||
getFirstSucceededRemoteDataPayload,
|
||||
getRemoteDataPayload,
|
||||
getFirstSucceededRemoteData,
|
||||
toDSpaceObjectListRD
|
||||
toDSpaceObjectListRD,
|
||||
getAllSucceededRemoteData, getFirstCompletedRemoteData
|
||||
} from '../../../core/shared/operators';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||
import { isNotEmpty } from '../../../shared/empty.util';
|
||||
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
@@ -50,6 +52,7 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
* The item to map to collections
|
||||
*/
|
||||
itemRD$: Observable<RemoteData<Item>>;
|
||||
itemName$: Observable<string>;
|
||||
|
||||
/**
|
||||
* Search options
|
||||
@@ -87,11 +90,22 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
private notificationsService: NotificationsService,
|
||||
private itemDataService: ItemDataService,
|
||||
private collectionDataService: CollectionDataService,
|
||||
private translateService: TranslateService) {
|
||||
private translateService: TranslateService,
|
||||
private dsoNameService: DSONameService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.dso)).pipe(getFirstSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||
this.itemRD$ = this.route.parent.data.pipe(
|
||||
take(1),
|
||||
map((data) => data.dso),
|
||||
);
|
||||
|
||||
this.itemName$ = this.itemRD$.pipe(
|
||||
filter((rd: RemoteData<Item>) => hasValue(rd)),
|
||||
map((rd: RemoteData<Item>) => {
|
||||
return this.dsoNameService.getName(rd.payload);
|
||||
})
|
||||
);
|
||||
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
|
||||
this.loadCollectionLists();
|
||||
}
|
||||
@@ -101,19 +115,28 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
* Load mappedCollectionsRD$ to only obtain collections that don't own this item
|
||||
*/
|
||||
loadCollectionLists() {
|
||||
console.log('loadCollectionLists');
|
||||
this.shouldUpdate$ = new BehaviorSubject<boolean>(true);
|
||||
this.itemCollectionsRD$ = observableCombineLatest(this.itemRD$, this.shouldUpdate$).pipe(
|
||||
map(([itemRD, shouldUpdate]) => {
|
||||
if (shouldUpdate) {
|
||||
return itemRD.payload;
|
||||
this.itemCollectionsRD$ = observableCombineLatest(this.itemRD$.pipe(getFirstSucceededRemoteDataPayload()), this.shouldUpdate$).pipe(
|
||||
switchMap(([item, shouldUpdate]) => {
|
||||
if (shouldUpdate === true) {
|
||||
this.shouldUpdate$.next(false);
|
||||
}
|
||||
return this.collectionDataService.findAllByHref(
|
||||
this.itemDataService.getMappedCollectionsEndpoint(item.id),
|
||||
undefined,
|
||||
!shouldUpdate,
|
||||
false
|
||||
).pipe(
|
||||
getAllSucceededRemoteData()
|
||||
);
|
||||
}),
|
||||
switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id))
|
||||
);
|
||||
|
||||
const owningCollectionRD$ = this.itemRD$.pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
switchMap((item: Item) => this.collectionDataService.findOwningCollectionFor(item))
|
||||
switchMap((item: Item) => this.collectionDataService.findOwningCollectionFor(item)),
|
||||
getAllSucceededRemoteData(),
|
||||
);
|
||||
const itemCollectionsAndOptions$ = observableCombineLatest(
|
||||
this.itemCollectionsRD$,
|
||||
@@ -141,13 +164,11 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
const itemIdAndExcludingIds$ = observableCombineLatest([
|
||||
this.itemRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
take(1),
|
||||
map((rd: RemoteData<Item>) => rd.payload),
|
||||
map((item: Item) => item.id)
|
||||
),
|
||||
this.itemCollectionsRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
take(1),
|
||||
map((rd: RemoteData<PaginatedList<Collection>>) => rd.payload.page),
|
||||
map((collections: Collection[]) => collections.map((collection: Collection) => collection.id))
|
||||
)
|
||||
@@ -155,7 +176,12 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
|
||||
// Map the item to the collections found in ids, excluding the collections the item is already mapped to
|
||||
const responses$ = itemIdAndExcludingIds$.pipe(
|
||||
switchMap(([itemId, excludingIds]) => observableCombineLatest(this.filterIds(ids, excludingIds).map((id: string) => this.itemDataService.mapToCollection(itemId, id))))
|
||||
switchMap(([itemId, excludingIds]) =>
|
||||
observableCombineLatest(
|
||||
this.filterIds(ids, excludingIds).map((id: string) =>
|
||||
this.itemDataService.mapToCollection(itemId, id).pipe(getFirstCompletedRemoteData())
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add');
|
||||
@@ -169,7 +195,11 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
const responses$ = this.itemRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
map((itemRD: RemoteData<Item>) => itemRD.payload.id),
|
||||
switchMap((itemId: string) => observableCombineLatest(ids.map((id: string) => this.itemDataService.removeMappingFromCollection(itemId, id))))
|
||||
switchMap((itemId: string) => observableCombineLatest(
|
||||
ids.map((id: string) =>
|
||||
this.itemDataService.removeMappingFromCollection(itemId, id).pipe(getFirstCompletedRemoteData())
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove');
|
||||
@@ -203,6 +233,7 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
successMessages.subscribe(([head, content]) => {
|
||||
this.notificationsService.success(head, content);
|
||||
});
|
||||
this.shouldUpdate$.next(true);
|
||||
}
|
||||
if (unsuccessful.length > 0) {
|
||||
const unsuccessMessages = observableCombineLatest([
|
||||
@@ -214,8 +245,6 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
this.notificationsService.error(head, content);
|
||||
});
|
||||
}
|
||||
// Force an update on all lists and switch back to the first tab
|
||||
this.shouldUpdate$.next(true);
|
||||
this.switchToFirstTab();
|
||||
});
|
||||
}
|
||||
|
@@ -228,7 +228,7 @@ describe('EditInPlaceFieldComponent', () => {
|
||||
}));
|
||||
|
||||
it('it should call queryMetadataFields on the metadataFieldService with the correct query', () => {
|
||||
expect(metadataFieldService.queryMetadataFields).toHaveBeenCalledWith(query, null, false, followLink('schema'));
|
||||
expect(metadataFieldService.queryMetadataFields).toHaveBeenCalledWith(query, null, true, false, followLink('schema'));
|
||||
});
|
||||
|
||||
it('it should set metadataFieldSuggestions to the right value', () => {
|
||||
|
@@ -127,7 +127,7 @@ export class EditInPlaceFieldComponent implements OnInit, OnChanges {
|
||||
*/
|
||||
findMetadataFieldSuggestions(query: string) {
|
||||
if (isNotEmpty(query)) {
|
||||
return this.registryService.queryMetadataFields(query, null, false, followLink('schema')).pipe(
|
||||
return this.registryService.queryMetadataFields(query, null, true, false, followLink('schema')).pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
metadataFieldsToString(),
|
||||
).subscribe((fieldNames: string[]) => {
|
||||
|
@@ -108,6 +108,11 @@ describe('ItemMetadataComponent', () => {
|
||||
[metadatum1.key]: [metadatum1],
|
||||
[metadatum2.key]: [metadatum2],
|
||||
[metadatum3.key]: [metadatum3]
|
||||
},
|
||||
_links: {
|
||||
self: {
|
||||
href: 'https://rest.api/core/items/a36d8bd2-8e8c-4969-9b1f-a574c2064983'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@@ -11,7 +11,7 @@ import {
|
||||
} from '../../../../core/data/object-updates/object-updates.reducer';
|
||||
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { defaultIfEmpty, map, mergeMap, switchMap, take, } from 'rxjs/operators';
|
||||
import { defaultIfEmpty, map, mergeMap, switchMap, take, startWith } from 'rxjs/operators';
|
||||
import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
|
||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||
@@ -311,12 +311,13 @@ export class EditRelationshipListComponent implements OnInit {
|
||||
return fieldUpdatesFiltered;
|
||||
}),
|
||||
)),
|
||||
startWith({}),
|
||||
);
|
||||
}
|
||||
|
||||
private getItemRelationships() {
|
||||
this.linkService.resolveLink(this.item,
|
||||
followLink('relationships', undefined, true,
|
||||
followLink('relationships', undefined, true, true, true,
|
||||
followLink('relationshipType'),
|
||||
followLink('leftItem'),
|
||||
followLink('rightItem'),
|
||||
|
@@ -133,6 +133,7 @@ describe('ItemRelationshipsComponent', () => {
|
||||
};
|
||||
|
||||
itemService = jasmine.createSpyObj('itemService', {
|
||||
findByHref: createSuccessfulRemoteDataObject$(item),
|
||||
findById: createSuccessfulRemoteDataObject$(item)
|
||||
});
|
||||
routeStub = {
|
||||
|
@@ -7,8 +7,12 @@ import {
|
||||
RelationshipIdentifiable,
|
||||
} from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { filter, map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import { combineLatest as observableCombineLatest, of as observableOf, zip as observableZip } from 'rxjs';
|
||||
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||
import {
|
||||
combineLatest as observableCombineLatest,
|
||||
of as observableOf,
|
||||
zip as observableZip
|
||||
} from 'rxjs';
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
@@ -39,7 +43,6 @@ import { hasValue } from '../../../shared/empty.util';
|
||||
*/
|
||||
export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
|
||||
|
||||
itemRD$: Observable<RemoteData<Item>>;
|
||||
|
||||
/**
|
||||
* The allowed relationship types for this type of item as an observable list
|
||||
@@ -67,40 +70,6 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
|
||||
super(itemService, objectUpdatesService, router, notificationsService, translateService, route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up and initialize all fields
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
this.initializeItemUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the item (and view) when it's removed in the request cache
|
||||
*/
|
||||
public initializeItemUpdate(): void {
|
||||
this.itemRD$ = this.requestService.hasByHref$(this.item.self).pipe(
|
||||
filter((exists: boolean) => !exists),
|
||||
switchMap(() => this.itemService.findById(
|
||||
this.item.uuid,
|
||||
true,
|
||||
followLink('owningCollection'),
|
||||
followLink('bundles'),
|
||||
followLink('relationships')),
|
||||
),
|
||||
filter((itemRD) => !!itemRD.statusCode),
|
||||
);
|
||||
|
||||
this.itemRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
).subscribe((item) => {
|
||||
this.item = item;
|
||||
this.cdr.detectChanges();
|
||||
this.initializeUpdates();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the values and updates of the current item's relationship fields
|
||||
*/
|
||||
@@ -118,6 +87,8 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
|
||||
switchMap((entityType) =>
|
||||
this.entityTypeService.getEntityTypeRelationships(
|
||||
entityType.id,
|
||||
true,
|
||||
true,
|
||||
followLink('leftType'),
|
||||
followLink('rightType'))
|
||||
.pipe(
|
||||
@@ -183,11 +154,9 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
|
||||
actions.forEach((action) =>
|
||||
action.subscribe((response) => {
|
||||
if (response.length > 0) {
|
||||
this.itemRD$.subscribe(() => {
|
||||
this.initializeOriginalFields();
|
||||
this.cdr.detectChanges();
|
||||
this.displayNotifications(response);
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -258,6 +227,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
|
||||
* Sends all initial values of this item to the object updates service
|
||||
*/
|
||||
public initializeOriginalFields() {
|
||||
console.log('init');
|
||||
return this.relationshipService.getRelatedItems(this.item).pipe(
|
||||
take(1),
|
||||
).subscribe((items: Item[]) => {
|
||||
|
@@ -67,6 +67,7 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
|
||||
'ORIGINAL',
|
||||
{elementsPerPage: this.pageSize, currentPage: pageNumber},
|
||||
true,
|
||||
true,
|
||||
followLink('format')
|
||||
)),
|
||||
tap((rd: RemoteData<PaginatedList<Bitstream>>) => {
|
||||
@@ -83,6 +84,7 @@ export class FullFileSectionComponent extends FileSectionComponent implements On
|
||||
'LICENSE',
|
||||
{elementsPerPage: this.pageSize, currentPage: pageNumber},
|
||||
true,
|
||||
true,
|
||||
followLink('format')
|
||||
)),
|
||||
tap((rd: RemoteData<PaginatedList<Bitstream>>) => {
|
||||
|
@@ -4,10 +4,17 @@ import { Observable } from 'rxjs';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { ItemDataService } from '../core/data/item-data.service';
|
||||
import { Item } from '../core/shared/item.model';
|
||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||
import { followLink, FollowLinkConfig } from '../shared/utils/follow-link-config.model';
|
||||
import { FindListOptions } from '../core/data/request.models';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
|
||||
export const ITEM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Item>[] = [
|
||||
followLink('owningCollection'),
|
||||
followLink('bundles', new FindListOptions(), true, true, true, followLink('bitstreams')),
|
||||
followLink('relationships'),
|
||||
followLink('version', undefined, true, true, true, followLink('versionhistory')),
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific item before the route is activated
|
||||
*/
|
||||
@@ -25,11 +32,9 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||
return this.itemService.findById(route.params.id,
|
||||
true,
|
||||
false,
|
||||
followLink('owningCollection'),
|
||||
followLink('bundles', new FindListOptions(), true, followLink('bitstreams')),
|
||||
followLink('relationships'),
|
||||
followLink('version', undefined, true, followLink('versionhistory')),
|
||||
...ITEM_PAGE_LINKS_TO_FOLLOW
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs';
|
||||
import {
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
of as observableOf,
|
||||
zip as observableZip
|
||||
} from 'rxjs';
|
||||
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||
import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
|
||||
@@ -82,7 +87,7 @@ export class MetadataRepresentationListComponent extends AbstractIncrementalList
|
||||
.map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
|
||||
.map((metadatum: MetadataValue) => {
|
||||
if (metadatum.isVirtual) {
|
||||
return this.relationshipService.findById(metadatum.virtualValue, false, followLink('leftItem'), followLink('rightItem')).pipe(
|
||||
return this.relationshipService.findById(metadatum.virtualValue, true, false, followLink('leftItem'), followLink('rightItem')).pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
switchMap((relRD: RemoteData<Relationship>) =>
|
||||
observableCombineLatest(relRD.payload.leftItem, relRD.payload.rightItem).pipe(
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FindByIDRequest, IdentifierType } from '../core/data/request.models';
|
||||
import { IdentifierType } from '../core/data/request.models';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
|
||||
interface LookupParams {
|
||||
type: IdentifierType;
|
||||
@@ -20,7 +21,7 @@ export class LookupGuard implements CanActivate {
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
const params = this.getLookupParams(route);
|
||||
return this.dsoService.findByIdAndIDType(params.id, params.type).pipe(
|
||||
map((response: RemoteData<FindByIDRequest>) => response.hasFailed)
|
||||
map((response: RemoteData<DSpaceObject>) => response.hasFailed)
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -24,6 +24,7 @@ export class WorkflowItemPageResolver implements Resolve<RemoteData<WorkflowItem
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<WorkflowItem>> {
|
||||
return this.workflowItemService.findById(route.params.id,
|
||||
true,
|
||||
false,
|
||||
followLink('item'),
|
||||
).pipe(
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
@@ -40,6 +40,9 @@ import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component';
|
||||
import { environment } from '../environments/environment';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||
import { AuthInterceptor } from './core/auth/auth.interceptor';
|
||||
import { LocaleInterceptor } from './core/locale/locale.interceptor';
|
||||
import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
|
||||
|
||||
export function getBase() {
|
||||
return environment.ui.nameSpace;
|
||||
@@ -94,6 +97,24 @@ const PROVIDERS = [
|
||||
deps: [ Store ],
|
||||
multi: true
|
||||
},
|
||||
// register AuthInterceptor as HttpInterceptor
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi: true
|
||||
},
|
||||
// register LocaleInterceptor as HttpInterceptor
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: LocaleInterceptor,
|
||||
multi: true
|
||||
},
|
||||
// register XsrfInterceptor as HttpInterceptor
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: XsrfInterceptor,
|
||||
multi: true
|
||||
},
|
||||
...DYNAMIC_MATCHER_PROVIDERS,
|
||||
];
|
||||
|
||||
|
@@ -45,11 +45,7 @@ export class AuthRequestService {
|
||||
map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
|
||||
distinctUntilChanged(),
|
||||
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, body, options)),
|
||||
map ((request: PostRequest) => {
|
||||
request.responseMsToLive = 10 * 1000;
|
||||
return request;
|
||||
}),
|
||||
tap((request: PostRequest) => this.requestService.configure(request)),
|
||||
tap((request: PostRequest) => this.requestService.send(request)),
|
||||
mergeMap((request: PostRequest) => this.fetchRequest(request)),
|
||||
distinctUntilChanged());
|
||||
}
|
||||
@@ -60,11 +56,7 @@ export class AuthRequestService {
|
||||
map((endpointURL) => this.getEndpointByMethod(endpointURL, method)),
|
||||
distinctUntilChanged(),
|
||||
map((endpointURL: string) => new GetRequest(this.requestService.generateRequestId(), endpointURL, undefined, options)),
|
||||
map ((request: GetRequest) => {
|
||||
request.forceBypassCache = true;
|
||||
return request;
|
||||
}),
|
||||
tap((request: GetRequest) => this.requestService.configure(request)),
|
||||
tap((request: GetRequest) => this.requestService.send(request)),
|
||||
mergeMap((request: GetRequest) => this.fetchRequest(request)),
|
||||
distinctUntilChanged());
|
||||
}
|
||||
@@ -78,7 +70,7 @@ export class AuthRequestService {
|
||||
distinctUntilChanged(),
|
||||
map((href: string) => new URLCombiner(href, this.shortlivedtokensEndpoint).toString()),
|
||||
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)),
|
||||
tap((request: PostRequest) => this.requestService.configure(request)),
|
||||
tap((request: PostRequest) => this.requestService.send(request)),
|
||||
switchMap((request: PostRequest) => this.rdbService.buildFromRequestUUID<ShortLivedToken>(request.uuid)),
|
||||
getFirstCompletedRemoteData(),
|
||||
map((response: RemoteData<ShortLivedToken>) => {
|
||||
|
@@ -269,7 +269,7 @@ export class AuthService {
|
||||
let headers = new HttpHeaders();
|
||||
headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
||||
const options: HttpOptions = Object.create({ headers, responseType: 'text' });
|
||||
return this.authRequestService.getRequest('logout', options).pipe(
|
||||
return this.authRequestService.postToEndpoint('logout', options).pipe(
|
||||
map((rd: RemoteData<AuthStatus>) => {
|
||||
const status = rd.payload;
|
||||
if (hasValue(status) && !status.authenticated) {
|
||||
|
@@ -23,7 +23,7 @@ export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collecti
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Collection>[] {
|
||||
return [
|
||||
followLink('parentCommunity', undefined, true,
|
||||
followLink('parentCommunity', undefined, true, true, true,
|
||||
followLink('parentCommunity')
|
||||
)
|
||||
];
|
||||
|
@@ -29,7 +29,7 @@ export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceO
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<T>> {
|
||||
const uuid = route.params.id;
|
||||
return this.dataService.findById(uuid, false, ...this.followLinks).pipe(
|
||||
return this.dataService.findById(uuid, true, false, ...this.followLinks).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((object: T) => {
|
||||
|
@@ -23,8 +23,8 @@ export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> {
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Item>[] {
|
||||
return [
|
||||
followLink('owningCollection', undefined, true,
|
||||
followLink('parentCommunity', undefined, true,
|
||||
followLink('owningCollection', undefined, true, true, true,
|
||||
followLink('parentCommunity', undefined, true, true, true,
|
||||
followLink('parentCommunity'))
|
||||
),
|
||||
followLink('bundles'),
|
||||
|
@@ -25,22 +25,22 @@ describe(`BrowseDefinitionDataService`, () => {
|
||||
|
||||
describe(`findAll`, () => {
|
||||
it(`should call findAll on DataServiceImpl`, () => {
|
||||
service.findAll(options, false, ...linksToFollow);
|
||||
expect(dataServiceImplSpy.findAll).toHaveBeenCalledWith(options, false, ...linksToFollow);
|
||||
service.findAll(options, true, false, ...linksToFollow);
|
||||
expect(dataServiceImplSpy.findAll).toHaveBeenCalledWith(options, true, false, ...linksToFollow);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`findByHref`, () => {
|
||||
it(`should call findByHref on DataServiceImpl`, () => {
|
||||
service.findByHref(hrefSingle, false, ...linksToFollow);
|
||||
expect(dataServiceImplSpy.findByHref).toHaveBeenCalledWith(hrefSingle, false, ...linksToFollow);
|
||||
service.findByHref(hrefSingle, true, false, ...linksToFollow);
|
||||
expect(dataServiceImplSpy.findByHref).toHaveBeenCalledWith(hrefSingle, true, false, ...linksToFollow);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`findAllByHref`, () => {
|
||||
it(`should call findAllByHref on DataServiceImpl`, () => {
|
||||
service.findAllByHref(hrefAll, options, false, ...linksToFollow);
|
||||
expect(dataServiceImplSpy.findAllByHref).toHaveBeenCalledWith(hrefAll, options, false, ...linksToFollow);
|
||||
service.findAllByHref(hrefAll, options, true, false, ...linksToFollow);
|
||||
expect(dataServiceImplSpy.findAllByHref).toHaveBeenCalledWith(hrefAll, options, true, false, ...linksToFollow);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -63,42 +63,48 @@ export class BrowseDefinitionDataService {
|
||||
* info should be added to the objects
|
||||
*
|
||||
* @param options Find list options object
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<BrowseDefinition>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
findAll(options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||
return this.dataService.findAll(options, reRequestOnStale, ...linksToFollow);
|
||||
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||
return this.dataService.findAll(options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of an {@link BrowseDefinition}, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the {@link BrowseDefinition}
|
||||
* @param href The url of {@link BrowseDefinition} we want to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<BrowseDefinition>> {
|
||||
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow);
|
||||
findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<BrowseDefinition>> {
|
||||
return this.dataService.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of observables of {@link RemoteData} of {@link BrowseDefinition}s, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the {@link BrowseDefinition}
|
||||
* @param href The url of the {@link BrowseDefinition} we want to retrieve
|
||||
* @param href The url of object we want to retrieve
|
||||
* @param findListOptions Find list options object
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||
return this.dataService.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow);
|
||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<BrowseDefinition>[]): Observable<RemoteData<PaginatedList<BrowseDefinition>>> {
|
||||
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,8 +11,8 @@ import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
||||
import { BrowseService } from './browse.service';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { GetRequest } from '../data/request.models';
|
||||
import { createPaginatedList, getFirstUsedArgumentOfSpyMethod } from '../../shared/testing/utils.test';
|
||||
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
||||
|
||||
describe('BrowseService', () => {
|
||||
let scheduler: TestScheduler;
|
||||
@@ -82,6 +82,7 @@ describe('BrowseService', () => {
|
||||
];
|
||||
|
||||
let browseDefinitionDataService;
|
||||
let hrefOnlyDataService;
|
||||
|
||||
const getRequestEntry$ = (successful: boolean) => {
|
||||
return observableOf({
|
||||
@@ -93,10 +94,12 @@ describe('BrowseService', () => {
|
||||
browseDefinitionDataService = jasmine.createSpyObj('browseDefinitionDataService', {
|
||||
findAll: createSuccessfulRemoteDataObject$(createPaginatedList(browseDefinitions))
|
||||
});
|
||||
hrefOnlyDataService = getMockHrefOnlyDataService();
|
||||
return new BrowseService(
|
||||
requestService,
|
||||
halService,
|
||||
browseDefinitionDataService,
|
||||
hrefOnlyDataService,
|
||||
rdbService
|
||||
);
|
||||
}
|
||||
@@ -123,54 +126,40 @@ describe('BrowseService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBrowseEntriesFor and getBrowseItemsFor', () => {
|
||||
describe('getBrowseEntriesFor and findList', () => {
|
||||
const mockAuthorName = 'Donald Smith';
|
||||
|
||||
beforeEach(() => {
|
||||
requestService = getMockRequestService(getRequestEntry$(true));
|
||||
rdbService = getMockRemoteDataBuildService();
|
||||
service = initTestService();
|
||||
spyOn(service, 'getBrowseDefinitions').and
|
||||
.returnValue(hot('--a-', {
|
||||
a: createSuccessfulRemoteDataObject(createPaginatedList(browseDefinitions))
|
||||
}));
|
||||
spyOn(rdbService, 'buildList').and.callThrough();
|
||||
});
|
||||
|
||||
describe('when getBrowseEntriesFor is called with a valid browse definition id', () => {
|
||||
it('should configure a new BrowseEntriesRequest', () => {
|
||||
const expected = new GetRequest(requestService.generateRequestId(), browseDefinitions[1]._links.entries.href);
|
||||
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||
const expected = browseDefinitions[1]._links.entries.href;
|
||||
|
||||
scheduler.schedule(() => service.getBrowseEntriesFor(new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
service.getBrowseEntriesFor(new BrowseEntrySearchOptions(browseDefinitions[1].id));
|
||||
|
||||
expect(rdbService.buildList).toHaveBeenCalled();
|
||||
|
||||
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
||||
a: expected
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when getBrowseItemsFor is called with a valid browse definition id', () => {
|
||||
it('should configure a new BrowseItemsRequest', () => {
|
||||
const expected = new GetRequest(requestService.generateRequestId(), browseDefinitions[1]._links.items.href + '?filterValue=' + mockAuthorName);
|
||||
describe('when findList is called with a valid browse definition id', () => {
|
||||
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||
const expected = browseDefinitions[1]._links.items.href + '?filterValue=' + mockAuthorName;
|
||||
|
||||
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id));
|
||||
|
||||
expect(rdbService.buildList).toHaveBeenCalled();
|
||||
|
||||
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
||||
a: expected
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
@@ -256,29 +245,19 @@ describe('BrowseService', () => {
|
||||
requestService = getMockRequestService();
|
||||
rdbService = getMockRemoteDataBuildService();
|
||||
service = initTestService();
|
||||
spyOn(service, 'getBrowseDefinitions').and
|
||||
.returnValue(hot('--a-', {
|
||||
a: createSuccessfulRemoteDataObject(createPaginatedList(browseDefinitions))
|
||||
}));
|
||||
spyOn(rdbService, 'buildList').and.callThrough();
|
||||
});
|
||||
|
||||
describe('when getFirstItemFor is called with a valid browse definition id', () => {
|
||||
const expectedURL = browseDefinitions[1]._links.items.href + '?page=0&size=1';
|
||||
|
||||
it('should configure a new BrowseItemsRequest', () => {
|
||||
const expected = new GetRequest(requestService.generateRequestId(), expectedURL);
|
||||
|
||||
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||
scheduler.schedule(() => service.getFirstItemFor(browseDefinitions[1].id).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
});
|
||||
|
||||
it('should call RemoteDataBuildService to create the RemoteData Observable', () => {
|
||||
service.getFirstItemFor(browseDefinitions[1].id);
|
||||
|
||||
expect(rdbService.buildList).toHaveBeenCalled();
|
||||
expect(getFirstUsedArgumentOfSpyMethod(hrefOnlyDataService.findAllByHref)).toBeObservable(cold('(a|)', {
|
||||
a: expectedURL
|
||||
}));
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -1,11 +1,10 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import { distinctUntilChanged, map, startWith, take } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map, startWith } from 'rxjs/operators';
|
||||
import { hasValue, hasValueOperator, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { PaginatedList } from '../data/paginated-list.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { GetRequest } from '../data/request.models';
|
||||
import { RequestService } from '../data/request.service';
|
||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||
import { BrowseEntry } from '../shared/browse-entry.model';
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
||||
import { BrowseDefinitionDataService } from './browse-definition-data.service';
|
||||
import { HrefOnlyDataService } from '../data/href-only-data.service';
|
||||
|
||||
/**
|
||||
* The service handling all browse requests
|
||||
@@ -46,6 +46,7 @@ export class BrowseService {
|
||||
protected requestService: RequestService,
|
||||
protected halService: HALEndpointService,
|
||||
private browseDefinitionDataService: BrowseDefinitionDataService,
|
||||
private hrefOnlyDataService: HrefOnlyDataService,
|
||||
private rdb: RemoteDataBuildService,
|
||||
) {
|
||||
}
|
||||
@@ -65,7 +66,7 @@ export class BrowseService {
|
||||
* @param options
|
||||
*/
|
||||
getBrowseEntriesFor(options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
|
||||
return this.getBrowseDefinitions().pipe(
|
||||
const href$ = this.getBrowseDefinitions().pipe(
|
||||
getBrowseDefinitionLinks(options.metadataDefinition),
|
||||
hasValueOperator(),
|
||||
map((_links: any) => {
|
||||
@@ -93,9 +94,9 @@ export class BrowseService {
|
||||
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||
}
|
||||
return href;
|
||||
}),
|
||||
getBrowseEntriesFor(this.requestService, this.rdb)
|
||||
})
|
||||
);
|
||||
return this.hrefOnlyDataService.findAllByHref<BrowseEntry>(href$);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +106,7 @@ export class BrowseService {
|
||||
* @returns {Observable<RemoteData<PaginatedList<Item>>>}
|
||||
*/
|
||||
getBrowseItemsFor(filterValue: string, options: BrowseEntrySearchOptions): Observable<RemoteData<PaginatedList<Item>>> {
|
||||
return this.getBrowseDefinitions().pipe(
|
||||
const href$ = this.getBrowseDefinitions().pipe(
|
||||
getBrowseDefinitionLinks(options.metadataDefinition),
|
||||
hasValueOperator(),
|
||||
map((_links: any) => {
|
||||
@@ -136,8 +137,8 @@ export class BrowseService {
|
||||
}
|
||||
return href;
|
||||
}),
|
||||
getBrowseItemsFor(this.requestService, this.rdb)
|
||||
);
|
||||
return this.hrefOnlyDataService.findAllByHref<Item>(href$);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,7 +147,7 @@ export class BrowseService {
|
||||
* @param scope
|
||||
*/
|
||||
getFirstItemFor(definition: string, scope?: string): Observable<RemoteData<Item>> {
|
||||
return this.getBrowseDefinitions().pipe(
|
||||
const href$ = this.getBrowseDefinitions().pipe(
|
||||
getBrowseDefinitionLinks(definition),
|
||||
hasValueOperator(),
|
||||
map((_links: any) => {
|
||||
@@ -165,11 +166,14 @@ export class BrowseService {
|
||||
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||
}
|
||||
return href;
|
||||
}),
|
||||
getBrowseItemsFor(this.requestService, this.rdb),
|
||||
})
|
||||
);
|
||||
|
||||
return this.hrefOnlyDataService.findAllByHref<Item>(href$).pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getFirstOccurrence()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -177,9 +181,7 @@ export class BrowseService {
|
||||
* @param items
|
||||
*/
|
||||
getPrevBrowseItems(items: RemoteData<PaginatedList<Item>>): Observable<RemoteData<PaginatedList<Item>>> {
|
||||
return observableOf(items.payload.prev).pipe(
|
||||
getBrowseItemsFor(this.requestService, this.rdb)
|
||||
);
|
||||
return this.hrefOnlyDataService.findAllByHref<Item>(items.payload.prev);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -187,9 +189,7 @@ export class BrowseService {
|
||||
* @param items
|
||||
*/
|
||||
getNextBrowseItems(items: RemoteData<PaginatedList<Item>>): Observable<RemoteData<PaginatedList<Item>>> {
|
||||
return observableOf(items.payload.next).pipe(
|
||||
getBrowseItemsFor(this.requestService, this.rdb)
|
||||
);
|
||||
return this.hrefOnlyDataService.findAllByHref<Item>(items.payload.next);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,9 +197,7 @@ export class BrowseService {
|
||||
* @param entries
|
||||
*/
|
||||
getPrevBrowseEntries(entries: RemoteData<PaginatedList<BrowseEntry>>): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
|
||||
return observableOf(entries.payload.prev).pipe(
|
||||
getBrowseEntriesFor(this.requestService, this.rdb)
|
||||
);
|
||||
return this.hrefOnlyDataService.findAllByHref<BrowseEntry>(entries.payload.prev);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -207,9 +205,7 @@ export class BrowseService {
|
||||
* @param entries
|
||||
*/
|
||||
getNextBrowseEntries(entries: RemoteData<PaginatedList<BrowseEntry>>): Observable<RemoteData<PaginatedList<BrowseEntry>>> {
|
||||
return observableOf(entries.payload.next).pipe(
|
||||
getBrowseEntriesFor(this.requestService, this.rdb)
|
||||
);
|
||||
return this.hrefOnlyDataService.findAllByHref<BrowseEntry>(entries.payload.next);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,39 +237,3 @@ export class BrowseService {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Operator for turning a href into a PaginatedList of BrowseEntries
|
||||
* @param requestService
|
||||
* @param responseCache
|
||||
* @param rdb
|
||||
*/
|
||||
export const getBrowseEntriesFor = (requestService: RequestService, rdb: RemoteDataBuildService) =>
|
||||
(source: Observable<string>): Observable<RemoteData<PaginatedList<BrowseEntry>>> => {
|
||||
const requestId = requestService.generateRequestId();
|
||||
|
||||
source.pipe(take(1)).subscribe((href: string) => {
|
||||
const request = new GetRequest(requestId, href);
|
||||
requestService.configure(request);
|
||||
});
|
||||
|
||||
return rdb.buildList(source);
|
||||
};
|
||||
|
||||
/**
|
||||
* Operator for turning a href into a PaginatedList of Items
|
||||
* @param requestService
|
||||
* @param responseCache
|
||||
* @param rdb
|
||||
*/
|
||||
export const getBrowseItemsFor = (requestService: RequestService, rdb: RemoteDataBuildService) =>
|
||||
(source: Observable<string>): Observable<RemoteData<PaginatedList<Item>>> => {
|
||||
const requestId = requestService.generateRequestId();
|
||||
|
||||
source.pipe(take(1)).subscribe((href: string) => {
|
||||
const request = new GetRequest(requestId, href);
|
||||
requestService.configure(request);
|
||||
});
|
||||
|
||||
return rdb.buildList(source);
|
||||
};
|
||||
|
18
src/app/core/cache/builders/link.service.spec.ts
vendored
18
src/app/core/cache/builders/link.service.spec.ts
vendored
@@ -38,11 +38,11 @@ class TestModel implements HALResource {
|
||||
|
||||
@Injectable()
|
||||
class TestDataService {
|
||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<any>[]) {
|
||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<any>[]) {
|
||||
return 'findAllByHref';
|
||||
}
|
||||
|
||||
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<any>[]) {
|
||||
findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<any>[]) {
|
||||
return 'findByHref';
|
||||
}
|
||||
}
|
||||
@@ -90,10 +90,10 @@ xdescribe('LinkService', () => {
|
||||
propertyName: 'predecessor'
|
||||
});
|
||||
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
|
||||
service.resolveLink(testModel, followLink('predecessor', {}, true, followLink('successor')));
|
||||
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
|
||||
});
|
||||
it('should call dataservice.findByHref with the correct href and nested links', () => {
|
||||
expect(testDataService.findByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, true, followLink('successor'));
|
||||
expect(testDataService.findByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, true, true, followLink('successor'));
|
||||
});
|
||||
});
|
||||
describe(`when the linkdefinition concerns a list`, () => {
|
||||
@@ -105,10 +105,10 @@ xdescribe('LinkService', () => {
|
||||
isList: true
|
||||
});
|
||||
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
|
||||
service.resolveLink(testModel, followLink('predecessor', { some: 'options ' } as any, true, followLink('successor')));
|
||||
service.resolveLink(testModel, followLink('predecessor', { some: 'options ' } as any, true, true, true, followLink('successor')));
|
||||
});
|
||||
it('should call dataservice.findAllByHref with the correct href, findListOptions, and nested links', () => {
|
||||
expect(testDataService.findAllByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, { some: 'options ' } as any, true, followLink('successor'));
|
||||
expect(testDataService.findAllByHref).toHaveBeenCalledWith(testModel._links.predecessor.href, { some: 'options ' } as any, true, true, followLink('successor'));
|
||||
});
|
||||
});
|
||||
describe('either way', () => {
|
||||
@@ -119,7 +119,7 @@ xdescribe('LinkService', () => {
|
||||
propertyName: 'predecessor'
|
||||
});
|
||||
spyOnFunction(decorators, 'getDataServiceFor').and.returnValue(TestDataService);
|
||||
result = service.resolveLink(testModel, followLink('predecessor', {}, true, followLink('successor')));
|
||||
result = service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
|
||||
});
|
||||
|
||||
it('should call getLinkDefinition with the correct model and link', () => {
|
||||
@@ -144,7 +144,7 @@ xdescribe('LinkService', () => {
|
||||
});
|
||||
it('should throw an error', () => {
|
||||
expect(() => {
|
||||
service.resolveLink(testModel, followLink('predecessor', {}, true, followLink('successor')));
|
||||
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -160,7 +160,7 @@ xdescribe('LinkService', () => {
|
||||
});
|
||||
it('should throw an error', () => {
|
||||
expect(() => {
|
||||
service.resolveLink(testModel, followLink('predecessor', {}, true, followLink('successor')));
|
||||
service.resolveLink(testModel, followLink('predecessor', {}, true, true, true, followLink('successor')));
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
4
src/app/core/cache/builders/link.service.ts
vendored
4
src/app/core/cache/builders/link.service.ts
vendored
@@ -61,9 +61,9 @@ export class LinkService {
|
||||
|
||||
try {
|
||||
if (matchingLinkDef.isList) {
|
||||
model[linkToFollow.name] = service.findAllByHref(href, linkToFollow.findListOptions, true, ...linkToFollow.linksToFollow);
|
||||
model[linkToFollow.name] = service.findAllByHref(href, linkToFollow.findListOptions, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
|
||||
} else {
|
||||
model[linkToFollow.name] = service.findByHref(href, true, ...linkToFollow.linksToFollow);
|
||||
model[linkToFollow.name] = service.findByHref(href, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Something went wrong when using @dataService(${matchingLinkDef.resourceType.value}) ${hasValue(service) ? '' : '(undefined) '}to resolve link ${linkToFollow.name} at ${href}`);
|
||||
|
@@ -523,7 +523,7 @@ describe('RemoteDataBuildService', () => {
|
||||
let paginatedLinksToFollow;
|
||||
beforeEach(() => {
|
||||
paginatedLinksToFollow = [
|
||||
followLink('page', undefined, true, ...linksToFollow),
|
||||
followLink('page', undefined, true, true, true, ...linksToFollow),
|
||||
...linksToFollow
|
||||
];
|
||||
});
|
||||
|
@@ -271,7 +271,7 @@ export class RemoteDataBuildService {
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
buildList<T extends HALResource>(href$: string | Observable<string>, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
return this.buildFromHref<PaginatedList<T>>(href$, followLink('page', undefined, false, ...linksToFollow));
|
||||
return this.buildFromHref<PaginatedList<T>>(href$, followLink('page', undefined, false, true, true, ...linksToFollow));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -94,14 +94,16 @@ export class ServerSyncBufferEffects {
|
||||
* @returns {Observable<Action>} ApplyPatchObjectCacheAction to be dispatched
|
||||
*/
|
||||
private applyPatch(href: string): Observable<Action> {
|
||||
const patchObject = this.objectCache.getByHref(href).pipe(take(1));
|
||||
const patchObject = this.objectCache.getByHref(href).pipe(
|
||||
take(1)
|
||||
);
|
||||
|
||||
return patchObject.pipe(
|
||||
map((entry: ObjectCacheEntry) => {
|
||||
if (isNotEmpty(entry.patches)) {
|
||||
const flatPatch: Operation[] = [].concat(...entry.patches.map((patch) => patch.operations));
|
||||
if (isNotEmpty(flatPatch)) {
|
||||
this.requestService.configure(new PatchRequest(this.requestService.generateRequestId(), href, flatPatch));
|
||||
this.requestService.send(new PatchRequest(this.requestService.generateRequestId(), href, flatPatch));
|
||||
}
|
||||
}
|
||||
return new ApplyPatchObjectCacheAction(href);
|
||||
|
@@ -58,12 +58,12 @@ describe('ConfigService', () => {
|
||||
|
||||
describe('findByHref', () => {
|
||||
|
||||
it('should configure a new GetRequest', () => {
|
||||
it('should send a new GetRequest', () => {
|
||||
const expected = new GetRequest(requestService.generateRequestId(), scopedEndpoint);
|
||||
scheduler.schedule(() => service.findByHref(scopedEndpoint).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -52,8 +52,8 @@ export abstract class ConfigService {
|
||||
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator, this.linkPath);
|
||||
}
|
||||
|
||||
public findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<ConfigObject>[]): Observable<RemoteData<ConfigObject>> {
|
||||
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow).pipe(
|
||||
public findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<ConfigObject>[]): Observable<RemoteData<ConfigObject>> {
|
||||
return this.dataService.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((rd: RemoteData<ConfigObject>) => {
|
||||
if (rd.hasFailed) {
|
||||
|
@@ -34,7 +34,7 @@ export class SubmissionFormsConfigService extends ConfigService {
|
||||
super(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator, 'submissionforms');
|
||||
}
|
||||
|
||||
public findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SubmissionFormsModel>[]): Observable<RemoteData<SubmissionFormsModel>> {
|
||||
return super.findByHref(href, reRequestOnStale, ...linksToFollow as FollowLinkConfig<ConfigObject>[]) as Observable<RemoteData<SubmissionFormsModel>>;
|
||||
public findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<SubmissionFormsModel>[]): Observable<RemoteData<SubmissionFormsModel>> {
|
||||
return super.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow as FollowLinkConfig<ConfigObject>[]) as Observable<RemoteData<SubmissionFormsModel>>;
|
||||
}
|
||||
}
|
||||
|
@@ -36,7 +36,7 @@ export class SubmissionUploadsConfigService extends ConfigService {
|
||||
super(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator, 'submissionuploads');
|
||||
}
|
||||
|
||||
findByHref(href: string, reRequestOnStale = true, ...linksToFollow): Observable<RemoteData<SubmissionUploadsModel>> {
|
||||
return super.findByHref(href, reRequestOnStale, ...linksToFollow as FollowLinkConfig<ConfigObject>[]) as Observable<RemoteData<SubmissionUploadsModel>>;
|
||||
findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow): Observable<RemoteData<SubmissionUploadsModel>> {
|
||||
return super.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow as FollowLinkConfig<ConfigObject>[]) as Observable<RemoteData<SubmissionUploadsModel>>;
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { HTTP_INTERCEPTORS, HttpClient } from '@angular/common/http';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
|
||||
|
||||
import { DynamicFormLayoutService, DynamicFormService, DynamicFormValidationService } from '@ng-dynamic-forms/core';
|
||||
import {
|
||||
DynamicFormLayoutService,
|
||||
DynamicFormService,
|
||||
DynamicFormValidationService
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
|
||||
import { Action, StoreConfig, StoreModule } from '@ngrx/store';
|
||||
@@ -28,7 +32,6 @@ import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { UploaderService } from '../shared/uploader/uploader.service';
|
||||
import { SectionFormOperationsService } from '../submission/sections/form/section-form-operations.service';
|
||||
import { AuthRequestService } from './auth/auth-request.service';
|
||||
import { AuthInterceptor } from './auth/auth.interceptor';
|
||||
import { AuthenticatedGuard } from './auth/authenticated.guard';
|
||||
import { AuthStatus } from './auth/models/auth-status.model';
|
||||
import { BrowseService } from './browse/browse.service';
|
||||
@@ -51,14 +54,12 @@ import { DSOChangeAnalyzer } from './data/dso-change-analyzer.service';
|
||||
import { DSOResponseParsingService } from './data/dso-response-parsing.service';
|
||||
import { DSpaceObjectDataService } from './data/dspace-object-data.service';
|
||||
import { EndpointMapResponseParsingService } from './data/endpoint-map-response-parsing.service';
|
||||
import { ItemTypeDataService } from './data/entity-type-data.service';
|
||||
import { EntityTypeService } from './data/entity-type.service';
|
||||
import { ExternalSourceService } from './data/external-source.service';
|
||||
import { FacetConfigResponseParsingService } from './data/facet-config-response-parsing.service';
|
||||
import { FacetValueResponseParsingService } from './data/facet-value-response-parsing.service';
|
||||
import { FilteredDiscoveryPageResponseParsingService } from './data/filtered-discovery-page-response-parsing.service';
|
||||
import { ItemDataService } from './data/item-data.service';
|
||||
import { LicenseDataService } from './data/license-data.service';
|
||||
import { LookupRelationService } from './data/lookup-relation.service';
|
||||
import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing.service';
|
||||
import { ObjectUpdatesService } from './data/object-updates/object-updates.service';
|
||||
@@ -130,7 +131,6 @@ import { ProcessDataService } from './data/processes/process-data.service';
|
||||
import { ScriptDataService } from './data/processes/script-data.service';
|
||||
import { WorkflowActionDataService } from './data/workflow-action-data.service';
|
||||
import { WorkflowAction } from './tasks/models/workflow-action-object.model';
|
||||
import { LocaleInterceptor } from './locale/locale.interceptor';
|
||||
import { ItemTemplateDataService } from './data/item-template-data.service';
|
||||
import { TemplateItem } from './shared/template-item.model';
|
||||
import { Feature } from './shared/feature.model';
|
||||
@@ -160,6 +160,7 @@ import { EndUserAgreementService } from './end-user-agreement/end-user-agreement
|
||||
import { SiteRegisterGuard } from './data/feature-authorization/feature-authorization-guard/site-register.guard';
|
||||
import { ShortLivedToken } from './auth/models/short-lived-token.model';
|
||||
import { UsageReport } from './statistics/models/usage-report.model';
|
||||
import { RootDataService } from './data/root-data.service';
|
||||
import { Root } from './data/root.model';
|
||||
|
||||
/**
|
||||
@@ -263,8 +264,6 @@ const PROVIDERS = [
|
||||
LookupRelationService,
|
||||
VersionDataService,
|
||||
VersionHistoryDataService,
|
||||
LicenseDataService,
|
||||
ItemTypeDataService,
|
||||
WorkflowActionDataService,
|
||||
ProcessDataService,
|
||||
ScriptDataService,
|
||||
@@ -279,18 +278,7 @@ const PROVIDERS = [
|
||||
EndUserAgreementCurrentUserGuard,
|
||||
EndUserAgreementCookieGuard,
|
||||
EndUserAgreementService,
|
||||
// register AuthInterceptor as HttpInterceptor
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi: true
|
||||
},
|
||||
// register LocaleInterceptor as HttpInterceptor
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: LocaleInterceptor,
|
||||
multi: true
|
||||
},
|
||||
RootDataService,
|
||||
NotificationsService,
|
||||
FilteredDiscoveryPageResponseParsingService,
|
||||
{ provide: NativeWindowService, useFactory: NativeWindowFactory },
|
||||
@@ -353,6 +341,7 @@ export const models =
|
||||
ShortLivedToken,
|
||||
Registration,
|
||||
UsageReport,
|
||||
Root,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@@ -55,8 +55,8 @@ describe('BitstreamDataService', () => {
|
||||
service.updateFormat(bitstream, format);
|
||||
});
|
||||
|
||||
it('should configure a put request', () => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PutRequest));
|
||||
it('should send a put request', () => {
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PutRequest));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -25,7 +25,7 @@ import { RequestService } from './request.service';
|
||||
import { BitstreamFormatDataService } from './bitstream-format-data.service';
|
||||
import { BitstreamFormat } from '../shared/bitstream-format.model';
|
||||
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||
import { configureRequest } from '../shared/operators';
|
||||
import { sendRequest } from '../shared/operators';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { PageInfo } from '../shared/page-info.model';
|
||||
import { RequestEntryState } from './request.reducer';
|
||||
@@ -64,13 +64,15 @@ export class BitstreamDataService extends DataService<Bitstream> {
|
||||
*
|
||||
* @param bundle the bundle to retrieve bitstreams from
|
||||
* @param options options for the find all request
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findAllByBundle(bundle: Bundle, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bitstream>[]): Observable<RemoteData<PaginatedList<Bitstream>>> {
|
||||
return this.findAllByHref(bundle._links.bitstreams.href, options, reRequestOnStale, ...linksToFollow);
|
||||
findAllByBundle(bundle: Bundle, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bitstream>[]): Observable<RemoteData<PaginatedList<Bitstream>>> {
|
||||
return this.findAllByHref(bundle._links.bitstreams.href, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +122,7 @@ export class BitstreamDataService extends DataService<Bitstream> {
|
||||
return this.bundleService.findByItemAndName(item, 'THUMBNAIL').pipe(
|
||||
switchMap((bundleRD: RemoteData<Bundle>) => {
|
||||
if (isNotEmpty(bundleRD.payload)) {
|
||||
return this.findAllByBundle(bundleRD.payload, { elementsPerPage: Number.MAX_SAFE_INTEGER }).pipe(
|
||||
return this.findAllByBundle(bundleRD.payload, { elementsPerPage: 9999 }).pipe(
|
||||
map((bitstreamRD: RemoteData<PaginatedList<Bitstream>>) => {
|
||||
if (hasValue(bitstreamRD.payload) && hasValue(bitstreamRD.payload.page)) {
|
||||
const matchingThumbnail = bitstreamRD.payload.page.find((thumbnail: Bitstream) =>
|
||||
@@ -166,18 +168,21 @@ export class BitstreamDataService extends DataService<Bitstream> {
|
||||
* in all current use cases, and having it simplifies this method
|
||||
*
|
||||
* @param item the {@link Item} the {@link Bundle} is a part of
|
||||
* @param bundleName the name of the {@link Bundle} we want to find {@link Bitstream}s for
|
||||
* @param bundleName the name of the {@link Bundle} we want to find
|
||||
* {@link Bitstream}s for
|
||||
* @param options the {@link FindListOptions} for the request
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bitstream>[]): Observable<RemoteData<PaginatedList<Bitstream>>> {
|
||||
public findAllByItemAndBundleName(item: Item, bundleName: string, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bitstream>[]): Observable<RemoteData<PaginatedList<Bitstream>>> {
|
||||
return this.bundleService.findByItemAndName(item, bundleName).pipe(
|
||||
switchMap((bundleRD: RemoteData<Bundle>) => {
|
||||
if (bundleRD.hasSucceeded && hasValue(bundleRD.payload)) {
|
||||
return this.findAllByBundle(bundleRD.payload, options, reRequestOnStale, ...linksToFollow);
|
||||
return this.findAllByBundle(bundleRD.payload, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
} else if (!bundleRD.hasSucceeded && bundleRD.statusCode === 404) {
|
||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), []), new Date().getTime());
|
||||
} else {
|
||||
@@ -209,7 +214,7 @@ export class BitstreamDataService extends DataService<Bitstream> {
|
||||
options.headers = headers;
|
||||
return new PutRequest(requestId, bitstreamHref, formatHref, options);
|
||||
}),
|
||||
configureRequest(this.requestService),
|
||||
sendRequest(this.requestService),
|
||||
take(1)
|
||||
).subscribe(() => {
|
||||
this.requestService.removeByHrefSubstring(bitstream.self + '/format');
|
||||
|
@@ -73,7 +73,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: cold('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
@@ -93,7 +93,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: cold('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
@@ -115,7 +115,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: cold('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
@@ -136,7 +136,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: cold('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
@@ -160,7 +160,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: cold('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
@@ -183,7 +183,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: cold('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
@@ -206,7 +206,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: cold('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
@@ -228,7 +228,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: cold('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
@@ -250,7 +250,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: cold('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
@@ -270,7 +270,7 @@ describe('BitstreamFormatDataService', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
scheduler = getTestScheduler();
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
configure: {},
|
||||
send: {},
|
||||
getByHref: observableOf(responseCacheEntry),
|
||||
getByUUID: hot('a', { a: responseCacheEntry }),
|
||||
generateRequestId: 'request-id',
|
||||
|
@@ -19,7 +19,7 @@ import { BitstreamFormat } from '../shared/bitstream-format.model';
|
||||
import { BITSTREAM_FORMAT } from '../shared/bitstream-format.resource-type';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { configureRequest } from '../shared/operators';
|
||||
import { sendRequest } from '../shared/operators';
|
||||
import { DataService } from './data.service';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { RemoteData } from './remote-data';
|
||||
@@ -82,7 +82,7 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
|
||||
distinctUntilChanged(),
|
||||
map((endpointURL: string) =>
|
||||
new PutRequest(requestId, endpointURL, bitstreamFormat)),
|
||||
configureRequest(this.requestService)).subscribe();
|
||||
sendRequest(this.requestService)).subscribe();
|
||||
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
|
||||
@@ -99,7 +99,7 @@ export class BitstreamFormatDataService extends DataService<BitstreamFormat> {
|
||||
map((endpointURL: string) => {
|
||||
return new PostRequest(requestId, endpointURL, bitstreamFormat);
|
||||
}),
|
||||
configureRequest(this.requestService)
|
||||
sendRequest(this.requestService)
|
||||
).subscribe();
|
||||
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
|
@@ -81,7 +81,7 @@ describe('BundleDataService', () => {
|
||||
});
|
||||
|
||||
it('should call findAllByHref with the item\'s bundles link', () => {
|
||||
expect(service.findAllByHref).toHaveBeenCalledWith(bundleLink, undefined, true);
|
||||
expect(service.findAllByHref).toHaveBeenCalledWith(bundleLink, undefined, true, true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -52,12 +52,15 @@ export class BundleDataService extends DataService<Bundle> {
|
||||
*
|
||||
* @param item the {@link Item} the {@link Bundle}s are a part of
|
||||
* @param options the {@link FindListOptions} for the request
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow the {@link FollowLinkConfig}s for the request
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findAllByItem(item: Item, options?: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<PaginatedList<Bundle>>> {
|
||||
return this.findAllByHref(item._links.bundles.href, options, reRequestOnStale, ...linksToFollow);
|
||||
findAllByItem(item: Item, options?: FindListOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<PaginatedList<Bundle>>> {
|
||||
return this.findAllByHref(item._links.bundles.href, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,13 +68,16 @@ export class BundleDataService extends DataService<Bundle> {
|
||||
*
|
||||
* @param item the {@link Item} the {@link Bundle}s are a part of
|
||||
* @param bundleName the name of the {@link Bundle} to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow the {@link FollowLinkConfig}s for the request
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
// TODO should be implemented rest side
|
||||
findByItemAndName(item: Item, bundleName: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<Bundle>> {
|
||||
return this.findAllByItem(item, { elementsPerPage: Number.MAX_SAFE_INTEGER }, reRequestOnStale, ...linksToFollow).pipe(
|
||||
findByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<Bundle>> {
|
||||
return this.findAllByItem(item, { elementsPerPage: 9999 }, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
|
||||
map((rd: RemoteData<PaginatedList<Bundle>>) => {
|
||||
if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
|
||||
const matchingBundle = rd.payload.page.find((bundle: Bundle) =>
|
||||
@@ -129,7 +135,7 @@ export class BundleDataService extends DataService<Bundle> {
|
||||
take(1)
|
||||
).subscribe((href) => {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), href);
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request, true);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList<Bitstream>(hrefObs, ...linksToFollow);
|
||||
|
@@ -6,7 +6,7 @@ import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-servic
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
import { getMockTranslateService } from '../../shared/mocks/translate.service.mock';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { ContentSourceRequest, GetRequest, UpdateContentSourceRequest } from './request.models';
|
||||
import { ContentSourceRequest, UpdateContentSourceRequest } from './request.models';
|
||||
import { ContentSource } from '../shared/content-source.model';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
@@ -87,10 +87,10 @@ describe('CollectionDataService', () => {
|
||||
contentSource$ = service.getContentSource(collectionId);
|
||||
});
|
||||
|
||||
it('should configure a new ContentSourceRequest', fakeAsync(() => {
|
||||
it('should send a new ContentSourceRequest', fakeAsync(() => {
|
||||
contentSource$.subscribe();
|
||||
tick();
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(ContentSourceRequest));
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(ContentSourceRequest), true);
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -103,25 +103,13 @@ describe('CollectionDataService', () => {
|
||||
returnedContentSource$ = service.updateContentSource(collectionId, contentSource);
|
||||
});
|
||||
|
||||
it('should configure a new UpdateContentSourceRequest', fakeAsync(() => {
|
||||
it('should send a new UpdateContentSourceRequest', fakeAsync(() => {
|
||||
returnedContentSource$.subscribe();
|
||||
tick();
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(UpdateContentSourceRequest));
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(UpdateContentSourceRequest));
|
||||
}));
|
||||
});
|
||||
|
||||
describe('getMappedItems', () => {
|
||||
let result;
|
||||
|
||||
beforeEach(() => {
|
||||
result = service.getMappedItems('collection-id');
|
||||
});
|
||||
|
||||
it('should configure a GET request', () => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest));
|
||||
});
|
||||
});
|
||||
|
||||
describe('when calling getAuthorizedCollection', () => {
|
||||
beforeEach(() => {
|
||||
scheduler = getTestScheduler();
|
||||
@@ -175,10 +163,10 @@ describe('CollectionDataService', () => {
|
||||
returnedContentSource$ = service.updateContentSource(collectionId, contentSource);
|
||||
});
|
||||
|
||||
it('should configure a new UpdateContentSourceRequest', fakeAsync(() => {
|
||||
it('should send a new UpdateContentSourceRequest', fakeAsync(() => {
|
||||
returnedContentSource$.subscribe();
|
||||
tick();
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(UpdateContentSourceRequest));
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(UpdateContentSourceRequest));
|
||||
}));
|
||||
|
||||
it('should display an error notification', fakeAsync(() => {
|
||||
|
@@ -3,12 +3,11 @@ import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators';
|
||||
import { filter, map, switchMap, take } from 'rxjs/operators';
|
||||
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
||||
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
||||
import { INotification } from '../../shared/notifications/models/notification.model';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { dataService } from '../cache/builders/build-decorators';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
@@ -20,26 +19,19 @@ import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { COLLECTION } from '../shared/collection.resource-type';
|
||||
import { ContentSource } from '../shared/content-source.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { GenericConstructor } from '../shared/generic-constructor';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import {
|
||||
configureRequest,
|
||||
getFirstCompletedRemoteData
|
||||
} from '../shared/operators';
|
||||
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||
import { ComColDataService } from './comcol-data.service';
|
||||
import { CommunityDataService } from './community-data.service';
|
||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||
import { DSOResponseParsingService } from './dso-response-parsing.service';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { ResponseParsingService } from './parsing.service';
|
||||
import { RemoteData } from './remote-data';
|
||||
import {
|
||||
ContentSourceRequest,
|
||||
FindListOptions,
|
||||
GetRequest,
|
||||
UpdateContentSourceRequest
|
||||
UpdateContentSourceRequest,
|
||||
RestRequest
|
||||
} from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { BitstreamDataService } from './bitstream-data.service';
|
||||
@@ -70,20 +62,25 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
/**
|
||||
* Get all collections the user is authorized to submit to
|
||||
*
|
||||
* @param query limit the returned collection to those with metadata values matching the query terms.
|
||||
* @param query limit the returned collection to those with metadata values
|
||||
* matching the query terms.
|
||||
* @param options The [[FindListOptions]] object
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return Observable<RemoteData<PaginatedList<Collection>>>
|
||||
* collection list
|
||||
*/
|
||||
getAuthorizedCollection(query: string, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Collection>[]): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||
getAuthorizedCollection(query: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Collection>[]): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||
const searchHref = 'findSubmitAuthorized';
|
||||
options = Object.assign({}, options, {
|
||||
searchParams: [new RequestParam('query', query)]
|
||||
});
|
||||
|
||||
return this.searchBy(searchHref, options, reRequestOnStale, ...linksToFollow).pipe(
|
||||
return this.searchBy(searchHref, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
|
||||
filter((collections: RemoteData<PaginatedList<Collection>>) => !collections.isResponsePending));
|
||||
}
|
||||
|
||||
@@ -149,7 +146,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
|
||||
href$.subscribe((href: string) => {
|
||||
const request = new ContentSourceRequest(this.requestService.generateRequestId(), href);
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request, true);
|
||||
});
|
||||
|
||||
return this.rdbService.buildSingle<ContentSource>(href$);
|
||||
@@ -175,9 +172,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
);
|
||||
|
||||
// Execute the post/put request
|
||||
request$.pipe(
|
||||
configureRequest(this.requestService)
|
||||
).subscribe();
|
||||
request$.subscribe((request: RestRequest) => this.requestService.send(request));
|
||||
|
||||
// Return updated ContentSource
|
||||
return this.rdbService.buildFromRequestUUID<ContentSource>(requestId).pipe(
|
||||
@@ -205,48 +200,6 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the endpoint used for mapping items to a collection
|
||||
* @param collectionId The id of the collection to map items to
|
||||
*/
|
||||
getMappedItemsEndpoint(collectionId): Observable<string> {
|
||||
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||
map((endpoint: string) => this.getIDHref(endpoint, collectionId)),
|
||||
map((endpoint: string) => `${endpoint}/mappedItems`)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a list of items that are mapped to a collection
|
||||
* @param collectionId The id of the collection
|
||||
* @param searchOptions Search options to sort or filter out items
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
getMappedItems(collectionId: string, searchOptions?: PaginatedSearchOptions, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
|
||||
const requestUuid = this.requestService.generateRequestId();
|
||||
|
||||
const href$ = this.getMappedItemsEndpoint(collectionId).pipe(
|
||||
isNotEmptyOperator(),
|
||||
distinctUntilChanged(),
|
||||
map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint)
|
||||
);
|
||||
|
||||
href$.pipe(
|
||||
map((endpoint: string) => {
|
||||
const request = new GetRequest(requestUuid, endpoint);
|
||||
return Object.assign(request, {
|
||||
responseMsToLive: 0,
|
||||
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||
return DSOResponseParsingService;
|
||||
}
|
||||
});
|
||||
}),
|
||||
configureRequest(this.requestService)
|
||||
).subscribe();
|
||||
|
||||
return this.rdbService.buildList(href$, ...linksToFollow);
|
||||
}
|
||||
|
||||
protected getFindByParentHref(parentUUID: string): Observable<string> {
|
||||
return this.halService.getEndpoint('communities').pipe(
|
||||
switchMap((communityEndpointHref: string) =>
|
||||
@@ -261,5 +214,4 @@ export class CollectionDataService extends ComColDataService<Collection> {
|
||||
findOwningCollectionFor(item: Item): Observable<RemoteData<Collection>> {
|
||||
return this.findByHref(item._links.owningCollection.href);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -13,14 +13,16 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { ComColDataService } from './comcol-data.service';
|
||||
import { CommunityDataService } from './community-data.service';
|
||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||
import { FindByIDRequest, FindListOptions } from './request.models';
|
||||
import { FindListOptions, GetRequest } from './request.models';
|
||||
import { RequestEntry } from './request.reducer';
|
||||
import { RequestService } from './request.service';
|
||||
import {
|
||||
createFailedRemoteDataObject$, createNoContentRemoteDataObject$,
|
||||
createFailedRemoteDataObject$,
|
||||
createNoContentRemoteDataObject$,
|
||||
createSuccessfulRemoteDataObject$
|
||||
} from '../../shared/remote-data.utils';
|
||||
import { BitstreamDataService } from './bitstream-data.service';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
const LINK_NAME = 'test';
|
||||
|
||||
@@ -147,23 +149,23 @@ describe('ComColDataService', () => {
|
||||
scheduler = getTestScheduler();
|
||||
});
|
||||
|
||||
it('should configure a new FindByIDRequest for the scope Community', () => {
|
||||
it('should send a new FindByIDRequest for the scope Community', () => {
|
||||
cds = initMockCommunityDataService();
|
||||
requestService = getMockRequestService(getRequestEntry$(true));
|
||||
objectCache = initMockObjectCacheService();
|
||||
service = initTestService();
|
||||
|
||||
const expected = new FindByIDRequest(requestService.generateRequestId(), communityEndpoint, scopeID);
|
||||
const expected = new GetRequest(requestService.generateRequestId(), communityEndpoint);
|
||||
|
||||
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(expected);
|
||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
||||
});
|
||||
|
||||
describe('if the scope Community can\'t be found', () => {
|
||||
it('should throw an error', () => {
|
||||
const result = service.getBrowseEndpoint(options);
|
||||
const result = service.getBrowseEndpoint(options).pipe(take(1));
|
||||
const expected = cold('--#-', undefined, new Error(`The Community with scope ${scopeID} couldn't be retrieved`));
|
||||
|
||||
expect(result).toBeObservable(expected);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { distinctUntilChanged, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators';
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
@@ -7,7 +7,7 @@ import { HALLink } from '../shared/hal-link.model';
|
||||
import { CommunityDataService } from './community-data.service';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
import { FindByIDRequest, FindListOptions } from './request.models';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
@@ -42,11 +42,10 @@ export abstract class ComColDataService<T extends Community | Collection> extend
|
||||
const scopeCommunityHrefObs = this.cds.getEndpoint().pipe(
|
||||
map((endpoint: string) => this.cds.getIDHref(endpoint, options.scopeID)),
|
||||
filter((href: string) => isNotEmpty(href)),
|
||||
take(1),
|
||||
tap((href: string) => {
|
||||
const request = new FindByIDRequest(this.requestService.generateRequestId(), href, options.scopeID);
|
||||
this.requestService.configure(request);
|
||||
}));
|
||||
take(1)
|
||||
);
|
||||
|
||||
this.createAndSendGetRequest(scopeCommunityHrefObs, true);
|
||||
|
||||
return scopeCommunityHrefObs.pipe(
|
||||
switchMap((href: string) => this.rdbService.buildSingle<Community>(href)),
|
||||
@@ -71,7 +70,7 @@ export abstract class ComColDataService<T extends Community | Collection> extend
|
||||
const href$ = this.getFindByParentHref(parentUUID).pipe(
|
||||
map((href: string) => this.buildHrefFromFindOptions(href, options))
|
||||
);
|
||||
return this.findList(href$, options);
|
||||
return this.findAllByHref(href$);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,8 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { filter, switchMap, take } from 'rxjs/operators';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { dataService } from '../cache/builders/build-decorators';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
@@ -17,7 +16,7 @@ import { ComColDataService } from './comcol-data.service';
|
||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindListOptions, FindListRequest } from './request.models';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { BitstreamDataService } from './bitstream-data.service';
|
||||
|
||||
@@ -48,15 +47,7 @@ export class CommunityDataService extends ComColDataService<Community> {
|
||||
|
||||
findTop(options: FindListOptions = {}): Observable<RemoteData<PaginatedList<Community>>> {
|
||||
const hrefObs = this.getFindAllHref(options, this.topLinkPath);
|
||||
hrefObs.pipe(
|
||||
filter((href: string) => hasValue(href)),
|
||||
take(1))
|
||||
.subscribe((href: string) => {
|
||||
const request = new FindListRequest(this.requestService.generateRequestId(), href, options);
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList<Community>(hrefObs) as Observable<RemoteData<PaginatedList<Community>>>;
|
||||
return this.findAllByHref(hrefObs, undefined);
|
||||
}
|
||||
|
||||
protected getFindByParentHref(parentUUID: string): Observable<string> {
|
||||
|
@@ -2,7 +2,7 @@ import { cold, getTestScheduler } from 'jasmine-marbles';
|
||||
import { TestScheduler } from 'rxjs/testing';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { FindByIDRequest } from './request.models';
|
||||
import { GetRequest } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
@@ -34,7 +34,7 @@ describe('ConfigurationDataService', () => {
|
||||
});
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: requestUUID,
|
||||
configure: true
|
||||
send: true
|
||||
});
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: cold('a', {
|
||||
@@ -67,11 +67,11 @@ describe('ConfigurationDataService', () => {
|
||||
expect(halService.getEndpoint).toHaveBeenCalledWith('properties');
|
||||
});
|
||||
|
||||
it('should configure the proper FindByIDRequest', () => {
|
||||
it('should send the proper FindByIDRequest', () => {
|
||||
scheduler.schedule(() => service.findByPropertyName(testObject.name));
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestURL, testObject.name));
|
||||
expect(requestService.send).toHaveBeenCalledWith(new GetRequest(requestUUID, requestURL), true);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<ConfigurationProperty> for the object with the given name', () => {
|
||||
|
@@ -212,7 +212,7 @@ describe('DataService', () => {
|
||||
it('should include nested linksToFollow 3lvl', () => {
|
||||
const expected = `${endpoint}?embed=owningCollection/itemtemplate/relationships`;
|
||||
|
||||
(service as any).getFindAllHref({}, null, followLink('owningCollection', undefined, true, followLink('itemtemplate', undefined, true, followLink('relationships')))).subscribe((value) => {
|
||||
(service as any).getFindAllHref({}, null, followLink('owningCollection', undefined, true, true, true, followLink('itemtemplate', undefined, true, true, true, followLink('relationships')))).subscribe((value) => {
|
||||
expect(value).toBe(expected);
|
||||
});
|
||||
});
|
||||
@@ -247,7 +247,7 @@ describe('DataService', () => {
|
||||
|
||||
it('should include nested linksToFollow 3lvl', () => {
|
||||
const expected = `${endpointMock}/${resourceIdMock}?embed=owningCollection/itemtemplate/relationships`;
|
||||
const result = (service as any).getIDHref(endpointMock, resourceIdMock, followLink('owningCollection', undefined, true, followLink('itemtemplate', undefined, true, followLink('relationships'))));
|
||||
const result = (service as any).getIDHref(endpointMock, resourceIdMock, followLink('owningCollection', undefined, true, true, true,followLink('itemtemplate', undefined, true, true, true, followLink('relationships'))));
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -268,8 +268,8 @@ describe('DataService', () => {
|
||||
service.patch(dso, operations);
|
||||
});
|
||||
|
||||
it('should configure a PatchRequest', () => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PatchRequest));
|
||||
it('should send a PatchRequest', () => {
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PatchRequest));
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -1,16 +1,17 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
find,
|
||||
first,
|
||||
map,
|
||||
mergeMap,
|
||||
take,
|
||||
takeWhile, switchMap, tap,
|
||||
takeWhile,
|
||||
switchMap,
|
||||
tap,
|
||||
} from 'rxjs/operators';
|
||||
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
||||
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
||||
@@ -25,22 +26,18 @@ import { CoreState } from '../core.reducers';
|
||||
import { DSpaceSerializer } from '../dspace-rest/dspace.serializer';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import {
|
||||
getRemoteDataPayload,
|
||||
getFirstSucceededRemoteData,
|
||||
} from '../shared/operators';
|
||||
import { getRemoteDataPayload, getFirstSucceededRemoteData, } from '../shared/operators';
|
||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||
import { ChangeAnalyzer } from './change-analyzer';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import {
|
||||
CreateRequest,
|
||||
FindByIDRequest,
|
||||
FindListOptions,
|
||||
FindListRequest,
|
||||
GetRequest,
|
||||
FindListOptions,
|
||||
PatchRequest,
|
||||
PutRequest, DeleteRequest
|
||||
PutRequest,
|
||||
DeleteRequest
|
||||
} from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { RestRequestMethod } from './rest-request-method';
|
||||
@@ -159,23 +156,23 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
|
||||
if (hasValue(options.currentPage) && typeof options.currentPage === 'number') {
|
||||
/* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */
|
||||
args = [...args, `page=${options.currentPage - 1}`];
|
||||
args = this.addHrefArg(href, args, `page=${options.currentPage - 1}`);
|
||||
}
|
||||
if (hasValue(options.elementsPerPage)) {
|
||||
args = [...args, `size=${options.elementsPerPage}`];
|
||||
args = this.addHrefArg(href, args, `size=${options.elementsPerPage}`);
|
||||
}
|
||||
if (hasValue(options.sort)) {
|
||||
args = [...args, `sort=${options.sort.field},${options.sort.direction}`];
|
||||
args = this.addHrefArg(href, args, `sort=${options.sort.field},${options.sort.direction}`);
|
||||
}
|
||||
if (hasValue(options.startsWith)) {
|
||||
args = [...args, `startsWith=${options.startsWith}`];
|
||||
args = this.addHrefArg(href, args, `startsWith=${options.startsWith}`);
|
||||
}
|
||||
if (hasValue(options.searchParams)) {
|
||||
options.searchParams.forEach((param: RequestParam) => {
|
||||
args = [...args, `${param.fieldName}=${param.fieldValue}`];
|
||||
args = this.addHrefArg(href, args, `${param.fieldName}=${param.fieldValue}`);
|
||||
});
|
||||
}
|
||||
args = this.addEmbedParams(args, ...linksToFollow);
|
||||
args = this.addEmbedParams(href, args, ...linksToFollow);
|
||||
if (isNotEmpty(args)) {
|
||||
return new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||
} else {
|
||||
@@ -198,11 +195,11 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
let args = [];
|
||||
if (hasValue(params)) {
|
||||
params.forEach((param: RequestParam) => {
|
||||
args.push(`${param.fieldName}=${param.fieldValue}`);
|
||||
args = this.addHrefArg(href, args, `${param.fieldName}=${param.fieldValue}`);
|
||||
});
|
||||
}
|
||||
|
||||
args = this.addEmbedParams(args, ...linksToFollow);
|
||||
args = this.addEmbedParams(href, args, ...linksToFollow);
|
||||
|
||||
if (isNotEmpty(args)) {
|
||||
return new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||
@@ -212,20 +209,39 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
}
|
||||
/**
|
||||
* Adds the embed options to the link for the request
|
||||
* @param href The href the params are to be added to
|
||||
* @param args params for the query string
|
||||
* @param linksToFollow links we want to embed in query string if shouldEmbed is true
|
||||
*/
|
||||
protected addEmbedParams(args: string[], ...linksToFollow: FollowLinkConfig<T>[]) {
|
||||
protected addEmbedParams(href: string, args: string[], ...linksToFollow: FollowLinkConfig<T>[]) {
|
||||
linksToFollow.forEach((linkToFollow: FollowLinkConfig<T>) => {
|
||||
if (linkToFollow !== undefined && linkToFollow.shouldEmbed) {
|
||||
const embedString = 'embed=' + String(linkToFollow.name);
|
||||
const embedWithNestedString = this.addNestedEmbeds(embedString, ...linkToFollow.linksToFollow);
|
||||
args = [...args, embedWithNestedString];
|
||||
args = this.addHrefArg(href, args, embedWithNestedString);
|
||||
}
|
||||
});
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new argument to the list of arguments, only if it doesn't already exist in the given href,
|
||||
* or the current list of arguments
|
||||
*
|
||||
* @param href The href the arguments are to be added to
|
||||
* @param currentArgs The current list of arguments
|
||||
* @param newArg The new argument to add
|
||||
* @return The next list of arguments, with newArg included if it wasn't already.
|
||||
* Note this function will not modify any of the input params.
|
||||
*/
|
||||
protected addHrefArg(href: string, currentArgs: string[], newArg: string): string[] {
|
||||
if (href.includes(newArg) || currentArgs.includes(newArg)) {
|
||||
return [...currentArgs];
|
||||
} else {
|
||||
return [...currentArgs, newArg];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the nested followLinks to the embed param, recursively, separated by a /
|
||||
* @param embedString embedString so far (recursive)
|
||||
@@ -249,44 +265,17 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
* info should be added to the objects
|
||||
*
|
||||
* @param options Find list options object
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<T>>>}
|
||||
* Return an observable that emits object list
|
||||
*/
|
||||
findAll(options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
return this.findList(this.getFindAllHref(options), options, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of an object, based on href observable,
|
||||
* with a list of {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
||||
* @param href$ Observable of href of object we want to retrieve
|
||||
* @param options Find list options object
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
*/
|
||||
protected findList(href$, options: FindListOptions, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]) {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
|
||||
href$.pipe(
|
||||
first((href: string) => hasValue(href)))
|
||||
.subscribe((href: string) => {
|
||||
const request = new FindListRequest(requestId, href, options);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList<T>(href$, ...linksToFollow).pipe(
|
||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||
this.findList(href$, options, reRequestOnStale, ...linksToFollow))
|
||||
) as Observable<RemoteData<PaginatedList<T>>>;
|
||||
findAll(options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
return this.findAllByHref(this.getFindAllHref(options), options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,78 +302,107 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
* Returns an observable of {@link RemoteData} of an object, based on its ID, with a list of
|
||||
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
||||
* @param id ID of object we want to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findById(id: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
|
||||
const href$ = this.getIDHrefObs(encodeURIComponent(id), ...linksToFollow).pipe(
|
||||
isNotEmptyOperator(),
|
||||
take(1)
|
||||
);
|
||||
|
||||
href$.subscribe((href: string) => {
|
||||
const request = new FindByIDRequest(requestId, href, id);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildSingle<T>(href$, ...linksToFollow).pipe(
|
||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||
this.findById(id, reRequestOnStale, ...linksToFollow))
|
||||
);
|
||||
findById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
|
||||
const href$ = this.getIDHrefObs(encodeURIComponent(id), ...linksToFollow);
|
||||
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of
|
||||
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
||||
* @param href The url of object we want to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @param href$ The url of object we want to retrieve. Can be a string or
|
||||
* an Observable<string>
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
|
||||
const requestHref = this.buildHrefFromFindOptions(href, {}, [], ...linksToFollow);
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
const request = new GetRequest(requestId, requestHref);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
findByHref(href$: string | Observable<string>, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
|
||||
if (typeof href$ === 'string') {
|
||||
href$ = observableOf(href$);
|
||||
}
|
||||
this.requestService.configure(request);
|
||||
return this.rdbService.buildSingle<T>(href, ...linksToFollow).pipe(
|
||||
|
||||
const requestHref$ = href$.pipe(
|
||||
isNotEmptyOperator(),
|
||||
take(1),
|
||||
map((href: string) => this.buildHrefFromFindOptions(href, {}, [], ...linksToFollow))
|
||||
);
|
||||
|
||||
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
||||
|
||||
return this.rdbService.buildSingle<T>(requestHref$, ...linksToFollow).pipe(
|
||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||
this.findByHref(href, reRequestOnStale, ...linksToFollow))
|
||||
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of observables of {@link RemoteData} of objects, based on an href, with a list
|
||||
* of {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
||||
* @param href The url of object we want to retrieve
|
||||
* @param href$ The url of object we want to retrieve. Can be a string or
|
||||
* an Observable<string>
|
||||
* @param findListOptions Find list options object
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
|
||||
* should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
const requestHref = this.buildHrefFromFindOptions(href, findListOptions, [], ...linksToFollow);
|
||||
findAllByHref(href$: string | Observable<string>, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
if (typeof href$ === 'string') {
|
||||
href$ = observableOf(href$);
|
||||
}
|
||||
|
||||
const requestHref$ = href$.pipe(
|
||||
isNotEmptyOperator(),
|
||||
take(1),
|
||||
map((href: string) => this.buildHrefFromFindOptions(href, findListOptions, [], ...linksToFollow))
|
||||
);
|
||||
|
||||
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
||||
|
||||
return this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
|
||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||
this.findAllByHref(href$, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GET request for the given href, and send it.
|
||||
*
|
||||
* @param href$ The url of object we want to retrieve. Can be a string or
|
||||
* an Observable<string>
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
*/
|
||||
protected createAndSendGetRequest(href$: string | Observable<string>, useCachedVersionIfAvailable = true): void {
|
||||
if (isNotEmpty(href$)) {
|
||||
if (typeof href$ === 'string') {
|
||||
href$ = observableOf(href$);
|
||||
}
|
||||
|
||||
href$.pipe(
|
||||
isNotEmptyOperator(),
|
||||
take(1)
|
||||
).subscribe((href: string) => {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
const request = new GetRequest(requestId, requestHref);
|
||||
const request = new GetRequest(requestId, href);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.configure(request);
|
||||
return this.rdbService.buildList<T>(requestHref, ...linksToFollow).pipe(
|
||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||
this.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow))
|
||||
);
|
||||
this.requestService.send(request, useCachedVersionIfAvailable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -403,30 +421,19 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
*
|
||||
* @param searchMethod The search method for the object
|
||||
* @param options The [[FindListOptions]] object
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow The array of [[FollowLinkConfig]]
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
* @return {Observable<RemoteData<PaginatedList<T>>}
|
||||
* Return an observable that emits response from the server
|
||||
*/
|
||||
searchBy(searchMethod: string, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
const requestId = this.requestService.generateRequestId();
|
||||
searchBy(searchMethod: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
const hrefObs = this.getSearchByHref(searchMethod, options, ...linksToFollow);
|
||||
|
||||
hrefObs.pipe(
|
||||
find((href: string) => hasValue(href))
|
||||
).subscribe((href: string) => {
|
||||
const request = new FindListRequest(requestId, href, options);
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList(hrefObs, ...linksToFollow).pipe(
|
||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||
this.searchBy(searchMethod, options, reRequestOnStale, ...linksToFollow))
|
||||
);
|
||||
return this.findAllByHref(hrefObs, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -447,14 +454,14 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
}
|
||||
|
||||
createPatchFromCache(object: T): Observable<Operation[]> {
|
||||
const oldVersion$ = this.findByHref(object._links.self.href, false);
|
||||
const oldVersion$ = this.findByHref(object._links.self.href, true, false);
|
||||
return oldVersion$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
@@ -475,7 +482,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request);
|
||||
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
}
|
||||
@@ -492,7 +499,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
if (isNotEmpty(operations)) {
|
||||
this.objectCache.addPatch(object._links.self.href, operations);
|
||||
}
|
||||
return this.findByHref(object._links.self.href, true);
|
||||
return this.findByHref(object._links.self.href, true, true);
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -524,7 +531,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request);
|
||||
});
|
||||
|
||||
const result$ = this.rdbService.buildFromRequestUUID<T>(requestId);
|
||||
@@ -579,7 +586,7 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
||||
if (hasValue(this.responseMsToLive)) {
|
||||
request.responseMsToLive = this.responseMsToLive;
|
||||
}
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request);
|
||||
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { DsoRedirectDataService } from './dso-redirect-data.service';
|
||||
import { FindByIDRequest, IdentifierType } from './request.models';
|
||||
import { GetRequest, IdentifierType } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('DsoRedirectDataService', () => {
|
||||
});
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: requestUUID,
|
||||
configure: true
|
||||
send: true
|
||||
});
|
||||
router = {
|
||||
navigate: jasmine.createSpy('navigate')
|
||||
@@ -93,18 +93,18 @@ describe('DsoRedirectDataService', () => {
|
||||
expect(halService.getEndpoint).toHaveBeenCalledWith('dso');
|
||||
});
|
||||
|
||||
it('should configure the proper FindByIDRequest for uuid', () => {
|
||||
it('should send the proper FindByIDRequest for uuid', () => {
|
||||
scheduler.schedule(() => service.findByIdAndIDType(dsoUUID, IdentifierType.UUID));
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestUUIDURL, dsoUUID));
|
||||
expect(requestService.send).toHaveBeenCalledWith(new GetRequest(requestUUID, requestUUIDURL), true);
|
||||
});
|
||||
|
||||
it('should configure the proper FindByIDRequest for handle', () => {
|
||||
it('should send the proper FindByIDRequest for handle', () => {
|
||||
scheduler.schedule(() => service.findByIdAndIDType(dsoHandle, IdentifierType.HANDLE));
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestHandleURL, dsoHandle));
|
||||
expect(requestService.send).toHaveBeenCalledWith(new GetRequest(requestUUID, requestHandleURL), true);
|
||||
});
|
||||
|
||||
it('should navigate to item route', () => {
|
||||
@@ -162,7 +162,7 @@ describe('DsoRedirectDataService', () => {
|
||||
|
||||
it('should include nested linksToFollow 3lvl', () => {
|
||||
const expected = `${requestUUIDURL}&embed=owningCollection/itemtemplate/relationships`;
|
||||
const result = (service as any).getIDHref(pidLink, dsoUUID, followLink('owningCollection', undefined, true, followLink('itemtemplate', undefined, true, followLink('relationships'))));
|
||||
const result = (service as any).getIDHref(pidLink, dsoUUID, followLink('owningCollection', undefined, true, true, true, followLink('itemtemplate', undefined, true, true, true, followLink('relationships'))));
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
@@ -14,9 +14,10 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { DataService } from './data.service';
|
||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindByIDRequest, IdentifierType } from './request.models';
|
||||
import { IdentifierType } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
|
||||
@Injectable()
|
||||
export class DsoRedirectDataService extends DataService<any> {
|
||||
@@ -53,7 +54,7 @@ export class DsoRedirectDataService extends DataService<any> {
|
||||
{}, [], ...linksToFollow);
|
||||
}
|
||||
|
||||
findByIdAndIDType(id: string, identifierType = IdentifierType.UUID): Observable<RemoteData<FindByIDRequest>> {
|
||||
findByIdAndIDType(id: string, identifierType = IdentifierType.UUID): Observable<RemoteData<DSpaceObject>> {
|
||||
this.setLinkPath(identifierType);
|
||||
return this.findById(id).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
|
@@ -3,7 +3,7 @@ import { TestScheduler } from 'rxjs/testing';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { FindByIDRequest } from './request.models';
|
||||
import { GetRequest } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
import { DSpaceObjectDataService } from './dspace-object-data.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
@@ -32,7 +32,7 @@ describe('DSpaceObjectDataService', () => {
|
||||
});
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: requestUUID,
|
||||
configure: true
|
||||
send: true
|
||||
});
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildSingle: cold('a', {
|
||||
@@ -65,11 +65,11 @@ describe('DSpaceObjectDataService', () => {
|
||||
expect(halService.getEndpoint).toHaveBeenCalledWith('dso');
|
||||
});
|
||||
|
||||
it('should configure the proper FindByIDRequest', () => {
|
||||
it('should send the proper FindByIDRequest', () => {
|
||||
scheduler.schedule(() => service.findById(testObject.uuid));
|
||||
scheduler.flush();
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(new FindByIDRequest(requestUUID, requestURL, testObject.uuid));
|
||||
expect(requestService.send).toHaveBeenCalledWith(new GetRequest(requestUUID, requestURL), true);
|
||||
});
|
||||
|
||||
it('should return a RemoteData<DSpaceObject> for the object with the given ID', () => {
|
||||
|
@@ -61,24 +61,30 @@ export class DSpaceObjectDataService {
|
||||
* Returns an observable of {@link RemoteData} of an object, based on its ID, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the object
|
||||
* @param id ID of object we want to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findById(id: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<DSpaceObject>> {
|
||||
return this.dataService.findById(id, reRequestOnStale, ...linksToFollow);
|
||||
findById(id: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<DSpaceObject>> {
|
||||
return this.dataService.findById(id, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
|
||||
}
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the object
|
||||
* @param href The url of object we want to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<DSpaceObject>> {
|
||||
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow);
|
||||
findByHref(href: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<DSpaceObject>> {
|
||||
return this.dataService.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,12 +92,15 @@ export class DSpaceObjectDataService {
|
||||
* to automatically resolve {@link HALLink}s of the object
|
||||
* @param href The url of object we want to retrieve
|
||||
* @param findListOptions Find list options object
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
|
||||
return this.dataService.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow);
|
||||
findAllByHref(href: string, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
|
||||
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,89 +0,0 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { dataService } from '../cache/builders/build-decorators';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { ItemType } from '../shared/item-relationships/item-type.model';
|
||||
import { ITEM_TYPE } from '../shared/item-relationships/item-type.resource-type';
|
||||
import { DataService } from './data.service';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
*/
|
||||
class DataServiceImpl extends DataService<ItemType> {
|
||||
protected linkPath = 'entitytypes';
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<ItemType>) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A service to retrieve {@link ItemType}s from the REST API.
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(ITEM_TYPE)
|
||||
export class ItemTypeDataService {
|
||||
/**
|
||||
* A private DataService instance to delegate specific methods to.
|
||||
*/
|
||||
private dataService: DataServiceImpl;
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<ItemType>) {
|
||||
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of an {@link ItemType}, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the {@link ItemType}
|
||||
* @param href The url of {@link ItemType} we want to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<ItemType>[]): Observable<RemoteData<ItemType>> {
|
||||
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of observables of {@link RemoteData} of {@link ItemType}s, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the {@link ItemType}
|
||||
* @param href The url of the {@link ItemType} we want to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByAllHref(href: string, reRequestOnStale = true, findListOptions: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<ItemType>[]): Observable<RemoteData<PaginatedList<ItemType>>> {
|
||||
return this.dataService.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
@@ -10,7 +10,6 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GetRequest } from './request.models';
|
||||
import { Observable } from 'rxjs';
|
||||
import { switchMap, take, map } from 'rxjs/operators';
|
||||
import { RemoteData } from './remote-data';
|
||||
@@ -18,6 +17,7 @@ import { RelationshipType } from '../shared/item-relationships/relationship-type
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { ItemType } from '../shared/item-relationships/item-type.model';
|
||||
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../shared/operators';
|
||||
import { RelationshipTypeService } from './relationship-type.service';
|
||||
|
||||
/**
|
||||
* Service handling all ItemType requests
|
||||
@@ -33,6 +33,7 @@ export class EntityTypeService extends DataService<ItemType> {
|
||||
protected halService: HALEndpointService,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected relationshipTypeService: RelationshipTypeService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<ItemType>) {
|
||||
super();
|
||||
@@ -69,18 +70,16 @@ export class EntityTypeService extends DataService<ItemType> {
|
||||
/**
|
||||
* Get the allowed relationship types for an entity type
|
||||
* @param entityTypeId
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
getEntityTypeRelationships(entityTypeId: string, ...linksToFollow: FollowLinkConfig<RelationshipType>[]): Observable<RemoteData<PaginatedList<RelationshipType>>> {
|
||||
|
||||
getEntityTypeRelationships(entityTypeId: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<RelationshipType>[]): Observable<RemoteData<PaginatedList<RelationshipType>>> {
|
||||
const href$ = this.getRelationshipTypesEndpoint(entityTypeId);
|
||||
|
||||
href$.pipe(take(1)).subscribe((href) => {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), href);
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList(href$, ...linksToFollow);
|
||||
return this.relationshipTypeService.findAllByHref(href$, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -31,7 +31,7 @@ describe('EpersonRegistrationService', () => {
|
||||
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: 'request-id',
|
||||
configure: {},
|
||||
send: {},
|
||||
getByUUID: cold('a',
|
||||
{ a: Object.assign(new RequestEntry(), { response: new RestResponse(true, 200, 'Success') }) })
|
||||
});
|
||||
@@ -70,7 +70,7 @@ describe('EpersonRegistrationService', () => {
|
||||
|
||||
const expected = service.registerEmail('test@mail.org');
|
||||
|
||||
expect(requestService.configure).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration));
|
||||
expect(requestService.send).toHaveBeenCalledWith(new PostRequest('request-id', 'rest-url/registrations', registration));
|
||||
expect(expected).toBeObservable(cold('(a|)', { a: rd }));
|
||||
});
|
||||
});
|
||||
|
@@ -66,7 +66,7 @@ export class EpersonRegistrationService {
|
||||
find((href: string) => hasValue(href)),
|
||||
map((href: string) => {
|
||||
const request = new PostRequest(requestId, href, registration);
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request);
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
@@ -93,7 +93,7 @@ export class EpersonRegistrationService {
|
||||
return RegistrationResponseParsingService;
|
||||
}
|
||||
});
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request, true);
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
|
@@ -42,7 +42,7 @@ describe('ExternalSourceService', () => {
|
||||
function init() {
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: 'request-uuid',
|
||||
configure: {}
|
||||
send: {}
|
||||
});
|
||||
rdbService = jasmine.createSpyObj('rdbService', {
|
||||
buildList: createSuccessfulRemoteDataObject$(createPaginatedList(entries))
|
||||
@@ -64,8 +64,8 @@ describe('ExternalSourceService', () => {
|
||||
result = service.getExternalSourceEntries('test');
|
||||
});
|
||||
|
||||
it('should configure a GetRequest', () => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest));
|
||||
it('should send a GetRequest', () => {
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(GetRequest), true);
|
||||
});
|
||||
|
||||
it('should return the entries', () => {
|
||||
|
@@ -9,16 +9,16 @@ import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { FindListOptions, GetRequest } from './request.models';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { Observable } from 'rxjs';
|
||||
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
|
||||
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
|
||||
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||
import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
|
||||
import { configureRequest } from '../shared/operators';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
|
||||
/**
|
||||
* A service handling all external source requests
|
||||
@@ -63,21 +63,22 @@ export class ExternalSourceService extends DataService<ExternalSource> {
|
||||
* Get the entries for an external source
|
||||
* @param externalSourceId The id of the external source to fetch entries for
|
||||
* @param searchOptions The search options to limit results to
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
getExternalSourceEntries(externalSourceId: string, searchOptions?: PaginatedSearchOptions): Observable<RemoteData<PaginatedList<ExternalSourceEntry>>> {
|
||||
const requestUuid = this.requestService.generateRequestId();
|
||||
|
||||
getExternalSourceEntries(externalSourceId: string, searchOptions?: PaginatedSearchOptions, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<ExternalSourceEntry>[]): Observable<RemoteData<PaginatedList<ExternalSourceEntry>>> {
|
||||
const href$ = this.getEntriesEndpoint(externalSourceId).pipe(
|
||||
isNotEmptyOperator(),
|
||||
distinctUntilChanged(),
|
||||
map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint)
|
||||
map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint),
|
||||
take(1)
|
||||
);
|
||||
|
||||
href$.pipe(
|
||||
map((endpoint: string) => new GetRequest(requestUuid, endpoint)),
|
||||
configureRequest(this.requestService)
|
||||
).subscribe();
|
||||
|
||||
return this.rdbService.buildList(href$);
|
||||
// TODO create a dedicated ExternalSourceEntryDataService and move this entire method to it. Then the "as any"s won't be necessary
|
||||
return this.findAllByHref(href$, undefined, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow as any) as any;
|
||||
}
|
||||
}
|
||||
|
@@ -68,7 +68,7 @@ describe('AuthorizationDataService', () => {
|
||||
});
|
||||
|
||||
it('should call searchBy with the site\'s url', () => {
|
||||
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self), true);
|
||||
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self), true, true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('AuthorizationDataService', () => {
|
||||
});
|
||||
|
||||
it('should call searchBy with the site\'s url and the feature', () => {
|
||||
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self, null, FeatureID.LoginOnBehalfOf), true);
|
||||
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self, null, FeatureID.LoginOnBehalfOf), true, true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('AuthorizationDataService', () => {
|
||||
});
|
||||
|
||||
it('should call searchBy with the object\'s url and the feature', () => {
|
||||
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, null, FeatureID.LoginOnBehalfOf), true);
|
||||
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, null, FeatureID.LoginOnBehalfOf), true, true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,7 +98,7 @@ describe('AuthorizationDataService', () => {
|
||||
});
|
||||
|
||||
it('should call searchBy with the object\'s url, user\'s uuid and the feature', () => {
|
||||
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, ePersonUuid, FeatureID.LoginOnBehalfOf), true);
|
||||
expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, ePersonUuid, FeatureID.LoginOnBehalfOf), true, true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -60,7 +60,7 @@ export class AuthorizationDataService extends DataService<Authorization> {
|
||||
* @param featureId ID of the {@link Feature} to check {@link Authorization} for
|
||||
*/
|
||||
isAuthorized(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string): Observable<boolean> {
|
||||
return this.searchByObject(featureId, objectUrl, ePersonUuid, {}, followLink('feature')).pipe(
|
||||
return this.searchByObject(featureId, objectUrl, ePersonUuid, {}, true, true, followLink('feature')).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((authorizationRD) => {
|
||||
if (authorizationRD.statusCode !== 401 && hasValue(authorizationRD.payload) && isNotEmpty(authorizationRD.payload.page)) {
|
||||
@@ -83,13 +83,18 @@ export class AuthorizationDataService extends DataService<Authorization> {
|
||||
* If not provided, the UUID of the currently authenticated {@link EPerson} will be used.
|
||||
* @param featureId ID of the {@link Feature} to search {@link Authorization}s for
|
||||
* @param options {@link FindListOptions} to provide pagination and/or additional arguments
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
searchByObject(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<Authorization>[]): Observable<RemoteData<PaginatedList<Authorization>>> {
|
||||
searchByObject(featureId?: FeatureID, objectUrl?: string, ePersonUuid?: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Authorization>[]): Observable<RemoteData<PaginatedList<Authorization>>> {
|
||||
return observableOf(new AuthorizationSearchParams(objectUrl, ePersonUuid, featureId)).pipe(
|
||||
addSiteObjectUrlIfEmpty(this.siteService),
|
||||
switchMap((params: AuthorizationSearchParams) => {
|
||||
return this.searchBy(this.searchByObjectPath, this.createSearchOptions(params.objectUrl, options, params.ePersonUuid, params.featureId), true, ...linksToFollow);
|
||||
return this.searchBy(this.searchByObjectPath, this.createSearchOptions(params.objectUrl, options, params.ePersonUuid, params.featureId), useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
82
src/app/core/data/href-only-data.service.spec.ts
Normal file
82
src/app/core/data/href-only-data.service.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { HrefOnlyDataService } from './href-only-data.service';
|
||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { DataService } from './data.service';
|
||||
|
||||
describe(`HrefOnlyDataService`, () => {
|
||||
let service: HrefOnlyDataService;
|
||||
let href: string;
|
||||
let spy: jasmine.Spy;
|
||||
let followLinks: FollowLinkConfig<any>[];
|
||||
let findListOptions: FindListOptions;
|
||||
|
||||
beforeEach(() => {
|
||||
href = 'https://rest.api/server/api/core/items/de7fa215-4a25-43a7-a4d7-17534a09fdfc';
|
||||
followLinks = [ followLink('link1'), followLink('link2') ];
|
||||
findListOptions = new FindListOptions();
|
||||
service = new HrefOnlyDataService(null, null, null, null, null, null, null, null);
|
||||
});
|
||||
|
||||
it(`should instantiate a private DataService`, () => {
|
||||
expect((service as any).dataService).toBeDefined();
|
||||
expect((service as any).dataService).toBeInstanceOf(DataService);
|
||||
});
|
||||
|
||||
describe(`findByHref`, () => {
|
||||
beforeEach(() => {
|
||||
spy = spyOn((service as any).dataService, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(null));
|
||||
});
|
||||
|
||||
it(`should delegate to findByHref on the internal DataService`, () => {
|
||||
service.findByHref(href, false, false, ...followLinks);
|
||||
expect(spy).toHaveBeenCalledWith(href, false, false, ...followLinks);
|
||||
});
|
||||
|
||||
describe(`when useCachedVersionIfAvailable is omitted`, () => {
|
||||
it(`should call findByHref on the internal DataService with useCachedVersionIfAvailable = true`, () => {
|
||||
service.findByHref(href);
|
||||
expect(spy).toHaveBeenCalledWith(jasmine.anything(), true, jasmine.anything());
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when reRequestOnStale is omitted`, () => {
|
||||
it(`should call findByHref on the internal DataService with reRequestOnStale = true`, () => {
|
||||
service.findByHref(href);
|
||||
expect(spy).toHaveBeenCalledWith(jasmine.anything(), jasmine.anything(), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`findAllByHref`, () => {
|
||||
beforeEach(() => {
|
||||
spy = spyOn((service as any).dataService, 'findAllByHref').and.returnValue(createSuccessfulRemoteDataObject$(null));
|
||||
});
|
||||
|
||||
it(`should delegate to findAllByHref on the internal DataService`, () => {
|
||||
service.findAllByHref(href, findListOptions, false, false, ...followLinks);
|
||||
expect(spy).toHaveBeenCalledWith(href, findListOptions, false, false, ...followLinks);
|
||||
});
|
||||
|
||||
describe(`when findListOptions is omitted`, () => {
|
||||
it(`should call findAllByHref on the internal DataService with findListOptions = {}`, () => {
|
||||
service.findAllByHref(href);
|
||||
expect(spy).toHaveBeenCalledWith(jasmine.anything(), {}, jasmine.anything(), jasmine.anything());
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when useCachedVersionIfAvailable is omitted`, () => {
|
||||
it(`should call findAllByHref on the internal DataService with useCachedVersionIfAvailable = true`, () => {
|
||||
service.findAllByHref(href);
|
||||
expect(spy).toHaveBeenCalledWith(jasmine.anything(), jasmine.anything(), true, jasmine.anything());
|
||||
});
|
||||
});
|
||||
|
||||
describe(`when reRequestOnStale is omitted`, () => {
|
||||
it(`should call findAllByHref on the internal DataService with reRequestOnStale = true`, () => {
|
||||
service.findAllByHref(href);
|
||||
expect(spy).toHaveBeenCalledWith(jasmine.anything(), jasmine.anything(), jasmine.anything(), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
99
src/app/core/data/href-only-data.service.ts
Normal file
99
src/app/core/data/href-only-data.service.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { DataService } from './data.service';
|
||||
import { RequestService } from './request.service';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { VOCABULARY_ENTRY } from '../submission/vocabularies/models/vocabularies.resource-type';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { dataService } from '../cache/builders/build-decorators';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { ITEM_TYPE } from '../shared/item-relationships/item-type.resource-type';
|
||||
import { LICENSE } from '../shared/license.resource-type';
|
||||
import { CacheableObject } from '../cache/object-cache.reducer';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
class DataServiceImpl extends DataService<any> {
|
||||
// linkPath isn't used if we're only searching by href.
|
||||
protected linkPath = undefined;
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<any>) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A DataService with only findByHref methods. Its purpose is to be used for resources that don't
|
||||
* need to be retrieved by ID, or have any way to update them, but require a DataService in order
|
||||
* for their links to be resolved by the LinkService.
|
||||
*
|
||||
* an @dataService annotation can be added for any number of these resource types
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
@dataService(VOCABULARY_ENTRY)
|
||||
@dataService(ITEM_TYPE)
|
||||
@dataService(LICENSE)
|
||||
export class HrefOnlyDataService {
|
||||
private dataService: DataServiceImpl;
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<any>) {
|
||||
this.dataService = new DataServiceImpl(requestService, rdbService, store, objectCache, halService, notificationsService, http, comparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the object
|
||||
* @param href The url of object we want to retrieve
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByHref<T extends CacheableObject>(href: string | Observable<string>, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<T>> {
|
||||
return this.dataService.findByHref(href, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of observables of {@link RemoteData} of objects, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the object
|
||||
* @param href The url of object we want to retrieve
|
||||
* @param findListOptions Find list options object
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findAllByHref<T extends CacheableObject>(href: string | Observable<string>, findListOptions: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<T>[]): Observable<RemoteData<PaginatedList<T>>> {
|
||||
return this.dataService.findAllByHref(href, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
}
|
@@ -126,8 +126,8 @@ describe('ItemDataService', () => {
|
||||
result = service.removeMappingFromCollection('item-id', 'collection-id');
|
||||
});
|
||||
|
||||
it('should configure a DELETE request', () => {
|
||||
result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(DeleteRequest)));
|
||||
it('should send a DELETE request', () => {
|
||||
result.subscribe(() => expect(requestService.send).toHaveBeenCalledWith(jasmine.any(DeleteRequest)));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -139,8 +139,8 @@ describe('ItemDataService', () => {
|
||||
result = service.mapToCollection('item-id', 'collection-href');
|
||||
});
|
||||
|
||||
it('should configure a POST request', () => {
|
||||
result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest)));
|
||||
it('should send a POST request', () => {
|
||||
result.subscribe(() => expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PostRequest)));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -158,9 +158,9 @@ describe('ItemDataService', () => {
|
||||
result = service.importExternalSourceEntry(externalSourceEntry, 'collection-id');
|
||||
});
|
||||
|
||||
it('should configure a POST request', (done) => {
|
||||
it('should send a POST request', (done) => {
|
||||
result.subscribe(() => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest));
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PostRequest));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -176,9 +176,9 @@ describe('ItemDataService', () => {
|
||||
result = service.createBundle(itemId, bundleName);
|
||||
});
|
||||
|
||||
it('should configure a POST request', (done) => {
|
||||
it('should send a POST request', (done) => {
|
||||
result.subscribe(() => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest));
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PostRequest));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@@ -16,7 +16,7 @@ import { ExternalSourceEntry } from '../shared/external-source-entry.model';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { ITEM } from '../shared/item.resource-type';
|
||||
import { configureRequest } from '../shared/operators';
|
||||
import { sendRequest } from '../shared/operators';
|
||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||
|
||||
import { DataService } from './data.service';
|
||||
@@ -99,7 +99,7 @@ export class ItemDataService extends DataService<Item> {
|
||||
isNotEmptyOperator(),
|
||||
distinctUntilChanged(),
|
||||
map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)),
|
||||
configureRequest(this.requestService),
|
||||
sendRequest(this.requestService),
|
||||
switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid)),
|
||||
);
|
||||
}
|
||||
@@ -120,29 +120,11 @@ export class ItemDataService extends DataService<Item> {
|
||||
options.headers = headers;
|
||||
return new PostRequest(this.requestService.generateRequestId(), endpointURL, collectionHref, options);
|
||||
}),
|
||||
configureRequest(this.requestService),
|
||||
sendRequest(this.requestService),
|
||||
switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all collections the item is mapped to
|
||||
* @param itemId The item's id
|
||||
*/
|
||||
public getMappedCollections(itemId: string): Observable<RemoteData<PaginatedList<Collection>>> {
|
||||
const href$ = this.getMappedCollectionsEndpoint(itemId).pipe(
|
||||
isNotEmptyOperator(),
|
||||
take(1)
|
||||
);
|
||||
|
||||
href$.subscribe((href: string) => {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), href);
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList(href$);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the isWithdrawn state of an item to a specified state
|
||||
* @param item
|
||||
@@ -196,7 +178,7 @@ export class ItemDataService extends DataService<Item> {
|
||||
take(1)
|
||||
).subscribe((href) => {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), href);
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList<Bundle>(hrefObs);
|
||||
@@ -225,7 +207,7 @@ export class ItemDataService extends DataService<Item> {
|
||||
headers = headers.append('Content-Type', 'application/json');
|
||||
options.headers = headers;
|
||||
const request = new PostRequest(requestId, href, JSON.stringify(bundleJson), options);
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildFromRequestUUID(requestId);
|
||||
@@ -260,7 +242,7 @@ export class ItemDataService extends DataService<Item> {
|
||||
find((href: string) => hasValue(href)),
|
||||
map((href: string) => {
|
||||
const request = new PutRequest(requestId, href, collection._links.self.href, options);
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request);
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
@@ -285,7 +267,7 @@ export class ItemDataService extends DataService<Item> {
|
||||
find((href: string) => hasValue(href)),
|
||||
map((href: string) => {
|
||||
const request = new PostRequest(requestId, href, externalSourceEntry._links.self.href, options);
|
||||
this.requestService.configure(request);
|
||||
this.requestService.send(request);
|
||||
})
|
||||
).subscribe();
|
||||
|
||||
|
@@ -28,7 +28,7 @@ describe('ItemTemplateDataService', () => {
|
||||
generateRequestId(): string {
|
||||
return scopeID;
|
||||
},
|
||||
configure(request: RestRequest) {
|
||||
send(request: RestRequest) {
|
||||
// Do nothing
|
||||
},
|
||||
getByHref(requestHref: string) {
|
||||
|
@@ -100,13 +100,16 @@ class DataServiceImpl extends ItemDataService {
|
||||
/**
|
||||
* Set the collection ID and send a find by ID request
|
||||
* @param collectionID
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByCollectionID(collectionID: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<Item>> {
|
||||
findByCollectionID(collectionID: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<Item>> {
|
||||
this.setCollectionEndpoint(collectionID);
|
||||
return super.findById(collectionID, reRequestOnStale, ...linksToFollow);
|
||||
return super.findById(collectionID, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,12 +181,15 @@ export class ItemTemplateDataService implements UpdateDataService<Item> {
|
||||
/**
|
||||
* Find an item template by collection ID
|
||||
* @param collectionID
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByCollectionID(collectionID: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<Item>> {
|
||||
return this.dataService.findByCollectionID(collectionID, reRequestOnStale, ...linksToFollow);
|
||||
findByCollectionID(collectionID: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Item>[]): Observable<RemoteData<Item>> {
|
||||
return this.dataService.findByCollectionID(collectionID, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,89 +0,0 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { dataService } from '../cache/builders/build-decorators';
|
||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||
import { License } from '../shared/license.model';
|
||||
import { LICENSE } from '../shared/license.resource-type';
|
||||
import { DataService } from './data.service';
|
||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||
import { PaginatedList } from './paginated-list.model';
|
||||
import { RemoteData } from './remote-data';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { RequestService } from './request.service';
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
|
||||
/**
|
||||
* A private DataService implementation to delegate specific methods to.
|
||||
*/
|
||||
class DataServiceImpl extends DataService<License> {
|
||||
protected linkPath = '';
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<License>) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A service to retrieve {@link License}s from the REST API.
|
||||
*/
|
||||
@Injectable()
|
||||
@dataService(LICENSE)
|
||||
export class LicenseDataService {
|
||||
/**
|
||||
* A private DataService instance to delegate specific methods to.
|
||||
*/
|
||||
private dataService: DataServiceImpl;
|
||||
|
||||
constructor(
|
||||
protected requestService: RequestService,
|
||||
protected rdbService: RemoteDataBuildService,
|
||||
protected store: Store<CoreState>,
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<License>) {
|
||||
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable of {@link RemoteData} of a {@link License}, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the {@link License}
|
||||
* @param href The URL of object we want to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<License>[]): Observable<RemoteData<License>> {
|
||||
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of observables of {@link RemoteData} of {@link License}s, based on an href, with a list of {@link FollowLinkConfig},
|
||||
* to automatically resolve {@link HALLink}s of the {@link License}
|
||||
* @param href The URL of object we want to retrieve
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findByAllHref(href: string, reRequestOnStale = true, findListOptions: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<License>[]): Observable<RemoteData<PaginatedList<License>>> {
|
||||
return this.dataService.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
@@ -31,9 +31,9 @@ describe('MetadataFieldDataService', () => {
|
||||
});
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: '34cfed7c-f597-49ef-9cbe-ea351f0023c2',
|
||||
configure: {},
|
||||
send: {},
|
||||
getByUUID: observableOf({ response: new RestResponse(true, 200, 'OK') }),
|
||||
removeByHrefSubstring: {}
|
||||
setStaleByHrefSubstring: {}
|
||||
});
|
||||
halService = Object.assign(new HALEndpointServiceStub(endpoint));
|
||||
notificationsService = jasmine.createSpyObj('notificationsService', {
|
||||
@@ -59,16 +59,15 @@ describe('MetadataFieldDataService', () => {
|
||||
const expectedOptions = Object.assign(new FindListOptions(), {
|
||||
searchParams: [new RequestParam('schema', schema.prefix)]
|
||||
});
|
||||
expect(metadataFieldService.searchBy).toHaveBeenCalledWith('bySchema', expectedOptions, true);
|
||||
expect(metadataFieldService.searchBy).toHaveBeenCalledWith('bySchema', expectedOptions, true, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearRequests', () => {
|
||||
it('should remove requests on the data service\'s endpoint', (done) => {
|
||||
metadataFieldService.clearRequests().subscribe(() => {
|
||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(`${endpoint}/${(metadataFieldService as any).linkPath}`);
|
||||
done();
|
||||
});
|
||||
it('should remove requests on the data service\'s endpoint', () => {
|
||||
spyOn(metadataFieldService, 'getBrowseEndpoint').and.returnValue(observableOf(endpoint));
|
||||
metadataFieldService.clearRequests();
|
||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(endpoint);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -19,7 +19,7 @@ import { MetadataSchema } from '../metadata/metadata-schema.model';
|
||||
import { FindListOptions } from './request.models';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { RequestParam } from '../cache/models/request-param.model';
|
||||
|
||||
/**
|
||||
@@ -48,15 +48,18 @@ export class MetadataFieldDataService extends DataService<MetadataField> {
|
||||
* Find metadata fields belonging to a metadata schema
|
||||
* @param schema The metadata schema to list fields for
|
||||
* @param options The options info used to retrieve the fields
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
|
||||
* the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
|
||||
* no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||
* requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||
* {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
findBySchema(schema: MetadataSchema, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<MetadataField>[]) {
|
||||
findBySchema(schema: MetadataSchema, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<MetadataField>[]) {
|
||||
const optionsWithSchema = Object.assign(new FindListOptions(), options, {
|
||||
searchParams: [new RequestParam('schema', schema.prefix)]
|
||||
});
|
||||
return this.searchBy(this.searchBySchemaLinkPath, optionsWithSchema, reRequestOnStale, ...linksToFollow);
|
||||
return this.searchBy(this.searchBySchemaLinkPath, optionsWithSchema, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,10 +74,11 @@ export class MetadataFieldDataService extends DataService<MetadataField> {
|
||||
* schema.element if no qualifier exists (e.g. "dc.title", "dc.contributor.author"). It will only return one value
|
||||
* if there's an exact match
|
||||
* @param options The options info used to retrieve the fields
|
||||
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's no valid cached version. Defaults to true
|
||||
* @param reRequestOnStale Whether or not the request should automatically be re-requested after the response becomes stale
|
||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
|
||||
*/
|
||||
searchByFieldNameParams(schema: string, element: string, qualifier: string, query: string, exactName: string, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<MetadataField>[]): Observable<RemoteData<PaginatedList<MetadataField>>> {
|
||||
searchByFieldNameParams(schema: string, element: string, qualifier: string, query: string, exactName: string, options: FindListOptions = {}, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<MetadataField>[]): Observable<RemoteData<PaginatedList<MetadataField>>> {
|
||||
const optionParams = Object.assign(new FindListOptions(), options, {
|
||||
searchParams: [
|
||||
new RequestParam('schema', hasValue(schema) ? schema : ''),
|
||||
@@ -84,7 +88,7 @@ export class MetadataFieldDataService extends DataService<MetadataField> {
|
||||
new RequestParam('exactName', hasValue(exactName) ? exactName : '')
|
||||
]
|
||||
});
|
||||
return this.searchBy(this.searchByFieldNameLinkPath, optionParams, reRequestOnStale, ...linksToFollow);
|
||||
return this.searchBy(this.searchByFieldNameLinkPath, optionParams, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,12 +105,11 @@ export class MetadataFieldDataService extends DataService<MetadataField> {
|
||||
* Clear all metadata field requests
|
||||
* Used for refreshing lists after adding/updating/removing a metadata field from a metadata schema
|
||||
*/
|
||||
clearRequests(): Observable<string> {
|
||||
return this.getBrowseEndpoint().pipe(
|
||||
tap((href: string) => {
|
||||
this.requestService.removeByHrefSubstring(href);
|
||||
})
|
||||
);
|
||||
clearRequests(): void {
|
||||
this.getBrowseEndpoint().pipe(take(1)).subscribe((href: string) => {
|
||||
this.requestService.setStaleByHrefSubstring(href);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -22,7 +22,7 @@ describe('MetadataSchemaDataService', () => {
|
||||
function init() {
|
||||
requestService = jasmine.createSpyObj('requestService', {
|
||||
generateRequestId: '34cfed7c-f597-49ef-9cbe-ea351f0023c2',
|
||||
configure: {},
|
||||
send: {},
|
||||
getByUUID: observableOf({ response: new RestResponse(true, 200, 'OK') }),
|
||||
removeByHrefSubstring: {}
|
||||
});
|
||||
@@ -54,7 +54,7 @@ describe('MetadataSchemaDataService', () => {
|
||||
describe('called with a new metadata schema', () => {
|
||||
it('should send a CreateRequest', (done) => {
|
||||
metadataSchemaService.createOrUpdateMetadataSchema(schema).subscribe(() => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(CreateRequest));
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(CreateRequest));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -69,7 +69,7 @@ describe('MetadataSchemaDataService', () => {
|
||||
|
||||
it('should send a PutRequest', (done) => {
|
||||
metadataSchemaService.createOrUpdateMetadataSchema(schema).subscribe(() => {
|
||||
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PutRequest));
|
||||
expect(requestService.send).toHaveBeenCalledWith(jasmine.any(PutRequest));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@@ -13,12 +13,11 @@ import { Process } from '../../../process-page/processes/process.model';
|
||||
import { dataService } from '../../cache/builders/build-decorators';
|
||||
import { PROCESS } from '../../../process-page/processes/process.resource-type';
|
||||
import { Observable } from 'rxjs';
|
||||
import { switchMap, take } from 'rxjs/operators';
|
||||
import { GetRequest } from '../request.models';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { PaginatedList } from '../paginated-list.model';
|
||||
import { Bitstream } from '../../shared/bitstream.model';
|
||||
import { RemoteData } from '../remote-data';
|
||||
import { isNotEmptyOperator } from '../../../shared/empty.util';
|
||||
import { BitstreamDataService } from '../bitstream-data.service';
|
||||
|
||||
@Injectable()
|
||||
@dataService(PROCESS)
|
||||
@@ -32,6 +31,7 @@ export class ProcessDataService extends DataService<Process> {
|
||||
protected objectCache: ObjectCacheService,
|
||||
protected halService: HALEndpointService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected bitstreamDataService: BitstreamDataService,
|
||||
protected http: HttpClient,
|
||||
protected comparator: DefaultChangeAnalyzer<Process>) {
|
||||
super();
|
||||
@@ -48,20 +48,11 @@ export class ProcessDataService extends DataService<Process> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a process his output files
|
||||
* Get a process' output files
|
||||
* @param processId The ID of the process
|
||||
*/
|
||||
getFiles(processId: string): Observable<RemoteData<PaginatedList<Bitstream>>> {
|
||||
const href$ = this.getFilesEndpoint(processId).pipe(
|
||||
isNotEmptyOperator(),
|
||||
take(1)
|
||||
);
|
||||
|
||||
href$.subscribe((href: string) => {
|
||||
const request = new GetRequest(this.requestService.generateRequestId(), href);
|
||||
this.requestService.configure(request);
|
||||
});
|
||||
|
||||
return this.rdbService.buildList(href$);
|
||||
const href$ = this.getFilesEndpoint(processId);
|
||||
return this.bitstreamDataService.findAllByHref(href$);
|
||||
}
|
||||
}
|
||||
|
@@ -49,7 +49,7 @@ export class ScriptDataService extends DataService<Script> {
|
||||
const body = this.getInvocationFormData(parameters, files);
|
||||
return new MultipartPostRequest(requestId, endpoint, body);
|
||||
})
|
||||
).subscribe((request: RestRequest) => this.requestService.configure(request));
|
||||
).subscribe((request: RestRequest) => this.requestService.send(request));
|
||||
|
||||
return this.rdbService.buildFromRequestUUID<Process>(requestId);
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user