mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Merge branch 'DSpace:main' into main
This commit is contained in:
@@ -88,7 +88,7 @@ describe('CollectionSourceControlsComponent', () => {
|
|||||||
invoke: createSuccessfulRemoteDataObject$(process),
|
invoke: createSuccessfulRemoteDataObject$(process),
|
||||||
});
|
});
|
||||||
processDataService = jasmine.createSpyObj('processDataService', {
|
processDataService = jasmine.createSpyObj('processDataService', {
|
||||||
findById: createSuccessfulRemoteDataObject$(process),
|
autoRefreshUntilCompletion: createSuccessfulRemoteDataObject$(process),
|
||||||
});
|
});
|
||||||
bitstreamService = jasmine.createSpyObj('bitstreamService', {
|
bitstreamService = jasmine.createSpyObj('bitstreamService', {
|
||||||
findByHref: createSuccessfulRemoteDataObject$(bitstream),
|
findByHref: createSuccessfulRemoteDataObject$(bitstream),
|
||||||
@@ -137,7 +137,7 @@ describe('CollectionSourceControlsComponent', () => {
|
|||||||
{name: '-i', value: new ContentSourceSetSerializer().Serialize(contentSource.oaiSetId)},
|
{name: '-i', value: new ContentSourceSetSerializer().Serialize(contentSource.oaiSetId)},
|
||||||
], []);
|
], []);
|
||||||
|
|
||||||
expect(processDataService.findById).toHaveBeenCalledWith(process.processId, false);
|
expect(processDataService.autoRefreshUntilCompletion).toHaveBeenCalledWith(process.processId);
|
||||||
expect(bitstreamService.findByHref).toHaveBeenCalledWith(process._links.output.href);
|
expect(bitstreamService.findByHref).toHaveBeenCalledWith(process._links.output.href);
|
||||||
expect(notificationsService.info).toHaveBeenCalledWith(jasmine.anything() as any, 'Script text');
|
expect(notificationsService.info).toHaveBeenCalledWith(jasmine.anything() as any, 'Script text');
|
||||||
});
|
});
|
||||||
@@ -151,7 +151,7 @@ describe('CollectionSourceControlsComponent', () => {
|
|||||||
{name: '-r', value: null},
|
{name: '-r', value: null},
|
||||||
{name: '-c', value: collection.uuid},
|
{name: '-c', value: collection.uuid},
|
||||||
], []);
|
], []);
|
||||||
expect(processDataService.findById).toHaveBeenCalledWith(process.processId, false);
|
expect(processDataService.autoRefreshUntilCompletion).toHaveBeenCalledWith(process.processId);
|
||||||
expect(notificationsService.success).toHaveBeenCalled();
|
expect(notificationsService.success).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -164,7 +164,7 @@ describe('CollectionSourceControlsComponent', () => {
|
|||||||
{name: '-o', value: null},
|
{name: '-o', value: null},
|
||||||
{name: '-c', value: collection.uuid},
|
{name: '-c', value: collection.uuid},
|
||||||
], []);
|
], []);
|
||||||
expect(processDataService.findById).toHaveBeenCalledWith(process.processId, false);
|
expect(processDataService.autoRefreshUntilCompletion).toHaveBeenCalledWith(process.processId);
|
||||||
expect(notificationsService.success).toHaveBeenCalled();
|
expect(notificationsService.success).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -3,13 +3,12 @@ import { ScriptDataService } from '../../../../core/data/processes/script-data.s
|
|||||||
import { ContentSource } from '../../../../core/shared/content-source.model';
|
import { ContentSource } from '../../../../core/shared/content-source.model';
|
||||||
import { ProcessDataService } from '../../../../core/data/processes/process-data.service';
|
import { ProcessDataService } from '../../../../core/data/processes/process-data.service';
|
||||||
import {
|
import {
|
||||||
getAllCompletedRemoteData,
|
|
||||||
getAllSucceededRemoteDataPayload,
|
getAllSucceededRemoteDataPayload,
|
||||||
getFirstCompletedRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload
|
getFirstSucceededRemoteDataPayload
|
||||||
} from '../../../../core/shared/operators';
|
} from '../../../../core/shared/operators';
|
||||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
import { ProcessStatus } from '../../../../process-page/processes/process-status.model';
|
import { ProcessStatus } from '../../../../process-page/processes/process-status.model';
|
||||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { RequestService } from '../../../../core/data/request.service';
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
@@ -95,19 +94,9 @@ export class CollectionSourceControlsComponent implements OnDestroy {
|
|||||||
}),
|
}),
|
||||||
// filter out responses that aren't successful since the pinging of the process only needs to happen when the invocation was successful.
|
// filter out responses that aren't successful since the pinging of the process only needs to happen when the invocation was successful.
|
||||||
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
|
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
|
||||||
switchMap((rd) => this.processDataService.findById(rd.payload.processId, false)),
|
switchMap((rd) => this.processDataService.autoRefreshUntilCompletion(rd.payload.processId)),
|
||||||
getAllCompletedRemoteData(),
|
map((rd) => rd.payload)
|
||||||
filter((rd) => !rd.isStale && (rd.hasSucceeded || rd.hasFailed)),
|
|
||||||
map((rd) => rd.payload),
|
|
||||||
hasValueOperator(),
|
|
||||||
).subscribe((process: Process) => {
|
).subscribe((process: Process) => {
|
||||||
if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() &&
|
|
||||||
process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) {
|
|
||||||
// Ping the current process state every 5s
|
|
||||||
setTimeout(() => {
|
|
||||||
this.requestService.setStaleByHrefSubstring(process._links.self.href);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
|
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
|
||||||
this.notificationsService.error(this.translateService.get('collection.source.controls.test.failed'));
|
this.notificationsService.error(this.translateService.get('collection.source.controls.test.failed'));
|
||||||
this.testConfigRunning$.next(false);
|
this.testConfigRunning$.next(false);
|
||||||
@@ -123,8 +112,7 @@ export class CollectionSourceControlsComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
this.testConfigRunning$.next(false);
|
this.testConfigRunning$.next(false);
|
||||||
}
|
}
|
||||||
}
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,20 +135,9 @@ export class CollectionSourceControlsComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
|
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
|
||||||
switchMap((rd) => this.processDataService.findById(rd.payload.processId, false)),
|
switchMap((rd) => this.processDataService.autoRefreshUntilCompletion(rd.payload.processId)),
|
||||||
getAllCompletedRemoteData(),
|
map((rd) => rd.payload)
|
||||||
filter((rd) => !rd.isStale && (rd.hasSucceeded || rd.hasFailed)),
|
|
||||||
map((rd) => rd.payload),
|
|
||||||
hasValueOperator(),
|
|
||||||
).subscribe((process) => {
|
).subscribe((process) => {
|
||||||
if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() &&
|
|
||||||
process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) {
|
|
||||||
// Ping the current process state every 5s
|
|
||||||
setTimeout(() => {
|
|
||||||
this.requestService.setStaleByHrefSubstring(process._links.self.href);
|
|
||||||
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
|
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
|
||||||
this.notificationsService.error(this.translateService.get('collection.source.controls.import.failed'));
|
this.notificationsService.error(this.translateService.get('collection.source.controls.import.failed'));
|
||||||
this.importRunning$.next(false);
|
this.importRunning$.next(false);
|
||||||
@@ -170,8 +147,7 @@ export class CollectionSourceControlsComponent implements OnDestroy {
|
|||||||
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
||||||
this.importRunning$.next(false);
|
this.importRunning$.next(false);
|
||||||
}
|
}
|
||||||
}
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,20 +170,9 @@ export class CollectionSourceControlsComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
|
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
|
||||||
switchMap((rd) => this.processDataService.findById(rd.payload.processId, false)),
|
switchMap((rd) => this.processDataService.autoRefreshUntilCompletion(rd.payload.processId)),
|
||||||
getAllCompletedRemoteData(),
|
map((rd) => rd.payload)
|
||||||
filter((rd) => !rd.isStale && (rd.hasSucceeded || rd.hasFailed)),
|
|
||||||
map((rd) => rd.payload),
|
|
||||||
hasValueOperator(),
|
|
||||||
).subscribe((process) => {
|
).subscribe((process) => {
|
||||||
if (process.processStatus.toString() !== ProcessStatus[ProcessStatus.COMPLETED].toString() &&
|
|
||||||
process.processStatus.toString() !== ProcessStatus[ProcessStatus.FAILED].toString()) {
|
|
||||||
// Ping the current process state every 5s
|
|
||||||
setTimeout(() => {
|
|
||||||
this.requestService.setStaleByHrefSubstring(process._links.self.href);
|
|
||||||
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
|
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
|
||||||
this.notificationsService.error(this.translateService.get('collection.source.controls.reset.failed'));
|
this.notificationsService.error(this.translateService.get('collection.source.controls.reset.failed'));
|
||||||
this.reImportRunning$.next(false);
|
this.reImportRunning$.next(false);
|
||||||
@@ -217,8 +182,7 @@ export class CollectionSourceControlsComponent implements OnDestroy {
|
|||||||
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
this.requestService.setStaleByHrefSubstring(this.collection._links.self.href);
|
||||||
this.reImportRunning$.next(false);
|
this.reImportRunning$.next(false);
|
||||||
}
|
}
|
||||||
}
|
}));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
@@ -273,12 +273,13 @@ export class RemoteDataBuildService {
|
|||||||
return isStale(r2.state) ? r1 : r2;
|
return isStale(r2.state) ? r1 : r2;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
distinctUntilKeyChanged('lastUpdated')
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const payload$ = this.buildPayload<T>(requestEntry$, href$, ...linksToFollow);
|
const payload$ = this.buildPayload<T>(requestEntry$, href$, ...linksToFollow);
|
||||||
|
|
||||||
return this.toRemoteDataObservable<T>(requestEntry$, payload$);
|
return this.toRemoteDataObservable<T>(requestEntry$, payload$).pipe(
|
||||||
|
distinctUntilKeyChanged('lastUpdated'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -21,6 +21,10 @@ import { RequestEntryState } from '../request-entry-state.model';
|
|||||||
import { fakeAsync, tick } from '@angular/core/testing';
|
import { fakeAsync, tick } from '@angular/core/testing';
|
||||||
import { BaseDataService } from './base-data.service';
|
import { BaseDataService } from './base-data.service';
|
||||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
|
import { ObjectCacheServiceStub } from '../../../shared/testing/object-cache-service.stub';
|
||||||
|
import { ObjectCacheEntry } from '../../cache/object-cache.reducer';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { createPaginatedList } from '../../../shared/testing/utils.test';
|
||||||
|
|
||||||
const endpoint = 'https://rest.api/core';
|
const endpoint = 'https://rest.api/core';
|
||||||
|
|
||||||
@@ -46,34 +50,18 @@ describe('BaseDataService', () => {
|
|||||||
let requestService;
|
let requestService;
|
||||||
let halService;
|
let halService;
|
||||||
let rdbService;
|
let rdbService;
|
||||||
let objectCache;
|
let objectCache: ObjectCacheServiceStub;
|
||||||
let selfLink;
|
let selfLink;
|
||||||
let linksToFollow;
|
let linksToFollow;
|
||||||
let testScheduler;
|
let testScheduler;
|
||||||
let remoteDataMocks;
|
let remoteDataMocks: { [responseType: string]: RemoteData<any> };
|
||||||
|
let remoteDataPageMocks: { [responseType: string]: RemoteData<any> };
|
||||||
|
|
||||||
function initTestService(): TestService {
|
function initTestService(): TestService {
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
halService = new HALEndpointServiceStub('url') as any;
|
halService = new HALEndpointServiceStub('url') as any;
|
||||||
rdbService = getMockRemoteDataBuildService();
|
rdbService = getMockRemoteDataBuildService();
|
||||||
objectCache = {
|
objectCache = new ObjectCacheServiceStub();
|
||||||
|
|
||||||
addPatch: () => {
|
|
||||||
/* empty */
|
|
||||||
},
|
|
||||||
getObjectBySelfLink: () => {
|
|
||||||
/* empty */
|
|
||||||
},
|
|
||||||
getByHref: () => {
|
|
||||||
/* empty */
|
|
||||||
},
|
|
||||||
addDependency: () => {
|
|
||||||
/* empty */
|
|
||||||
},
|
|
||||||
removeDependents: () => {
|
|
||||||
/* empty */
|
|
||||||
},
|
|
||||||
} as any;
|
|
||||||
selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
||||||
linksToFollow = [
|
linksToFollow = [
|
||||||
followLink('a'),
|
followLink('a'),
|
||||||
@@ -88,7 +76,27 @@ describe('BaseDataService', () => {
|
|||||||
|
|
||||||
const timeStamp = new Date().getTime();
|
const timeStamp = new Date().getTime();
|
||||||
const msToLive = 15 * 60 * 1000;
|
const msToLive = 15 * 60 * 1000;
|
||||||
const payload = { foo: 'bar' };
|
const payload = {
|
||||||
|
foo: 'bar',
|
||||||
|
followLink1: {},
|
||||||
|
followLink2: {},
|
||||||
|
_links: {
|
||||||
|
self: Object.assign(new HALLink(), {
|
||||||
|
href: 'self-test-link',
|
||||||
|
}),
|
||||||
|
followLink1: Object.assign(new HALLink(), {
|
||||||
|
href: 'follow-link-1',
|
||||||
|
}),
|
||||||
|
followLink2: [
|
||||||
|
Object.assign(new HALLink(), {
|
||||||
|
href: 'follow-link-2-1',
|
||||||
|
}),
|
||||||
|
Object.assign(new HALLink(), {
|
||||||
|
href: 'follow-link-2-2',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
};
|
||||||
const statusCodeSuccess = 200;
|
const statusCodeSuccess = 200;
|
||||||
const statusCodeError = 404;
|
const statusCodeError = 404;
|
||||||
const errorMessage = 'not found';
|
const errorMessage = 'not found';
|
||||||
@@ -101,11 +109,20 @@ describe('BaseDataService', () => {
|
|||||||
Error: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError),
|
Error: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError),
|
||||||
ErrorStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError),
|
ErrorStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError),
|
||||||
};
|
};
|
||||||
|
remoteDataPageMocks = {
|
||||||
|
RequestPending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.RequestPending, undefined, undefined, undefined),
|
||||||
|
ResponsePending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.ResponsePending, undefined, undefined, undefined),
|
||||||
|
ResponsePendingStale: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.ResponsePendingStale, undefined, undefined, undefined),
|
||||||
|
Success: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Success, undefined, createPaginatedList([payload]), statusCodeSuccess),
|
||||||
|
SuccessStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.SuccessStale, undefined, createPaginatedList([payload]), statusCodeSuccess),
|
||||||
|
Error: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError),
|
||||||
|
ErrorStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError),
|
||||||
|
};
|
||||||
|
|
||||||
return new TestService(
|
return new TestService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
objectCache,
|
objectCache as ObjectCacheService,
|
||||||
halService,
|
halService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -380,6 +397,27 @@ describe('BaseDataService', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should link all the followLinks of a cached object by calling addDependency', () => {
|
||||||
|
spyOn(objectCache, 'addDependency').and.callThrough();
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d', {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, false, false, ...linksToFollow)).toBe(expected, values);
|
||||||
|
flush();
|
||||||
|
expect(objectCache.addDependency).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`findListByHref`, () => {
|
describe(`findListByHref`, () => {
|
||||||
@@ -392,8 +430,8 @@ describe('BaseDataService', () => {
|
|||||||
it(`should call buildHrefFromFindOptions with href and linksToFollow`, () => {
|
it(`should call buildHrefFromFindOptions with href and linksToFollow`, () => {
|
||||||
testScheduler.run(({ cold }) => {
|
testScheduler.run(({ cold }) => {
|
||||||
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataPageMocks.Success }));
|
||||||
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataPageMocks.Success }));
|
||||||
|
|
||||||
service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
expect(service.buildHrefFromFindOptions).toHaveBeenCalledWith(selfLink, findListOptions, [], ...linksToFollow);
|
expect(service.buildHrefFromFindOptions).toHaveBeenCalledWith(selfLink, findListOptions, [], ...linksToFollow);
|
||||||
@@ -403,8 +441,8 @@ describe('BaseDataService', () => {
|
|||||||
it(`should call createAndSendGetRequest with the result from buildHrefFromFindOptions and useCachedVersionIfAvailable`, () => {
|
it(`should call createAndSendGetRequest with the result from buildHrefFromFindOptions and useCachedVersionIfAvailable`, () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataPageMocks.Success }));
|
||||||
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataPageMocks.Success }));
|
||||||
|
|
||||||
service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), true);
|
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), true);
|
||||||
@@ -419,8 +457,8 @@ describe('BaseDataService', () => {
|
|||||||
it(`should call rdbService.buildList with the result from buildHrefFromFindOptions and linksToFollow`, () => {
|
it(`should call rdbService.buildList with the result from buildHrefFromFindOptions and linksToFollow`, () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataPageMocks.Success }));
|
||||||
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataPageMocks.Success }));
|
||||||
|
|
||||||
service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
expect(rdbService.buildList).toHaveBeenCalledWith(jasmine.anything() as any, ...linksToFollow);
|
expect(rdbService.buildList).toHaveBeenCalledWith(jasmine.anything() as any, ...linksToFollow);
|
||||||
@@ -431,12 +469,12 @@ describe('BaseDataService', () => {
|
|||||||
it(`should call reRequestStaleRemoteData with reRequestOnStale and the exact same findListByHref call as a callback`, () => {
|
it(`should call reRequestStaleRemoteData with reRequestOnStale and the exact same findListByHref call as a callback`, () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataPageMocks.SuccessStale }));
|
||||||
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.SuccessStale }));
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataPageMocks.SuccessStale }));
|
||||||
|
|
||||||
service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
expect((service as any).reRequestStaleRemoteData.calls.argsFor(0)[0]).toBeTrue();
|
expect((service as any).reRequestStaleRemoteData.calls.argsFor(0)[0]).toBeTrue();
|
||||||
spyOn(service, 'findListByHref').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
spyOn(service, 'findListByHref').and.returnValue(cold('a', { a: remoteDataPageMocks.SuccessStale }));
|
||||||
// prove that the spy we just added hasn't been called yet
|
// prove that the spy we just added hasn't been called yet
|
||||||
expect(service.findListByHref).not.toHaveBeenCalled();
|
expect(service.findListByHref).not.toHaveBeenCalled();
|
||||||
// call the callback passed to reRequestStaleRemoteData
|
// call the callback passed to reRequestStaleRemoteData
|
||||||
@@ -451,7 +489,7 @@ describe('BaseDataService', () => {
|
|||||||
it(`should return a the output from reRequestStaleRemoteData`, () => {
|
it(`should return a the output from reRequestStaleRemoteData`, () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataPageMocks.Success }));
|
||||||
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: 'bingo!' }));
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: 'bingo!' }));
|
||||||
const expected = 'a';
|
const expected = 'a';
|
||||||
const values = {
|
const values = {
|
||||||
@@ -471,19 +509,19 @@ describe('BaseDataService', () => {
|
|||||||
it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets rerequested`, () => {
|
it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets rerequested`, () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
a: remoteDataMocks.Success,
|
a: remoteDataPageMocks.Success,
|
||||||
b: remoteDataMocks.RequestPending,
|
b: remoteDataPageMocks.RequestPending,
|
||||||
c: remoteDataMocks.ResponsePending,
|
c: remoteDataPageMocks.ResponsePending,
|
||||||
d: remoteDataMocks.Success,
|
d: remoteDataPageMocks.Success,
|
||||||
e: remoteDataMocks.SuccessStale,
|
e: remoteDataPageMocks.SuccessStale,
|
||||||
}));
|
}));
|
||||||
const expected = 'a-b-c-d-e';
|
const expected = 'a-b-c-d-e';
|
||||||
const values = {
|
const values = {
|
||||||
a: remoteDataMocks.Success,
|
a: remoteDataPageMocks.Success,
|
||||||
b: remoteDataMocks.RequestPending,
|
b: remoteDataPageMocks.RequestPending,
|
||||||
c: remoteDataMocks.ResponsePending,
|
c: remoteDataPageMocks.ResponsePending,
|
||||||
d: remoteDataMocks.Success,
|
d: remoteDataPageMocks.Success,
|
||||||
e: remoteDataMocks.SuccessStale,
|
e: remoteDataPageMocks.SuccessStale,
|
||||||
};
|
};
|
||||||
|
|
||||||
expectObservable(service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
expectObservable(service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
@@ -493,20 +531,20 @@ describe('BaseDataService', () => {
|
|||||||
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e-f-g', {
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e-f-g', {
|
||||||
a: remoteDataMocks.ResponsePendingStale,
|
a: remoteDataPageMocks.ResponsePendingStale,
|
||||||
b: remoteDataMocks.SuccessStale,
|
b: remoteDataPageMocks.SuccessStale,
|
||||||
c: remoteDataMocks.ErrorStale,
|
c: remoteDataPageMocks.ErrorStale,
|
||||||
d: remoteDataMocks.RequestPending,
|
d: remoteDataPageMocks.RequestPending,
|
||||||
e: remoteDataMocks.ResponsePending,
|
e: remoteDataPageMocks.ResponsePending,
|
||||||
f: remoteDataMocks.Success,
|
f: remoteDataPageMocks.Success,
|
||||||
g: remoteDataMocks.SuccessStale,
|
g: remoteDataPageMocks.SuccessStale,
|
||||||
}));
|
}));
|
||||||
const expected = '------d-e-f-g';
|
const expected = '------d-e-f-g';
|
||||||
const values = {
|
const values = {
|
||||||
d: remoteDataMocks.RequestPending,
|
d: remoteDataPageMocks.RequestPending,
|
||||||
e: remoteDataMocks.ResponsePending,
|
e: remoteDataPageMocks.ResponsePending,
|
||||||
f: remoteDataMocks.Success,
|
f: remoteDataPageMocks.Success,
|
||||||
g: remoteDataMocks.SuccessStale,
|
g: remoteDataPageMocks.SuccessStale,
|
||||||
};
|
};
|
||||||
|
|
||||||
expectObservable(service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
expectObservable(service.findListByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
@@ -525,18 +563,18 @@ describe('BaseDataService', () => {
|
|||||||
it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
a: remoteDataMocks.Success,
|
a: remoteDataPageMocks.Success,
|
||||||
b: remoteDataMocks.RequestPending,
|
b: remoteDataPageMocks.RequestPending,
|
||||||
c: remoteDataMocks.ResponsePending,
|
c: remoteDataPageMocks.ResponsePending,
|
||||||
d: remoteDataMocks.Success,
|
d: remoteDataPageMocks.Success,
|
||||||
e: remoteDataMocks.SuccessStale,
|
e: remoteDataPageMocks.SuccessStale,
|
||||||
}));
|
}));
|
||||||
const expected = '--b-c-d-e';
|
const expected = '--b-c-d-e';
|
||||||
const values = {
|
const values = {
|
||||||
b: remoteDataMocks.RequestPending,
|
b: remoteDataPageMocks.RequestPending,
|
||||||
c: remoteDataMocks.ResponsePending,
|
c: remoteDataPageMocks.ResponsePending,
|
||||||
d: remoteDataMocks.Success,
|
d: remoteDataPageMocks.Success,
|
||||||
e: remoteDataMocks.SuccessStale,
|
e: remoteDataPageMocks.SuccessStale,
|
||||||
};
|
};
|
||||||
|
|
||||||
expectObservable(service.findListByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values);
|
expectObservable(service.findListByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values);
|
||||||
@@ -546,20 +584,20 @@ describe('BaseDataService', () => {
|
|||||||
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
testScheduler.run(({ cold, expectObservable }) => {
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e-f-g', {
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e-f-g', {
|
||||||
a: remoteDataMocks.ResponsePendingStale,
|
a: remoteDataPageMocks.ResponsePendingStale,
|
||||||
b: remoteDataMocks.SuccessStale,
|
b: remoteDataPageMocks.SuccessStale,
|
||||||
c: remoteDataMocks.ErrorStale,
|
c: remoteDataPageMocks.ErrorStale,
|
||||||
d: remoteDataMocks.RequestPending,
|
d: remoteDataPageMocks.RequestPending,
|
||||||
e: remoteDataMocks.ResponsePending,
|
e: remoteDataPageMocks.ResponsePending,
|
||||||
f: remoteDataMocks.Success,
|
f: remoteDataPageMocks.Success,
|
||||||
g: remoteDataMocks.SuccessStale,
|
g: remoteDataPageMocks.SuccessStale,
|
||||||
}));
|
}));
|
||||||
const expected = '------d-e-f-g';
|
const expected = '------d-e-f-g';
|
||||||
const values = {
|
const values = {
|
||||||
d: remoteDataMocks.RequestPending,
|
d: remoteDataPageMocks.RequestPending,
|
||||||
e: remoteDataMocks.ResponsePending,
|
e: remoteDataPageMocks.ResponsePending,
|
||||||
f: remoteDataMocks.Success,
|
f: remoteDataPageMocks.Success,
|
||||||
g: remoteDataMocks.SuccessStale,
|
g: remoteDataPageMocks.SuccessStale,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -567,6 +605,27 @@ describe('BaseDataService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should link all the followLinks of the cached objects by calling addDependency', () => {
|
||||||
|
spyOn(objectCache, 'addDependency').and.callThrough();
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d', {
|
||||||
|
a: remoteDataPageMocks.SuccessStale,
|
||||||
|
b: remoteDataPageMocks.RequestPending,
|
||||||
|
c: remoteDataPageMocks.ResponsePending,
|
||||||
|
d: remoteDataPageMocks.Success,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataPageMocks.RequestPending,
|
||||||
|
c: remoteDataPageMocks.ResponsePending,
|
||||||
|
d: remoteDataPageMocks.Success,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findListByHref(selfLink, findListOptions, false, false, ...linksToFollow)).toBe(expected, values);
|
||||||
|
flush();
|
||||||
|
expect(objectCache.addDependency).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -577,7 +636,7 @@ describe('BaseDataService', () => {
|
|||||||
getByHrefSpy = spyOn(objectCache, 'getByHref').and.returnValue(observableOf({
|
getByHrefSpy = spyOn(objectCache, 'getByHref').and.returnValue(observableOf({
|
||||||
requestUUIDs: ['request1', 'request2', 'request3'],
|
requestUUIDs: ['request1', 'request2', 'request3'],
|
||||||
dependentRequestUUIDs: ['request4', 'request5']
|
dependentRequestUUIDs: ['request4', 'request5']
|
||||||
}));
|
} as ObjectCacheEntry));
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -24,6 +24,7 @@ import { ObjectCacheEntry } from '../../cache/object-cache.reducer';
|
|||||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||||
import { HALDataService } from './hal-data-service.interface';
|
import { HALDataService } from './hal-data-service.interface';
|
||||||
import { getFirstCompletedRemoteData } from '../../shared/operators';
|
import { getFirstCompletedRemoteData } from '../../shared/operators';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
|
||||||
export const EMBED_SEPARATOR = '%2F';
|
export const EMBED_SEPARATOR = '%2F';
|
||||||
/**
|
/**
|
||||||
@@ -268,7 +269,7 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
|
|||||||
|
|
||||||
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
||||||
|
|
||||||
return this.rdbService.buildSingle<T>(requestHref$, ...linksToFollow).pipe(
|
const response$: Observable<RemoteData<T>> = this.rdbService.buildSingle<T>(requestHref$, ...linksToFollow).pipe(
|
||||||
// This skip ensures that if a stale object is present in the cache when you do a
|
// This skip ensures that if a stale object is present in the cache when you do a
|
||||||
// call it isn't immediately returned, but we wait until the remote data for the new request
|
// call it isn't immediately returned, but we wait until the remote data for the new request
|
||||||
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
||||||
@@ -277,6 +278,25 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
|
|||||||
this.reRequestStaleRemoteData(reRequestOnStale, () =>
|
this.reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||||
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)),
|
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)),
|
||||||
);
|
);
|
||||||
|
return response$.pipe(
|
||||||
|
// Ensure all followLinks from the cached object are automatically invalidated when invalidating the cached object
|
||||||
|
tap((remoteDataObject: RemoteData<T>) => {
|
||||||
|
if (hasValue(remoteDataObject?.payload?._links)) {
|
||||||
|
for (const followLinkName of Object.keys(remoteDataObject.payload._links)) {
|
||||||
|
// only add the followLinks if they are embedded
|
||||||
|
if (hasValue(remoteDataObject.payload[followLinkName]) && followLinkName !== 'self') {
|
||||||
|
// followLink can be either an individual HALLink or a HALLink[]
|
||||||
|
const followLinksList: HALLink[] = [].concat(remoteDataObject.payload._links[followLinkName]);
|
||||||
|
for (const individualFollowLink of followLinksList) {
|
||||||
|
if (hasValue(individualFollowLink?.href)) {
|
||||||
|
this.addDependency(response$, individualFollowLink.href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -302,7 +322,7 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
|
|||||||
|
|
||||||
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
||||||
|
|
||||||
return this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
|
const response$: Observable<RemoteData<PaginatedList<T>>> = this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
|
||||||
// This skip ensures that if a stale object is present in the cache when you do a
|
// This skip ensures that if a stale object is present in the cache when you do a
|
||||||
// call it isn't immediately returned, but we wait until the remote data for the new request
|
// call it isn't immediately returned, but we wait until the remote data for the new request
|
||||||
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
||||||
@@ -311,6 +331,29 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
|
|||||||
this.reRequestStaleRemoteData(reRequestOnStale, () =>
|
this.reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||||
this.findListByHref(href$, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)),
|
this.findListByHref(href$, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow)),
|
||||||
);
|
);
|
||||||
|
return response$.pipe(
|
||||||
|
// Ensure all followLinks from the cached object are automatically invalidated when invalidating the cached object
|
||||||
|
tap((remoteDataObject: RemoteData<PaginatedList<T>>) => {
|
||||||
|
if (hasValue(remoteDataObject?.payload?.page)) {
|
||||||
|
for (const object of remoteDataObject.payload.page) {
|
||||||
|
if (hasValue(object?._links)) {
|
||||||
|
for (const followLinkName of Object.keys(object._links)) {
|
||||||
|
// only add the followLinks if they are embedded
|
||||||
|
if (hasValue(object[followLinkName]) && followLinkName !== 'self') {
|
||||||
|
// followLink can be either an individual HALLink or a HALLink[]
|
||||||
|
const followLinksList: HALLink[] = [].concat(object._links[followLinkName]);
|
||||||
|
for (const individualFollowLink of followLinksList) {
|
||||||
|
if (hasValue(individualFollowLink?.href)) {
|
||||||
|
this.addDependency(response$, individualFollowLink.href);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -24,6 +24,7 @@ import { testFindAllDataImplementation } from './base/find-all-data.spec';
|
|||||||
import { testSearchDataImplementation } from './base/search-data.spec';
|
import { testSearchDataImplementation } from './base/search-data.spec';
|
||||||
import { testPatchDataImplementation } from './base/patch-data.spec';
|
import { testPatchDataImplementation } from './base/patch-data.spec';
|
||||||
import { testDeleteDataImplementation } from './base/delete-data.spec';
|
import { testDeleteDataImplementation } from './base/delete-data.spec';
|
||||||
|
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';
|
||||||
|
|
||||||
const url = 'fake-url';
|
const url = 'fake-url';
|
||||||
const collectionId = 'fake-collection-id';
|
const collectionId = 'fake-collection-id';
|
||||||
@@ -35,7 +36,7 @@ describe('CollectionDataService', () => {
|
|||||||
let translate: TranslateService;
|
let translate: TranslateService;
|
||||||
let notificationsService: any;
|
let notificationsService: any;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
let objectCache: ObjectCacheService;
|
let objectCache: ObjectCacheServiceStub;
|
||||||
let halService: any;
|
let halService: any;
|
||||||
|
|
||||||
const mockCollection1: Collection = Object.assign(new Collection(), {
|
const mockCollection1: Collection = Object.assign(new Collection(), {
|
||||||
@@ -205,14 +206,12 @@ describe('CollectionDataService', () => {
|
|||||||
buildFromRequestUUID: buildResponse$,
|
buildFromRequestUUID: buildResponse$,
|
||||||
buildSingle: buildResponse$
|
buildSingle: buildResponse$
|
||||||
});
|
});
|
||||||
objectCache = jasmine.createSpyObj('objectCache', {
|
objectCache = new ObjectCacheServiceStub();
|
||||||
remove: jasmine.createSpy('remove')
|
|
||||||
});
|
|
||||||
halService = new HALEndpointServiceStub(url);
|
halService = new HALEndpointServiceStub(url);
|
||||||
notificationsService = new NotificationsServiceStub();
|
notificationsService = new NotificationsServiceStub();
|
||||||
translate = getMockTranslateService();
|
translate = getMockTranslateService();
|
||||||
|
|
||||||
service = new CollectionDataService(requestService, rdbService, objectCache, halService, null, notificationsService, null, null, translate);
|
service = new CollectionDataService(requestService, rdbService, objectCache as ObjectCacheService, halService, null, notificationsService, null, null, translate);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -7,13 +7,120 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { testFindAllDataImplementation } from '../base/find-all-data.spec';
|
import { testFindAllDataImplementation } from '../base/find-all-data.spec';
|
||||||
import { ProcessDataService } from './process-data.service';
|
import { ProcessDataService, TIMER_FACTORY } from './process-data.service';
|
||||||
import { testDeleteDataImplementation } from '../base/delete-data.spec';
|
import { testDeleteDataImplementation } from '../base/delete-data.spec';
|
||||||
|
import { waitForAsync, TestBed } from '@angular/core/testing';
|
||||||
|
import { RequestService } from '../request.service';
|
||||||
|
import { RemoteData } from '../remote-data';
|
||||||
|
import { RequestEntryState } from '../request-entry-state.model';
|
||||||
|
import { Process } from '../../../process-page/processes/process.model';
|
||||||
|
import { ProcessStatus } from '../../../process-page/processes/process-status.model';
|
||||||
|
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||||
|
import { ReducerManager } from '@ngrx/store';
|
||||||
|
import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
||||||
|
import { DSOChangeAnalyzer } from '../dso-change-analyzer.service';
|
||||||
|
import { BitstreamFormatDataService } from '../bitstream-format-data.service';
|
||||||
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
describe('ProcessDataService', () => {
|
describe('ProcessDataService', () => {
|
||||||
|
let testScheduler;
|
||||||
|
|
||||||
|
const mockTimer = (fn: () => {}, interval: number) => {
|
||||||
|
fn();
|
||||||
|
return 555;
|
||||||
|
};
|
||||||
|
|
||||||
describe('composition', () => {
|
describe('composition', () => {
|
||||||
const initService = () => new ProcessDataService(null, null, null, null, null, null);
|
const initService = () => new ProcessDataService(null, null, null, null, null, null, null, null);
|
||||||
testFindAllDataImplementation(initService);
|
testFindAllDataImplementation(initService);
|
||||||
testDeleteDataImplementation(initService);
|
testDeleteDataImplementation(initService);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let requestService;
|
||||||
|
let processDataService;
|
||||||
|
let remoteDataBuildService;
|
||||||
|
|
||||||
|
describe('autoRefreshUntilCompletion', () => {
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
testScheduler = new TestScheduler((actual, expected) => {
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [],
|
||||||
|
providers: [
|
||||||
|
ProcessDataService,
|
||||||
|
{ provide: RequestService, useValue: null },
|
||||||
|
{ provide: RemoteDataBuildService, useValue: null },
|
||||||
|
{ provide: ObjectCacheService, useValue: null },
|
||||||
|
{ provide: ReducerManager, useValue: null },
|
||||||
|
{ provide: HALEndpointService, useValue: null },
|
||||||
|
{ provide: DSOChangeAnalyzer, useValue: null },
|
||||||
|
{ provide: BitstreamFormatDataService, useValue: null },
|
||||||
|
{ provide: NotificationsService, useValue: null },
|
||||||
|
{ provide: TIMER_FACTORY, useValue: mockTimer },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
processDataService = TestBed.inject(ProcessDataService);
|
||||||
|
spyOn(processDataService, 'invalidateByHref');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should not do any polling when the process is already completed', () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
let completedProcess = new Process();
|
||||||
|
completedProcess.processStatus = ProcessStatus.COMPLETED;
|
||||||
|
|
||||||
|
const completedProcessRD = new RemoteData(0, 0, 0, RequestEntryState.Success, null, completedProcess);
|
||||||
|
|
||||||
|
spyOn(processDataService, 'findById').and.returnValue(
|
||||||
|
cold('c', {
|
||||||
|
'c': completedProcessRD
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let process$ = processDataService.autoRefreshUntilCompletion('instantly');
|
||||||
|
expectObservable(process$).toBe('c', {
|
||||||
|
c: completedProcessRD
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(processDataService.findById).toHaveBeenCalledTimes(1);
|
||||||
|
expect(processDataService.invalidateByHref).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should poll until a process completes', () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
const runningProcess = Object.assign(new Process(), {
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/processes/123'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
runningProcess.processStatus = ProcessStatus.RUNNING;
|
||||||
|
const completedProcess = new Process();
|
||||||
|
completedProcess.processStatus = ProcessStatus.COMPLETED;
|
||||||
|
const runningProcessRD = new RemoteData(0, 0, 0, RequestEntryState.Success, null, runningProcess);
|
||||||
|
const completedProcessRD = new RemoteData(0, 0, 0, RequestEntryState.Success, null, completedProcess);
|
||||||
|
|
||||||
|
spyOn(processDataService, 'findById').and.returnValue(
|
||||||
|
cold('r 150ms c', {
|
||||||
|
'r': runningProcessRD,
|
||||||
|
'c': completedProcessRD
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let process$ = processDataService.autoRefreshUntilCompletion('foo', 100);
|
||||||
|
expectObservable(process$).toBe('r 150ms c', {
|
||||||
|
'r': runningProcessRD,
|
||||||
|
'c': completedProcessRD
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(processDataService.findById).toHaveBeenCalledTimes(1);
|
||||||
|
expect(processDataService.invalidateByHref).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable, NgZone, Inject, InjectionToken } from '@angular/core';
|
||||||
import { RequestService } from '../request.service';
|
import { RequestService } from '../request.service';
|
||||||
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
|
||||||
import { ObjectCacheService } from '../../cache/object-cache.service';
|
import { ObjectCacheService } from '../../cache/object-cache.service';
|
||||||
@@ -6,7 +6,7 @@ import { HALEndpointService } from '../../shared/hal-endpoint.service';
|
|||||||
import { Process } from '../../../process-page/processes/process.model';
|
import { Process } from '../../../process-page/processes/process.model';
|
||||||
import { PROCESS } from '../../../process-page/processes/process.resource-type';
|
import { PROCESS } from '../../../process-page/processes/process.resource-type';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap, filter, distinctUntilChanged, find } from 'rxjs/operators';
|
||||||
import { PaginatedList } from '../paginated-list.model';
|
import { PaginatedList } from '../paginated-list.model';
|
||||||
import { Bitstream } from '../../shared/bitstream.model';
|
import { Bitstream } from '../../shared/bitstream.model';
|
||||||
import { RemoteData } from '../remote-data';
|
import { RemoteData } from '../remote-data';
|
||||||
@@ -19,12 +19,26 @@ import { dataService } from '../base/data-service.decorator';
|
|||||||
import { DeleteData, DeleteDataImpl } from '../base/delete-data';
|
import { DeleteData, DeleteDataImpl } from '../base/delete-data';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { NoContent } from '../../shared/NoContent.model';
|
import { NoContent } from '../../shared/NoContent.model';
|
||||||
|
import { getAllCompletedRemoteData } from '../../shared/operators';
|
||||||
|
import { ProcessStatus } from 'src/app/process-page/processes/process-status.model';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an InjectionToken for the default JS setTimeout function, purely so we can mock it during
|
||||||
|
* testing. (fakeAsync isn't working for this case)
|
||||||
|
*/
|
||||||
|
export const TIMER_FACTORY = new InjectionToken<(callback: (...args: any[]) => void, ms?: number, ...args: any[]) => NodeJS.Timeout>('timer', {
|
||||||
|
providedIn: 'root',
|
||||||
|
factory: () => setTimeout
|
||||||
|
});
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@dataService(PROCESS)
|
@dataService(PROCESS)
|
||||||
export class ProcessDataService extends IdentifiableDataService<Process> implements FindAllData<Process>, DeleteData<Process> {
|
export class ProcessDataService extends IdentifiableDataService<Process> implements FindAllData<Process>, DeleteData<Process> {
|
||||||
|
|
||||||
private findAllData: FindAllData<Process>;
|
private findAllData: FindAllData<Process>;
|
||||||
private deleteData: DeleteData<Process>;
|
private deleteData: DeleteData<Process>;
|
||||||
|
protected activelyBeingPolled: Map<string, NodeJS.Timeout> = new Map();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
@@ -33,6 +47,8 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
|
|||||||
protected halService: HALEndpointService,
|
protected halService: HALEndpointService,
|
||||||
protected bitstreamDataService: BitstreamDataService,
|
protected bitstreamDataService: BitstreamDataService,
|
||||||
protected notificationsService: NotificationsService,
|
protected notificationsService: NotificationsService,
|
||||||
|
protected zone: NgZone,
|
||||||
|
@Inject(TIMER_FACTORY) protected timer: (callback: (...args: any[]) => void, ms?: number, ...args: any[]) => NodeJS.Timeout
|
||||||
) {
|
) {
|
||||||
super('processes', requestService, rdbService, objectCache, halService);
|
super('processes', requestService, rdbService, objectCache, halService);
|
||||||
|
|
||||||
@@ -40,6 +56,22 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
|
|||||||
this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint);
|
this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the given process has the given status
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected static statusIs(process: Process, status: ProcessStatus): boolean {
|
||||||
|
return hasValue(process) && process.processStatus === status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the given process has the status COMPLETED or FAILED
|
||||||
|
*/
|
||||||
|
public static hasCompletedOrFailed(process: Process): boolean {
|
||||||
|
return ProcessDataService.statusIs(process, ProcessStatus.COMPLETED) ||
|
||||||
|
ProcessDataService.statusIs(process, ProcessStatus.FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the endpoint for the files of the process
|
* Get the endpoint for the files of the process
|
||||||
* @param processId The ID of the process
|
* @param processId The ID of the process
|
||||||
@@ -101,4 +133,73 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
|
|||||||
public deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
public deleteByHref(href: string, copyVirtualMetadata?: string[]): Observable<RemoteData<NoContent>> {
|
||||||
return this.deleteData.deleteByHref(href, copyVirtualMetadata);
|
return this.deleteData.deleteByHref(href, copyVirtualMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the timeout for the given process, if that timeout exists
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected clearCurrentTimeout(processId: string): void {
|
||||||
|
const timeout = this.activelyBeingPolled.get(processId);
|
||||||
|
if (hasValue(timeout)) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poll the process with the given ID, using the given interval, until that process either
|
||||||
|
* completes successfully or fails
|
||||||
|
*
|
||||||
|
* Return an Observable<RemoteData> for the Process. Note that this will also emit while the
|
||||||
|
* process is still running. It will only emit again when the process (not the RemoteData!) changes
|
||||||
|
* status. That makes it more convenient to retrieve that process for a component: you can replace
|
||||||
|
* a findByID call with this method, rather than having to do a separate findById, and then call
|
||||||
|
* this method
|
||||||
|
*
|
||||||
|
* @param processId The ID of the {@link Process} to poll
|
||||||
|
* @param pollingIntervalInMs The interval for how often the request needs to be polled
|
||||||
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be
|
||||||
|
* automatically resolved
|
||||||
|
*/
|
||||||
|
public autoRefreshUntilCompletion(processId: string, pollingIntervalInMs = 5000, ...linksToFollow: FollowLinkConfig<Process>[]): Observable<RemoteData<Process>> {
|
||||||
|
const process$: Observable<RemoteData<Process>> = this.findById(processId, true, true, ...linksToFollow)
|
||||||
|
.pipe(
|
||||||
|
getAllCompletedRemoteData(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a subscription that marks the data as stale if the process hasn't been completed and
|
||||||
|
// the polling interval time has been exceeded.
|
||||||
|
const sub = process$.pipe(
|
||||||
|
filter((processRD: RemoteData<Process>) =>
|
||||||
|
!ProcessDataService.hasCompletedOrFailed(processRD.payload) &&
|
||||||
|
!this.activelyBeingPolled.has(processId)
|
||||||
|
)
|
||||||
|
).subscribe((processRD: RemoteData<Process>) => {
|
||||||
|
this.clearCurrentTimeout(processId);
|
||||||
|
if (processRD.hasSucceeded) {
|
||||||
|
const nextTimeout = this.timer(() => {
|
||||||
|
this.activelyBeingPolled.delete(processId);
|
||||||
|
this.invalidateByHref(processRD.payload._links.self.href);
|
||||||
|
}, pollingIntervalInMs);
|
||||||
|
|
||||||
|
this.activelyBeingPolled.set(processId, nextTimeout);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the process completes create a one off subscription (the `find` completes the
|
||||||
|
// observable) that unsubscribes the previous one, removes the processId from the list of
|
||||||
|
// processes being polled and clears any running timeouts
|
||||||
|
process$.pipe(
|
||||||
|
find((processRD: RemoteData<Process>) => ProcessDataService.hasCompletedOrFailed(processRD.payload))
|
||||||
|
).subscribe(() => {
|
||||||
|
this.clearCurrentTimeout(processId);
|
||||||
|
this.activelyBeingPolled.delete(processId);
|
||||||
|
sub.unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
return process$.pipe(
|
||||||
|
distinctUntilChanged((previous: RemoteData<Process>, current: RemoteData<Process>) =>
|
||||||
|
previous.payload?.processStatus === current.payload?.processStatus,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ import { FindListOptions } from './find-list-options.model';
|
|||||||
import { testSearchDataImplementation } from './base/search-data.spec';
|
import { testSearchDataImplementation } from './base/search-data.spec';
|
||||||
import { MetadataValue } from '../shared/metadata.models';
|
import { MetadataValue } from '../shared/metadata.models';
|
||||||
import { MetadataRepresentationType } from '../shared/metadata-representation/metadata-representation.model';
|
import { MetadataRepresentationType } from '../shared/metadata-representation/metadata-representation.model';
|
||||||
|
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';
|
||||||
|
|
||||||
describe('RelationshipDataService', () => {
|
describe('RelationshipDataService', () => {
|
||||||
let service: RelationshipDataService;
|
let service: RelationshipDataService;
|
||||||
@@ -114,14 +115,7 @@ describe('RelationshipDataService', () => {
|
|||||||
'href': buildList$,
|
'href': buildList$,
|
||||||
'https://rest.api/core/publication/relationships': relationships$
|
'https://rest.api/core/publication/relationships': relationships$
|
||||||
});
|
});
|
||||||
const objectCache = Object.assign({
|
const objectCache = new ObjectCacheServiceStub();
|
||||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
|
||||||
remove: () => {
|
|
||||||
},
|
|
||||||
hasBySelfLinkObservable: () => observableOf(false),
|
|
||||||
hasByHref$: () => observableOf(false)
|
|
||||||
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
|
||||||
}) as ObjectCacheService;
|
|
||||||
|
|
||||||
const itemService = jasmine.createSpyObj('itemService', {
|
const itemService = jasmine.createSpyObj('itemService', {
|
||||||
findById: (uuid) => createSuccessfulRemoteDataObject(relatedItems.find((relatedItem) => relatedItem.id === uuid)),
|
findById: (uuid) => createSuccessfulRemoteDataObject(relatedItems.find((relatedItem) => relatedItem.id === uuid)),
|
||||||
@@ -133,7 +127,7 @@ describe('RelationshipDataService', () => {
|
|||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
halService,
|
halService,
|
||||||
objectCache,
|
objectCache as ObjectCacheService,
|
||||||
itemService,
|
itemService,
|
||||||
null,
|
null,
|
||||||
jasmine.createSpy('paginatedRelationsToItems').and.returnValue((v) => v),
|
jasmine.createSpy('paginatedRelationsToItems').and.returnValue((v) => v),
|
||||||
|
@@ -10,6 +10,7 @@ import { RequestService } from './request.service';
|
|||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
import { hasValueOperator } from '../../shared/empty.util';
|
import { hasValueOperator } from '../../shared/empty.util';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';
|
||||||
|
|
||||||
describe('RelationshipTypeDataService', () => {
|
describe('RelationshipTypeDataService', () => {
|
||||||
let service: RelationshipTypeDataService;
|
let service: RelationshipTypeDataService;
|
||||||
@@ -28,7 +29,7 @@ describe('RelationshipTypeDataService', () => {
|
|||||||
|
|
||||||
let buildList;
|
let buildList;
|
||||||
let rdbService;
|
let rdbService;
|
||||||
let objectCache;
|
let objectCache: ObjectCacheServiceStub;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
restEndpointURL = 'https://rest.api/relationshiptypes';
|
restEndpointURL = 'https://rest.api/relationshiptypes';
|
||||||
@@ -60,21 +61,14 @@ describe('RelationshipTypeDataService', () => {
|
|||||||
|
|
||||||
buildList = createSuccessfulRemoteDataObject(createPaginatedList([relationshipType1, relationshipType2]));
|
buildList = createSuccessfulRemoteDataObject(createPaginatedList([relationshipType1, relationshipType2]));
|
||||||
rdbService = getMockRemoteDataBuildService(undefined, observableOf(buildList));
|
rdbService = getMockRemoteDataBuildService(undefined, observableOf(buildList));
|
||||||
objectCache = Object.assign({
|
objectCache = new ObjectCacheServiceStub();
|
||||||
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
|
|
||||||
remove: () => {
|
|
||||||
},
|
|
||||||
hasBySelfLinkObservable: () => observableOf(false)
|
|
||||||
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
|
|
||||||
}) as ObjectCacheService;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTestService() {
|
function initTestService() {
|
||||||
return new RelationshipTypeDataService(
|
return new RelationshipTypeDataService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
objectCache,
|
objectCache as ObjectCacheService,
|
||||||
halService,
|
halService,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ import {
|
|||||||
import { ReplaceOperation } from 'fast-json-patch';
|
import { ReplaceOperation } from 'fast-json-patch';
|
||||||
import { RequestEntry } from '../../../data/request-entry.model';
|
import { RequestEntry } from '../../../data/request-entry.model';
|
||||||
import { FindListOptions } from '../../../data/find-list-options.model';
|
import { FindListOptions } from '../../../data/find-list-options.model';
|
||||||
|
import { ObjectCacheServiceStub } from '../../../../shared/testing/object-cache-service.stub';
|
||||||
|
|
||||||
describe('QualityAssuranceEventDataService', () => {
|
describe('QualityAssuranceEventDataService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -32,7 +33,7 @@ describe('QualityAssuranceEventDataService', () => {
|
|||||||
let responseCacheEntryC: RequestEntry;
|
let responseCacheEntryC: RequestEntry;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
let objectCache: ObjectCacheService;
|
let objectCache: ObjectCacheServiceStub;
|
||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
let notificationsService: NotificationsService;
|
let notificationsService: NotificationsService;
|
||||||
let http: HttpClient;
|
let http: HttpClient;
|
||||||
@@ -91,7 +92,7 @@ describe('QualityAssuranceEventDataService', () => {
|
|||||||
buildFromRequestUUIDAndAwait: jasmine.createSpy('buildFromRequestUUIDAndAwait')
|
buildFromRequestUUIDAndAwait: jasmine.createSpy('buildFromRequestUUIDAndAwait')
|
||||||
});
|
});
|
||||||
|
|
||||||
objectCache = {} as ObjectCacheService;
|
objectCache = new ObjectCacheServiceStub();
|
||||||
halService = jasmine.createSpyObj('halService', {
|
halService = jasmine.createSpyObj('halService', {
|
||||||
getEndpoint: cold('a|', { a: endpointURL })
|
getEndpoint: cold('a|', { a: endpointURL })
|
||||||
});
|
});
|
||||||
@@ -103,7 +104,7 @@ describe('QualityAssuranceEventDataService', () => {
|
|||||||
service = new QualityAssuranceEventDataService(
|
service = new QualityAssuranceEventDataService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
objectCache,
|
objectCache as ObjectCacheService,
|
||||||
halService,
|
halService,
|
||||||
notificationsService,
|
notificationsService,
|
||||||
comparator
|
comparator
|
||||||
|
@@ -19,6 +19,7 @@ import {
|
|||||||
} from '../../../../shared/mocks/notifications.mock';
|
} from '../../../../shared/mocks/notifications.mock';
|
||||||
import { RequestEntry } from '../../../data/request-entry.model';
|
import { RequestEntry } from '../../../data/request-entry.model';
|
||||||
import { QualityAssuranceSourceDataService } from './quality-assurance-source-data.service';
|
import { QualityAssuranceSourceDataService } from './quality-assurance-source-data.service';
|
||||||
|
import { ObjectCacheServiceStub } from '../../../../shared/testing/object-cache-service.stub';
|
||||||
|
|
||||||
describe('QualityAssuranceSourceDataService', () => {
|
describe('QualityAssuranceSourceDataService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -26,7 +27,7 @@ describe('QualityAssuranceSourceDataService', () => {
|
|||||||
let responseCacheEntry: RequestEntry;
|
let responseCacheEntry: RequestEntry;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
let objectCache: ObjectCacheService;
|
let objectCache: ObjectCacheServiceStub;
|
||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
let notificationsService: NotificationsService;
|
let notificationsService: NotificationsService;
|
||||||
let http: HttpClient;
|
let http: HttpClient;
|
||||||
@@ -63,7 +64,7 @@ describe('QualityAssuranceSourceDataService', () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
objectCache = {} as ObjectCacheService;
|
objectCache = new ObjectCacheServiceStub();
|
||||||
halService = jasmine.createSpyObj('halService', {
|
halService = jasmine.createSpyObj('halService', {
|
||||||
getEndpoint: cold('a|', { a: endpointURL })
|
getEndpoint: cold('a|', { a: endpointURL })
|
||||||
});
|
});
|
||||||
@@ -75,7 +76,7 @@ describe('QualityAssuranceSourceDataService', () => {
|
|||||||
service = new QualityAssuranceSourceDataService(
|
service = new QualityAssuranceSourceDataService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
objectCache,
|
objectCache as ObjectCacheService,
|
||||||
halService,
|
halService,
|
||||||
notificationsService
|
notificationsService
|
||||||
);
|
);
|
||||||
|
@@ -19,6 +19,7 @@ import {
|
|||||||
qualityAssuranceTopicObjectMorePid
|
qualityAssuranceTopicObjectMorePid
|
||||||
} from '../../../../shared/mocks/notifications.mock';
|
} from '../../../../shared/mocks/notifications.mock';
|
||||||
import { RequestEntry } from '../../../data/request-entry.model';
|
import { RequestEntry } from '../../../data/request-entry.model';
|
||||||
|
import { ObjectCacheServiceStub } from '../../../../shared/testing/object-cache-service.stub';
|
||||||
|
|
||||||
describe('QualityAssuranceTopicDataService', () => {
|
describe('QualityAssuranceTopicDataService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -26,7 +27,7 @@ describe('QualityAssuranceTopicDataService', () => {
|
|||||||
let responseCacheEntry: RequestEntry;
|
let responseCacheEntry: RequestEntry;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
let objectCache: ObjectCacheService;
|
let objectCache: ObjectCacheServiceStub;
|
||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
let notificationsService: NotificationsService;
|
let notificationsService: NotificationsService;
|
||||||
let http: HttpClient;
|
let http: HttpClient;
|
||||||
@@ -63,7 +64,7 @@ describe('QualityAssuranceTopicDataService', () => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
objectCache = {} as ObjectCacheService;
|
objectCache = new ObjectCacheServiceStub();
|
||||||
halService = jasmine.createSpyObj('halService', {
|
halService = jasmine.createSpyObj('halService', {
|
||||||
getEndpoint: cold('a|', { a: endpointURL })
|
getEndpoint: cold('a|', { a: endpointURL })
|
||||||
});
|
});
|
||||||
@@ -75,7 +76,7 @@ describe('QualityAssuranceTopicDataService', () => {
|
|||||||
service = new QualityAssuranceTopicDataService(
|
service = new QualityAssuranceTopicDataService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
objectCache,
|
objectCache as ObjectCacheService,
|
||||||
halService,
|
halService,
|
||||||
notificationsService
|
notificationsService
|
||||||
);
|
);
|
||||||
|
@@ -20,13 +20,14 @@ import { FindListOptions } from '../data/find-list-options.model';
|
|||||||
import { EPersonDataService } from '../eperson/eperson-data.service';
|
import { EPersonDataService } from '../eperson/eperson-data.service';
|
||||||
import { GroupDataService } from '../eperson/group-data.service';
|
import { GroupDataService } from '../eperson/group-data.service';
|
||||||
import { RestRequestMethod } from '../data/rest-request-method';
|
import { RestRequestMethod } from '../data/rest-request-method';
|
||||||
|
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';
|
||||||
|
|
||||||
describe('ResourcePolicyService', () => {
|
describe('ResourcePolicyService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
let service: ResourcePolicyDataService;
|
let service: ResourcePolicyDataService;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
let objectCache: ObjectCacheService;
|
let objectCache: ObjectCacheServiceStub;
|
||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
let responseCacheEntry: RequestEntry;
|
let responseCacheEntry: RequestEntry;
|
||||||
let ePersonService: EPersonDataService;
|
let ePersonService: EPersonDataService;
|
||||||
@@ -139,14 +140,14 @@ describe('ResourcePolicyService', () => {
|
|||||||
a: 'https://rest.api/rest/api/eperson/groups/' + groupUUID
|
a: 'https://rest.api/rest/api/eperson/groups/' + groupUUID
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
objectCache = {} as ObjectCacheService;
|
objectCache = new ObjectCacheServiceStub();
|
||||||
const notificationsService = {} as NotificationsService;
|
const notificationsService = {} as NotificationsService;
|
||||||
const comparator = {} as any;
|
const comparator = {} as any;
|
||||||
|
|
||||||
service = new ResourcePolicyDataService(
|
service = new ResourcePolicyDataService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
objectCache,
|
objectCache as ObjectCacheService,
|
||||||
halService,
|
halService,
|
||||||
notificationsService,
|
notificationsService,
|
||||||
comparator,
|
comparator,
|
||||||
|
@@ -25,6 +25,7 @@ import { createPaginatedList } from '../../../shared/testing/utils.test';
|
|||||||
import { RequestEntry } from '../../data/request-entry.model';
|
import { RequestEntry } from '../../data/request-entry.model';
|
||||||
import { VocabularyDataService } from './vocabulary.data.service';
|
import { VocabularyDataService } from './vocabulary.data.service';
|
||||||
import { VocabularyEntryDetailsDataService } from './vocabulary-entry-details.data.service';
|
import { VocabularyEntryDetailsDataService } from './vocabulary-entry-details.data.service';
|
||||||
|
import { ObjectCacheServiceStub } from '../../../shared/testing/object-cache-service.stub';
|
||||||
|
|
||||||
describe('VocabularyService', () => {
|
describe('VocabularyService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -205,6 +206,7 @@ describe('VocabularyService', () => {
|
|||||||
|
|
||||||
function initTestService() {
|
function initTestService() {
|
||||||
hrefOnlyDataService = getMockHrefOnlyDataService();
|
hrefOnlyDataService = getMockHrefOnlyDataService();
|
||||||
|
objectCache = new ObjectCacheServiceStub() as ObjectCacheService;
|
||||||
|
|
||||||
return new VocabularyService(
|
return new VocabularyService(
|
||||||
requestService,
|
requestService,
|
||||||
|
@@ -17,13 +17,14 @@ import { RestResponse } from '../cache/response.models';
|
|||||||
import { RequestEntry } from '../data/request-entry.model';
|
import { RequestEntry } from '../data/request-entry.model';
|
||||||
import { FindListOptions } from '../data/find-list-options.model';
|
import { FindListOptions } from '../data/find-list-options.model';
|
||||||
import { GroupDataService } from '../eperson/group-data.service';
|
import { GroupDataService } from '../eperson/group-data.service';
|
||||||
|
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';
|
||||||
|
|
||||||
describe('SupervisionOrderService', () => {
|
describe('SupervisionOrderService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
let service: SupervisionOrderDataService;
|
let service: SupervisionOrderDataService;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
let objectCache: ObjectCacheService;
|
let objectCache: ObjectCacheServiceStub;
|
||||||
let halService: HALEndpointService;
|
let halService: HALEndpointService;
|
||||||
let responseCacheEntry: RequestEntry;
|
let responseCacheEntry: RequestEntry;
|
||||||
let groupService: GroupDataService;
|
let groupService: GroupDataService;
|
||||||
@@ -127,14 +128,14 @@ describe('SupervisionOrderService', () => {
|
|||||||
a: 'https://rest.api/rest/api/group/groups/' + groupUUID
|
a: 'https://rest.api/rest/api/group/groups/' + groupUUID
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
objectCache = {} as ObjectCacheService;
|
objectCache = new ObjectCacheServiceStub();
|
||||||
const notificationsService = {} as NotificationsService;
|
const notificationsService = {} as NotificationsService;
|
||||||
const comparator = {} as any;
|
const comparator = {} as any;
|
||||||
|
|
||||||
service = new SupervisionOrderDataService(
|
service = new SupervisionOrderDataService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
objectCache,
|
objectCache as ObjectCacheService,
|
||||||
halService,
|
halService,
|
||||||
notificationsService,
|
notificationsService,
|
||||||
comparator,
|
comparator,
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
{{ 'process.detail.title' | translate:{ id: process?.processId, name: process?.scriptName } }}
|
{{ 'process.detail.title' | translate:{ id: process?.processId, name: process?.scriptName } }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="refreshCounter$ | async as seconds" class="col-2 refresh-counter">
|
<div *ngIf="isRefreshing$ | async" class="col-2 refresh-counter">
|
||||||
Refreshing in {{ seconds }}s <i class="fas fa-sync-alt fa-spin"></i>
|
{{ 'process.detail.refreshing' | translate }} <i class="fas fa-sync-alt fa-spin"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@@ -27,7 +27,6 @@ import { ProcessDataService } from '../../core/data/processes/process-data.servi
|
|||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
import {
|
import {
|
||||||
createFailedRemoteDataObject$,
|
createFailedRemoteDataObject$,
|
||||||
createSuccessfulRemoteDataObject,
|
|
||||||
createSuccessfulRemoteDataObject$
|
createSuccessfulRemoteDataObject$
|
||||||
} from '../../shared/remote-data.utils';
|
} from '../../shared/remote-data.utils';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
@@ -35,7 +34,10 @@ import { NotificationsServiceStub } from '../../shared/testing/notifications-ser
|
|||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { getProcessListRoute } from '../process-page-routing.paths';
|
import { getProcessListRoute } from '../process-page-routing.paths';
|
||||||
import {ProcessStatus} from '../processes/process-status.model';
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { RouterStub } from '../../shared/testing/router.stub';
|
||||||
|
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||||
|
|
||||||
describe('ProcessDetailComponent', () => {
|
describe('ProcessDetailComponent', () => {
|
||||||
let component: ProcessDetailComponent;
|
let component: ProcessDetailComponent;
|
||||||
@@ -45,44 +47,18 @@ describe('ProcessDetailComponent', () => {
|
|||||||
let nameService: DSONameService;
|
let nameService: DSONameService;
|
||||||
let bitstreamDataService: BitstreamDataService;
|
let bitstreamDataService: BitstreamDataService;
|
||||||
let httpClient: HttpClient;
|
let httpClient: HttpClient;
|
||||||
let route: ActivatedRoute;
|
let route: ActivatedRouteStub;
|
||||||
|
let router: RouterStub;
|
||||||
|
let modalService;
|
||||||
|
let notificationsService: NotificationsServiceStub;
|
||||||
|
|
||||||
let process: Process;
|
let process: Process;
|
||||||
let fileName: string;
|
let fileName: string;
|
||||||
let files: Bitstream[];
|
let files: Bitstream[];
|
||||||
|
|
||||||
let processOutput;
|
let processOutput: string;
|
||||||
|
|
||||||
let modalService;
|
|
||||||
let notificationsService;
|
|
||||||
|
|
||||||
let router;
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
processOutput = 'Process Started';
|
|
||||||
process = Object.assign(new Process(), {
|
|
||||||
processId: 1,
|
|
||||||
scriptName: 'script-name',
|
|
||||||
processStatus: 'COMPLETED',
|
|
||||||
parameters: [
|
|
||||||
{
|
|
||||||
name: '-f',
|
|
||||||
value: 'file.xml'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '-i',
|
|
||||||
value: 'identifier'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
_links: {
|
|
||||||
self: {
|
|
||||||
href: 'https://rest.api/processes/1'
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
href: 'https://rest.api/processes/1/output'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fileName = 'fake-file-name';
|
fileName = 'fake-file-name';
|
||||||
files = [
|
files = [
|
||||||
Object.assign(new Bitstream(), {
|
Object.assign(new Bitstream(), {
|
||||||
@@ -100,6 +76,33 @@ describe('ProcessDetailComponent', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
processOutput = 'Process Started';
|
||||||
|
process = Object.assign(new Process(), {
|
||||||
|
processId: 1,
|
||||||
|
scriptName: 'script-name',
|
||||||
|
processStatus: 'COMPLETED',
|
||||||
|
parameters: [
|
||||||
|
{
|
||||||
|
name: '-f',
|
||||||
|
value: 'file.xml'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '-i',
|
||||||
|
value: 'identifier'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
files: createSuccessfulRemoteDataObject$(Object.assign(new PaginatedList(), {
|
||||||
|
page: files,
|
||||||
|
})),
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'https://rest.api/processes/1'
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
href: 'https://rest.api/processes/1/output'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
const logBitstream = Object.assign(new Bitstream(), {
|
const logBitstream = Object.assign(new Bitstream(), {
|
||||||
id: 'output.log',
|
id: 'output.log',
|
||||||
_links: {
|
_links: {
|
||||||
@@ -110,6 +113,7 @@ describe('ProcessDetailComponent', () => {
|
|||||||
getFiles: createSuccessfulRemoteDataObject$(createPaginatedList(files)),
|
getFiles: createSuccessfulRemoteDataObject$(createPaginatedList(files)),
|
||||||
delete: createSuccessfulRemoteDataObject$(null),
|
delete: createSuccessfulRemoteDataObject$(null),
|
||||||
findById: createSuccessfulRemoteDataObject$(process),
|
findById: createSuccessfulRemoteDataObject$(process),
|
||||||
|
autoRefreshUntilCompletion: createSuccessfulRemoteDataObject$(process)
|
||||||
});
|
});
|
||||||
bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', {
|
bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', {
|
||||||
findByHref: createSuccessfulRemoteDataObject$(logBitstream)
|
findByHref: createSuccessfulRemoteDataObject$(logBitstream)
|
||||||
@@ -127,28 +131,22 @@ describe('ProcessDetailComponent', () => {
|
|||||||
|
|
||||||
notificationsService = new NotificationsServiceStub();
|
notificationsService = new NotificationsServiceStub();
|
||||||
|
|
||||||
router = jasmine.createSpyObj('router', {
|
router = new RouterStub();
|
||||||
navigateByUrl:{}
|
|
||||||
});
|
|
||||||
|
|
||||||
route = jasmine.createSpyObj('route', {
|
route = new ActivatedRouteStub({
|
||||||
data: observableOf({ process: createSuccessfulRemoteDataObject(process) }),
|
id: process.processId,
|
||||||
snapshot: {
|
}, {
|
||||||
params: { id: process.processId }
|
process: createSuccessfulRemoteDataObject$(process),
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule({
|
void TestBed.configureTestingModule({
|
||||||
declarations: [ProcessDetailComponent, ProcessDetailFieldComponent, VarDirective, FileSizePipe],
|
declarations: [ProcessDetailComponent, ProcessDetailFieldComponent, VarDirective, FileSizePipe],
|
||||||
imports: [TranslateModule.forRoot()],
|
imports: [TranslateModule.forRoot(), RouterTestingModule],
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{ provide: ActivatedRoute, useValue: route },
|
||||||
provide: ActivatedRoute,
|
|
||||||
useValue: { data: observableOf({ process: createSuccessfulRemoteDataObject(process) }), snapshot: { params: { id: 1 } } },
|
|
||||||
},
|
|
||||||
{ provide: ProcessDataService, useValue: processService },
|
{ provide: ProcessDataService, useValue: processService },
|
||||||
{ provide: BitstreamDataService, useValue: bitstreamDataService },
|
{ provide: BitstreamDataService, useValue: bitstreamDataService },
|
||||||
{ provide: DSONameService, useValue: nameService },
|
{ provide: DSONameService, useValue: nameService },
|
||||||
@@ -253,6 +251,8 @@ describe('ProcessDetailComponent', () => {
|
|||||||
describe('deleteProcess', () => {
|
describe('deleteProcess', () => {
|
||||||
it('should delete the process and navigate back to the overview page on success', () => {
|
it('should delete the process and navigate back to the overview page on success', () => {
|
||||||
spyOn(component, 'closeModal');
|
spyOn(component, 'closeModal');
|
||||||
|
spyOn(router, 'navigateByUrl').and.callThrough();
|
||||||
|
|
||||||
component.deleteProcess(process);
|
component.deleteProcess(process);
|
||||||
|
|
||||||
expect(processService.delete).toHaveBeenCalledWith(process.processId);
|
expect(processService.delete).toHaveBeenCalledWith(process.processId);
|
||||||
@@ -263,6 +263,7 @@ describe('ProcessDetailComponent', () => {
|
|||||||
it('should delete the process and not navigate on error', () => {
|
it('should delete the process and not navigate on error', () => {
|
||||||
(processService.delete as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$());
|
(processService.delete as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$());
|
||||||
spyOn(component, 'closeModal');
|
spyOn(component, 'closeModal');
|
||||||
|
spyOn(router, 'navigateByUrl').and.callThrough();
|
||||||
|
|
||||||
component.deleteProcess(process);
|
component.deleteProcess(process);
|
||||||
|
|
||||||
@@ -272,98 +273,4 @@ describe('ProcessDetailComponent', () => {
|
|||||||
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('refresh counter', () => {
|
|
||||||
const queryRefreshCounter = () => fixture.debugElement.query(By.css('.refresh-counter'));
|
|
||||||
|
|
||||||
describe('if process is completed', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
process.processStatus = ProcessStatus.COMPLETED;
|
|
||||||
route.data = observableOf({process: createSuccessfulRemoteDataObject(process)});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not show', () => {
|
|
||||||
spyOn(component, 'startRefreshTimer');
|
|
||||||
|
|
||||||
const refreshCounter = queryRefreshCounter();
|
|
||||||
expect(refreshCounter).toBeNull();
|
|
||||||
|
|
||||||
expect(component.startRefreshTimer).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('if process is not finished', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
process.processStatus = ProcessStatus.RUNNING;
|
|
||||||
route.data = observableOf({process: createSuccessfulRemoteDataObject(process)});
|
|
||||||
fixture.detectChanges();
|
|
||||||
component.stopRefreshTimer();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call startRefreshTimer', () => {
|
|
||||||
spyOn(component, 'startRefreshTimer');
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges(); // subscribe to process observable with async pipe
|
|
||||||
|
|
||||||
expect(component.startRefreshTimer).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call refresh method every 5 seconds, until process is completed', fakeAsync(() => {
|
|
||||||
spyOn(component, 'refresh').and.callThrough();
|
|
||||||
spyOn(component, 'stopRefreshTimer').and.callThrough();
|
|
||||||
|
|
||||||
// start off with a running process in order for the refresh counter starts counting up
|
|
||||||
process.processStatus = ProcessStatus.RUNNING;
|
|
||||||
// set findbyId to return a completed process
|
|
||||||
(processService.findById as jasmine.Spy).and.returnValue(observableOf(createSuccessfulRemoteDataObject(process)));
|
|
||||||
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges(); // subscribe to process observable with async pipe
|
|
||||||
|
|
||||||
expect(component.refresh).not.toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(component.refreshCounter$.value).toBe(0);
|
|
||||||
|
|
||||||
tick(1001); // 1 second + 1 ms by the setTimeout
|
|
||||||
expect(component.refreshCounter$.value).toBe(5); // 5 - 0
|
|
||||||
|
|
||||||
tick(2001); // 2 seconds + 1 ms by the setTimeout
|
|
||||||
expect(component.refreshCounter$.value).toBe(3); // 5 - 2
|
|
||||||
|
|
||||||
tick(2001); // 2 seconds + 1 ms by the setTimeout
|
|
||||||
expect(component.refreshCounter$.value).toBe(1); // 3 - 2
|
|
||||||
|
|
||||||
tick(1001); // 1 second + 1 ms by the setTimeout
|
|
||||||
expect(component.refreshCounter$.value).toBe(0); // 1 - 1
|
|
||||||
|
|
||||||
// set the process to completed right before the counter checks the process
|
|
||||||
process.processStatus = ProcessStatus.COMPLETED;
|
|
||||||
(processService.findById as jasmine.Spy).and.returnValue(observableOf(createSuccessfulRemoteDataObject(process)));
|
|
||||||
|
|
||||||
tick(1000); // 1 second
|
|
||||||
|
|
||||||
expect(component.refresh).toHaveBeenCalledTimes(1);
|
|
||||||
expect(component.stopRefreshTimer).toHaveBeenCalled();
|
|
||||||
|
|
||||||
expect(component.refreshCounter$.value).toBe(0);
|
|
||||||
|
|
||||||
tick(1001); // 1 second + 1 ms by the setTimeout
|
|
||||||
// startRefreshTimer not called again
|
|
||||||
expect(component.refreshCounter$.value).toBe(0);
|
|
||||||
|
|
||||||
discardPeriodicTasks(); // discard any periodic tasks that have not yet executed
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should show if refreshCounter is different from 0', () => {
|
|
||||||
component.refreshCounter$.next(1);
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
const refreshCounter = queryRefreshCounter();
|
|
||||||
expect(refreshCounter).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Component, Inject, NgZone, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
import { Component, Inject, NgZone, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { BehaviorSubject, interval, Observable, shareReplay, Subscription } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { finalize, map, switchMap, take, tap } from 'rxjs/operators';
|
import { finalize, map, switchMap, take, tap, find, startWith, filter } from 'rxjs/operators';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
@@ -14,7 +14,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
|||||||
import {
|
import {
|
||||||
getFirstCompletedRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteData,
|
getFirstSucceededRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload
|
getFirstSucceededRemoteDataPayload, getAllSucceededRemoteDataPayload
|
||||||
} from '../../core/shared/operators';
|
} from '../../core/shared/operators';
|
||||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||||
import { AlertType } from '../../shared/alert/alert-type';
|
import { AlertType } from '../../shared/alert/alert-type';
|
||||||
@@ -26,8 +26,8 @@ import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { getProcessListRoute } from '../process-page-routing.paths';
|
import { getProcessListRoute } from '../process-page-routing.paths';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
|
import { PROCESS_PAGE_FOLLOW_LINKS } from '../process-page.resolver';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-process-detail',
|
selector: 'ds-process-detail',
|
||||||
@@ -36,7 +36,7 @@ import { isPlatformBrowser } from '@angular/common';
|
|||||||
/**
|
/**
|
||||||
* A component displaying detailed information about a DSpace Process
|
* A component displaying detailed information about a DSpace Process
|
||||||
*/
|
*/
|
||||||
export class ProcessDetailComponent implements OnInit, OnDestroy {
|
export class ProcessDetailComponent implements OnInit {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The AlertType enumeration
|
* The AlertType enumeration
|
||||||
@@ -78,15 +78,15 @@ export class ProcessDetailComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
dateFormat = 'yyyy-MM-dd HH:mm:ss ZZZZ';
|
dateFormat = 'yyyy-MM-dd HH:mm:ss ZZZZ';
|
||||||
|
|
||||||
refreshCounter$ = new BehaviorSubject(0);
|
isRefreshing$: Observable<boolean>;
|
||||||
|
|
||||||
|
isDeleting: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to NgbModal
|
* Reference to NgbModal
|
||||||
*/
|
*/
|
||||||
protected modalRef: NgbModalRef;
|
protected modalRef: NgbModalRef;
|
||||||
|
|
||||||
private refreshTimerSub?: Subscription;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(PLATFORM_ID) protected platformId: object,
|
@Inject(PLATFORM_ID) protected platformId: object,
|
||||||
protected route: ActivatedRoute,
|
protected route: ActivatedRoute,
|
||||||
@@ -108,70 +108,27 @@ export class ProcessDetailComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.processRD$ = this.route.data.pipe(
|
this.processRD$ = this.route.data.pipe(
|
||||||
map((data) => {
|
switchMap((data) => {
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
if (!this.isProcessFinished(data.process.payload)) {
|
return this.processService.autoRefreshUntilCompletion(this.route.snapshot.params.id, 5000, ...PROCESS_PAGE_FOLLOW_LINKS);
|
||||||
this.startRefreshTimer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.process as RemoteData<Process>;
|
|
||||||
}),
|
|
||||||
redirectOn4xx(this.router, this.authService),
|
|
||||||
shareReplay(1)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.filesRD$ = this.processRD$.pipe(
|
|
||||||
getFirstSucceededRemoteDataPayload(),
|
|
||||||
switchMap((process: Process) => this.processService.getFiles(process.processId))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh() {
|
|
||||||
this.processRD$ = this.processService.findById(
|
|
||||||
this.route.snapshot.params.id,
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
followLink('script')
|
|
||||||
).pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
redirectOn4xx(this.router, this.authService),
|
|
||||||
tap((processRemoteData: RemoteData<Process>) => {
|
|
||||||
if (!this.isProcessFinished(processRemoteData.payload)) {
|
|
||||||
this.startRefreshTimer();
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
shareReplay(1)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.filesRD$ = this.processRD$.pipe(
|
|
||||||
getFirstSucceededRemoteDataPayload(),
|
|
||||||
switchMap((process: Process) => this.processService.getFiles(process.processId))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
startRefreshTimer() {
|
|
||||||
this.refreshCounter$.next(0);
|
|
||||||
|
|
||||||
this.refreshTimerSub = interval(1000).subscribe(
|
|
||||||
value => {
|
|
||||||
if (value > 5) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.refresh();
|
|
||||||
this.stopRefreshTimer();
|
|
||||||
this.refreshCounter$.next(0);
|
|
||||||
}, 1);
|
|
||||||
} else {
|
} else {
|
||||||
this.refreshCounter$.next(5 - value);
|
return [data.process as RemoteData<Process>];
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
filter(() => !this.isDeleting),
|
||||||
|
redirectOn4xx(this.router, this.authService),
|
||||||
|
);
|
||||||
|
|
||||||
stopRefreshTimer() {
|
this.isRefreshing$ = this.processRD$.pipe(
|
||||||
if (hasValue(this.refreshTimerSub)) {
|
find((processRD: RemoteData<Process>) => ProcessDataService.hasCompletedOrFailed(processRD.payload)),
|
||||||
this.refreshTimerSub.unsubscribe();
|
map(() => false),
|
||||||
this.refreshTimerSub = undefined;
|
startWith(true)
|
||||||
}
|
);
|
||||||
|
|
||||||
|
this.filesRD$ = this.processRD$.pipe(
|
||||||
|
getAllSucceededRemoteDataPayload(),
|
||||||
|
switchMap((process: Process) => process.files),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -249,15 +206,17 @@ export class ProcessDetailComponent implements OnInit, OnDestroy {
|
|||||||
* @param process
|
* @param process
|
||||||
*/
|
*/
|
||||||
deleteProcess(process: Process) {
|
deleteProcess(process: Process) {
|
||||||
|
this.isDeleting = true;
|
||||||
this.processService.delete(process.processId).pipe(
|
this.processService.delete(process.processId).pipe(
|
||||||
getFirstCompletedRemoteData()
|
getFirstCompletedRemoteData()
|
||||||
).subscribe((rd) => {
|
).subscribe((rd) => {
|
||||||
if (rd.hasSucceeded) {
|
if (rd.hasSucceeded) {
|
||||||
this.notificationsService.success(this.translateService.get('process.detail.delete.success'));
|
this.notificationsService.success(this.translateService.get('process.detail.delete.success'));
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
this.router.navigateByUrl(getProcessListRoute());
|
void this.router.navigateByUrl(getProcessListRoute());
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error(this.translateService.get('process.detail.delete.error'));
|
this.notificationsService.error(this.translateService.get('process.detail.delete.error'));
|
||||||
|
this.isDeleting = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -276,8 +235,4 @@ export class ProcessDetailComponent implements OnInit, OnDestroy {
|
|||||||
closeModal() {
|
closeModal() {
|
||||||
this.modalRef.close();
|
this.modalRef.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.stopRefreshTimer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -6,8 +6,9 @@ import { Process } from './processes/process.model';
|
|||||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||||
import { ProcessDataService } from '../core/data/processes/process-data.service';
|
import { ProcessDataService } from '../core/data/processes/process-data.service';
|
||||||
import { BreadcrumbConfig } from '../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
import { BreadcrumbConfig } from '../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||||
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { ProcessBreadcrumbsService } from './process-breadcrumbs.service';
|
import { ProcessBreadcrumbsService } from './process-breadcrumbs.service';
|
||||||
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific process before the route is activated
|
* This class represents a resolver that requests a specific process before the route is activated
|
||||||
@@ -28,12 +29,11 @@ export class ProcessBreadcrumbResolver implements Resolve<BreadcrumbConfig<Proce
|
|||||||
const id = route.params.id;
|
const id = route.params.id;
|
||||||
|
|
||||||
return this.processService.findById(route.params.id, true, false, followLink('script')).pipe(
|
return this.processService.findById(route.params.id, true, false, followLink('script')).pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
getRemoteDataPayload(),
|
map((object: RemoteData<Process>) => {
|
||||||
map((object: Process) => {
|
|
||||||
const fullPath = state.url;
|
const fullPath = state.url;
|
||||||
const url = fullPath.substr(0, fullPath.indexOf(id)) + id;
|
const url = fullPath.substr(0, fullPath.indexOf(id)) + id;
|
||||||
return { provider: this.breadcrumbService, key: object, url: url };
|
return { provider: this.breadcrumbService, key: object.payload, url: url };
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { BreadcrumbsProviderService } from '../core/breadcrumbs/breadcrumbsProviderService';
|
import { BreadcrumbsProviderService } from '../core/breadcrumbs/breadcrumbsProviderService';
|
||||||
import { Breadcrumb } from '../breadcrumbs/breadcrumb/breadcrumb.model';
|
import { Breadcrumb } from '../breadcrumbs/breadcrumb/breadcrumb.model';
|
||||||
import { Process } from './processes/process.model';
|
import { Process } from './processes/process.model';
|
||||||
|
import { hasValue } from '../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to calculate process breadcrumbs for a single part of the route
|
* Service to calculate process breadcrumbs for a single part of the route
|
||||||
@@ -16,6 +17,10 @@ export class ProcessBreadcrumbsService implements BreadcrumbsProviderService<Pro
|
|||||||
* @param url The url to use as a link for this breadcrumb
|
* @param url The url to use as a link for this breadcrumb
|
||||||
*/
|
*/
|
||||||
getBreadcrumbs(key: Process, url: string): Observable<Breadcrumb[]> {
|
getBreadcrumbs(key: Process, url: string): Observable<Breadcrumb[]> {
|
||||||
|
if (hasValue(key)) {
|
||||||
return observableOf([new Breadcrumb(key.processId + ' - ' + key.scriptName, url)]);
|
return observableOf([new Breadcrumb(key.processId + ' - ' + key.scriptName, url)]);
|
||||||
|
} else {
|
||||||
|
return observableOf([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,10 @@ import { followLink } from '../shared/utils/follow-link-config.model';
|
|||||||
import { ProcessDataService } from '../core/data/processes/process-data.service';
|
import { ProcessDataService } from '../core/data/processes/process-data.service';
|
||||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
|
|
||||||
|
export const PROCESS_PAGE_FOLLOW_LINKS = [
|
||||||
|
followLink('files'),
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific process before the route is activated
|
* This class represents a resolver that requests a specific process before the route is activated
|
||||||
*/
|
*/
|
||||||
@@ -23,7 +27,7 @@ export class ProcessPageResolver implements Resolve<RemoteData<Process>> {
|
|||||||
* or an error if something went wrong
|
* or an error if something went wrong
|
||||||
*/
|
*/
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Process>> {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Process>> {
|
||||||
return this.processService.findById(route.params.id, false, true, followLink('script')).pipe(
|
return this.processService.findById(route.params.id, false, true, ...PROCESS_PAGE_FOLLOW_LINKS).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
34
src/app/process-page/processes/filetypes.model.ts
Normal file
34
src/app/process-page/processes/filetypes.model.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { typedObject } from '../../core/cache/builders/build-decorators';
|
||||||
|
import { excludeFromEquals } from '../../core/utilities/equals.decorators';
|
||||||
|
import { autoserialize } from 'cerialize';
|
||||||
|
import { ResourceType } from '../../core/shared/resource-type';
|
||||||
|
import { FILETYPES } from './filetypes.resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object representing the file types of the {@link Bitstream}s of a {@link Process}
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
export class Filetypes {
|
||||||
|
|
||||||
|
static type = FILETYPES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of this {@link Filetypes}
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The values of this {@link Filetypes}
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
values: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object type
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
@autoserialize
|
||||||
|
type: ResourceType;
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* The resource type for {@link Filetypes}
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular dependencies in webpack.
|
||||||
|
*/
|
||||||
|
import { ResourceType } from '../../core/shared/resource-type';
|
||||||
|
|
||||||
|
export const FILETYPES = new ResourceType('filetypes');
|
@@ -2,8 +2,8 @@
|
|||||||
* List of process statuses
|
* List of process statuses
|
||||||
*/
|
*/
|
||||||
export enum ProcessStatus {
|
export enum ProcessStatus {
|
||||||
SCHEDULED,
|
SCHEDULED = 'SCHEDULED',
|
||||||
RUNNING,
|
RUNNING = 'RUNNING',
|
||||||
COMPLETED,
|
COMPLETED = 'COMPLETED',
|
||||||
FAILED
|
FAILED = 'FAILED'
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,10 @@ import { RemoteData } from '../../core/data/remote-data';
|
|||||||
import { SCRIPT } from '../scripts/script.resource-type';
|
import { SCRIPT } from '../scripts/script.resource-type';
|
||||||
import { Script } from '../scripts/script.model';
|
import { Script } from '../scripts/script.model';
|
||||||
import { CacheableObject } from '../../core/cache/cacheable-object.model';
|
import { CacheableObject } from '../../core/cache/cacheable-object.model';
|
||||||
|
import { BITSTREAM } from '../../core/shared/bitstream.resource-type';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
|
import { Filetypes } from './filetypes.model';
|
||||||
|
import { FILETYPES } from './filetypes.resource-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object representing a process
|
* Object representing a process
|
||||||
@@ -78,7 +82,8 @@ export class Process implements CacheableObject {
|
|||||||
self: HALLink,
|
self: HALLink,
|
||||||
script: HALLink,
|
script: HALLink,
|
||||||
output: HALLink,
|
output: HALLink,
|
||||||
files: HALLink
|
files: HALLink,
|
||||||
|
filetypes: HALLink,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,4 +99,19 @@ export class Process implements CacheableObject {
|
|||||||
*/
|
*/
|
||||||
@link(PROCESS_OUTPUT_TYPE)
|
@link(PROCESS_OUTPUT_TYPE)
|
||||||
output?: Observable<RemoteData<Bitstream>>;
|
output?: Observable<RemoteData<Bitstream>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The files created by this Process
|
||||||
|
* Will be undefined unless the output {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(BITSTREAM, true)
|
||||||
|
files?: Observable<RemoteData<PaginatedList<Bitstream>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filetypes present in this Process
|
||||||
|
* Will be undefined unless the output {@link HALLink} has been resolved.
|
||||||
|
*/
|
||||||
|
@link(FILETYPES)
|
||||||
|
filetypes?: Observable<RemoteData<Filetypes>>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
31
src/app/shared/testing/object-cache-service.stub.ts
Normal file
31
src/app/shared/testing/object-cache-service.stub.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { CacheableObject } from '../../core/cache/cacheable-object.model';
|
||||||
|
import { ObjectCacheEntry } from '../../core/cache/object-cache.reducer';
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
|
/**
|
||||||
|
* Stub class of {@link ObjectCacheService}
|
||||||
|
*/
|
||||||
|
export class ObjectCacheServiceStub {
|
||||||
|
|
||||||
|
add(_object: CacheableObject, _msToLive: number, _requestUUID: string, _alternativeLink?: string): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(_href: string): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
getByHref(_href: string): Observable<ObjectCacheEntry> {
|
||||||
|
return observableOf(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
hasByHref$(_href: string): Observable<boolean> {
|
||||||
|
return observableOf(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
addDependency(_href$: string | Observable<string>, _dependsOnHref$: string | Observable<string>): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
removeDependents(_href: string): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -3689,6 +3689,8 @@
|
|||||||
|
|
||||||
"process.detail.delete.error": "Something went wrong when deleting the process",
|
"process.detail.delete.error": "Something went wrong when deleting the process",
|
||||||
|
|
||||||
|
"process.detail.refreshing": "Auto-refreshing…",
|
||||||
|
|
||||||
"process.overview.table.finish": "Finish time (UTC)",
|
"process.overview.table.finish": "Finish time (UTC)",
|
||||||
|
|
||||||
"process.overview.table.id": "Process ID",
|
"process.overview.table.id": "Process ID",
|
||||||
|
Reference in New Issue
Block a user