Skip to content

Commit

Permalink
Merge branch 'feature-process_polling-7.6' into feature-process_polling
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/app/core/data/processes/process-data.service.ts
  • Loading branch information
alexandrevryghem committed Dec 22, 2023
2 parents 11e98f7 + 5400981 commit f35eab3
Show file tree
Hide file tree
Showing 16 changed files with 355 additions and 175 deletions.
196 changes: 126 additions & 70 deletions src/app/core/data/base/base-data.service.spec.ts

Large diffs are not rendered by default.

41 changes: 39 additions & 2 deletions src/app/core/data/base/base-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { ObjectCacheEntry } from '../../cache/object-cache.reducer';
import { ObjectCacheService } from '../../cache/object-cache.service';
import { HALDataService } from './hal-data-service.interface';
import { getFirstCompletedRemoteData } from '../../shared/operators';
import { HALLink } from '../../shared/hal-link.model';

export const EMBED_SEPARATOR = '%2F';
/**
Expand Down Expand Up @@ -268,7 +269,7 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic

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
// 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
Expand All @@ -277,6 +278,22 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
this.reRequestStaleRemoteData(reRequestOnStale, () =>
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 followLink of Object.values(remoteDataObject.payload._links)) {
// followLink can be either an individual HALLink or a HALLink[]
const followLinksList: HALLink[] = [].concat(followLink);
for (const individualFollowLink of followLinksList) {
if (hasValue(individualFollowLink?.href)) {
this.addDependency(response$, individualFollowLink.href);
}
}
}
}
}),
);
}

/**
Expand All @@ -302,7 +319,7 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic

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
// 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
Expand All @@ -311,6 +328,26 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
this.reRequestStaleRemoteData(reRequestOnStale, () =>
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 followLink of Object.values(object._links)) {
// followLink can be either an individual HALLink or a HALLink[]
const followLinksList: HALLink[] = [].concat(followLink);
for (const individualFollowLink of followLinksList) {
if (hasValue(individualFollowLink?.href)) {
this.addDependency(response$, individualFollowLink.href);
}
}
}
}
}
}
}),
);
}

/**
Expand Down
9 changes: 4 additions & 5 deletions src/app/core/data/collection-data.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { testFindAllDataImplementation } from './base/find-all-data.spec';
import { testSearchDataImplementation } from './base/search-data.spec';
import { testPatchDataImplementation } from './base/patch-data.spec';
import { testDeleteDataImplementation } from './base/delete-data.spec';
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';

const url = 'fake-url';
const collectionId = 'fake-collection-id';
Expand All @@ -35,7 +36,7 @@ describe('CollectionDataService', () => {
let translate: TranslateService;
let notificationsService: any;
let rdbService: RemoteDataBuildService;
let objectCache: ObjectCacheService;
let objectCache: ObjectCacheServiceStub;
let halService: any;

const mockCollection1: Collection = Object.assign(new Collection(), {
Expand Down Expand Up @@ -205,14 +206,12 @@ describe('CollectionDataService', () => {
buildFromRequestUUID: buildResponse$,
buildSingle: buildResponse$
});
objectCache = jasmine.createSpyObj('objectCache', {
remove: jasmine.createSpy('remove')
});
objectCache = new ObjectCacheServiceStub();
halService = new HALEndpointServiceStub(url);
notificationsService = new NotificationsServiceStub();
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);
}

});
46 changes: 25 additions & 21 deletions src/app/core/data/processes/process-data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Bitstream } from '../../shared/bitstream.model';
import { RemoteData } from '../remote-data';
import { BitstreamDataService } from '../bitstream-data.service';
import { IdentifiableDataService } from '../base/identifiable-data.service';
import { FollowLinkConfig, followLink } from '../../../shared/utils/follow-link-config.model';
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
import { FindAllData, FindAllDataImpl } from '../base/find-all-data';
import { FindListOptions } from '../find-list-options.model';
import { dataService } from '../base/data-service.decorator';
Expand All @@ -35,26 +35,11 @@ export const TIMER_FACTORY = new InjectionToken<(callback: (...args: any[]) => v
@Injectable()
@dataService(PROCESS)
export class ProcessDataService extends IdentifiableDataService<Process> implements FindAllData<Process>, DeleteData<Process> {

private findAllData: FindAllData<Process>;
private deleteData: DeleteData<Process>;
protected activelyBeingPolled: Map<string, NodeJS.Timeout> = new Map();

/**
* 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);
}

constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
Expand All @@ -71,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);
}

/**
* 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
* @param processId The ID of the process
Expand Down Expand Up @@ -153,11 +154,14 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
* 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
* @param pollingIntervalInMs
*
* @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): Observable<RemoteData<Process>> {
const process$ = this.findById(processId, true, true, followLink('script'))
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(),
);
Expand Down
12 changes: 3 additions & 9 deletions src/app/core/data/relationship-data.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { FindListOptions } from './find-list-options.model';
import { testSearchDataImplementation } from './base/search-data.spec';
import { MetadataValue } from '../shared/metadata.models';
import { MetadataRepresentationType } from '../shared/metadata-representation/metadata-representation.model';
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';

describe('RelationshipDataService', () => {
let service: RelationshipDataService;
Expand Down Expand Up @@ -114,14 +115,7 @@ describe('RelationshipDataService', () => {
'href': buildList$,
'https://rest.api/core/publication/relationships': relationships$
});
const objectCache = Object.assign({
/* 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 objectCache = new ObjectCacheServiceStub();

const itemService = jasmine.createSpyObj('itemService', {
findById: (uuid) => createSuccessfulRemoteDataObject(relatedItems.find((relatedItem) => relatedItem.id === uuid)),
Expand All @@ -133,7 +127,7 @@ describe('RelationshipDataService', () => {
requestService,
rdbService,
halService,
objectCache,
objectCache as ObjectCacheService,
itemService,
null,
jasmine.createSpy('paginatedRelationsToItems').and.returnValue((v) => v),
Expand Down
14 changes: 4 additions & 10 deletions src/app/core/data/relationship-type-data.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { RequestService } from './request.service';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { hasValueOperator } from '../../shared/empty.util';
import { ObjectCacheService } from '../cache/object-cache.service';
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';

describe('RelationshipTypeDataService', () => {
let service: RelationshipTypeDataService;
Expand All @@ -28,7 +29,7 @@ describe('RelationshipTypeDataService', () => {

let buildList;
let rdbService;
let objectCache;
let objectCache: ObjectCacheServiceStub;

function init() {
restEndpointURL = 'https://rest.api/relationshiptypes';
Expand Down Expand Up @@ -60,21 +61,14 @@ describe('RelationshipTypeDataService', () => {

buildList = createSuccessfulRemoteDataObject(createPaginatedList([relationshipType1, relationshipType2]));
rdbService = getMockRemoteDataBuildService(undefined, observableOf(buildList));
objectCache = Object.assign({
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */
remove: () => {
},
hasBySelfLinkObservable: () => observableOf(false)
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
}) as ObjectCacheService;

objectCache = new ObjectCacheServiceStub();
}

function initTestService() {
return new RelationshipTypeDataService(
requestService,
rdbService,
objectCache,
objectCache as ObjectCacheService,
halService,
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ import { FindListOptions } from '../data/find-list-options.model';
import { EPersonDataService } from '../eperson/eperson-data.service';
import { GroupDataService } from '../eperson/group-data.service';
import { RestRequestMethod } from '../data/rest-request-method';
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';

describe('ResourcePolicyService', () => {
let scheduler: TestScheduler;
let service: ResourcePolicyDataService;
let requestService: RequestService;
let rdbService: RemoteDataBuildService;
let objectCache: ObjectCacheService;
let objectCache: ObjectCacheServiceStub;
let halService: HALEndpointService;
let responseCacheEntry: RequestEntry;
let ePersonService: EPersonDataService;
Expand Down Expand Up @@ -139,14 +140,14 @@ describe('ResourcePolicyService', () => {
a: 'https://rest.api/rest/api/eperson/groups/' + groupUUID
}),
});
objectCache = {} as ObjectCacheService;
objectCache = new ObjectCacheServiceStub();
const notificationsService = {} as NotificationsService;
const comparator = {} as any;

service = new ResourcePolicyDataService(
requestService,
rdbService,
objectCache,
objectCache as ObjectCacheService,
halService,
notificationsService,
comparator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { createPaginatedList } from '../../../shared/testing/utils.test';
import { RequestEntry } from '../../data/request-entry.model';
import { VocabularyDataService } from './vocabulary.data.service';
import { VocabularyEntryDetailsDataService } from './vocabulary-entry-details.data.service';
import { ObjectCacheServiceStub } from '../../../shared/testing/object-cache-service.stub';

describe('VocabularyService', () => {
let scheduler: TestScheduler;
Expand Down Expand Up @@ -205,6 +206,7 @@ describe('VocabularyService', () => {

function initTestService() {
hrefOnlyDataService = getMockHrefOnlyDataService();
objectCache = new ObjectCacheServiceStub() as ObjectCacheService;

return new VocabularyService(
requestService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ import { RestResponse } from '../cache/response.models';
import { RequestEntry } from '../data/request-entry.model';
import { FindListOptions } from '../data/find-list-options.model';
import { GroupDataService } from '../eperson/group-data.service';
import { ObjectCacheServiceStub } from '../../shared/testing/object-cache-service.stub';

describe('SupervisionOrderService', () => {
let scheduler: TestScheduler;
let service: SupervisionOrderDataService;
let requestService: RequestService;
let rdbService: RemoteDataBuildService;
let objectCache: ObjectCacheService;
let objectCache: ObjectCacheServiceStub;
let halService: HALEndpointService;
let responseCacheEntry: RequestEntry;
let groupService: GroupDataService;
Expand Down Expand Up @@ -127,14 +128,14 @@ describe('SupervisionOrderService', () => {
a: 'https://rest.api/rest/api/group/groups/' + groupUUID
}),
});
objectCache = {} as ObjectCacheService;
objectCache = new ObjectCacheServiceStub();
const notificationsService = {} as NotificationsService;
const comparator = {} as any;

service = new SupervisionOrderDataService(
requestService,
rdbService,
objectCache,
objectCache as ObjectCacheService,
halService,
notificationsService,
comparator,
Expand Down
Loading

0 comments on commit f35eab3

Please sign in to comment.