Skip to content

Commit 5ef168b

Browse files
committed
Merge branch 'w2p-111638_Process-admin-UI-redesign_Overview-page-tables_PR-feedback' into process-admin-ui-redesign-8.0.0-next
2 parents 4e195b6 + f887997 commit 5ef168b

7 files changed

Lines changed: 169 additions & 78 deletions

File tree

src/app/collection-page/edit-collection-page/collection-source/collection-source-controls/collection-source-controls.component.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, Input, OnDestroy } from '@angular/core';
1+
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
22
import { ScriptDataService } from '../../../../core/data/processes/script-data.service';
33
import { ContentSource } from '../../../../core/shared/content-source.model';
44
import { ProcessDataService } from '../../../../core/data/processes/process-data.service';
@@ -29,7 +29,7 @@ import { ContentSourceSetSerializer } from '../../../../core/shared/content-sour
2929
styleUrls: ['./collection-source-controls.component.scss'],
3030
templateUrl: './collection-source-controls.component.html',
3131
})
32-
export class CollectionSourceControlsComponent implements OnDestroy {
32+
export class CollectionSourceControlsComponent implements OnInit, OnDestroy {
3333

3434
/**
3535
* Should the controls be enabled.
@@ -48,6 +48,7 @@ export class CollectionSourceControlsComponent implements OnDestroy {
4848

4949
contentSource$: Observable<ContentSource>;
5050
private subs: Subscription[] = [];
51+
private autoRefreshIDs: string[] = [];
5152

5253
testConfigRunning$ = new BehaviorSubject(false);
5354
importRunning$ = new BehaviorSubject(false);
@@ -94,7 +95,10 @@ export class CollectionSourceControlsComponent implements OnDestroy {
9495
}),
9596
// filter out responses that aren't successful since the pinging of the process only needs to happen when the invocation was successful.
9697
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
97-
switchMap((rd) => this.processDataService.autoRefreshUntilCompletion(rd.payload.processId)),
98+
switchMap((rd) => {
99+
this.autoRefreshIDs.push(rd.payload.processId);
100+
return this.processDataService.autoRefreshUntilCompletion(rd.payload.processId);
101+
}),
98102
map((rd) => rd.payload)
99103
).subscribe((process: Process) => {
100104
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
@@ -135,7 +139,10 @@ export class CollectionSourceControlsComponent implements OnDestroy {
135139
}
136140
}),
137141
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
138-
switchMap((rd) => this.processDataService.autoRefreshUntilCompletion(rd.payload.processId)),
142+
switchMap((rd) => {
143+
this.autoRefreshIDs.push(rd.payload.processId);
144+
return this.processDataService.autoRefreshUntilCompletion(rd.payload.processId);
145+
}),
139146
map((rd) => rd.payload)
140147
).subscribe((process) => {
141148
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
@@ -170,7 +177,10 @@ export class CollectionSourceControlsComponent implements OnDestroy {
170177
}
171178
}),
172179
filter((rd) => rd.hasSucceeded && hasValue(rd.payload)),
173-
switchMap((rd) => this.processDataService.autoRefreshUntilCompletion(rd.payload.processId)),
180+
switchMap((rd) => {
181+
this.autoRefreshIDs.push(rd.payload.processId);
182+
return this.processDataService.autoRefreshUntilCompletion(rd.payload.processId);
183+
}),
174184
map((rd) => rd.payload)
175185
).subscribe((process) => {
176186
if (process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()) {
@@ -191,5 +201,9 @@ export class CollectionSourceControlsComponent implements OnDestroy {
191201
sub.unsubscribe();
192202
}
193203
});
204+
205+
this.autoRefreshIDs.forEach((id) => {
206+
this.processDataService.stopAutoRefreshing(id);
207+
});
194208
}
195209
}

src/app/core/data/processes/process-data.service.spec.ts

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import { testFindAllDataImplementation } from '../base/find-all-data.spec';
1010
import { ProcessDataService, TIMER_FACTORY } from './process-data.service';
1111
import { testDeleteDataImplementation } from '../base/delete-data.spec';
12-
import { waitForAsync, TestBed, fakeAsync, tick, flush } from '@angular/core/testing';
12+
import { waitForAsync, TestBed, fakeAsync, tick } from '@angular/core/testing';
1313
import { RequestService } from '../request.service';
1414
import { RemoteData } from '../remote-data';
1515
import { RequestEntryState } from '../request-entry-state.model';
@@ -27,6 +27,7 @@ import { testSearchDataImplementation } from '../base/search-data.spec';
2727
import { PaginatedList } from '../paginated-list.model';
2828
import { FindListOptions } from '../find-list-options.model';
2929
import { of } from 'rxjs';
30+
import { getMockRequestService } from '../../../shared/mocks/request.service.mock';
3031

3132
describe('ProcessDataService', () => {
3233
let testScheduler;
@@ -43,7 +44,7 @@ describe('ProcessDataService', () => {
4344
testSearchDataImplementation(initService);
4445
});
4546

46-
let requestService;
47+
let requestService = getMockRequestService();
4748
let processDataService;
4849
let remoteDataBuildService;
4950

@@ -136,7 +137,7 @@ describe('ProcessDataService', () => {
136137
imports: [],
137138
providers: [
138139
ProcessDataService,
139-
{ provide: RequestService, useValue: null },
140+
{ provide: RequestService, useValue: requestService },
140141
{ provide: RemoteDataBuildService, useValue: null },
141142
{ provide: ObjectCacheService, useValue: null },
142143
{ provide: ReducerManager, useValue: null },
@@ -152,41 +153,41 @@ describe('ProcessDataService', () => {
152153
}));
153154

154155
it('should refresh after the specified interval', fakeAsync(() => {
155-
const runningProcess = Object.assign(new Process(), {
156-
_links: {
157-
self: {
158-
href: 'https://rest.api/processes/123'
159-
}
156+
const runningProcess = Object.assign(new Process(), {
157+
_links: {
158+
self: {
159+
href: 'https://rest.api/processes/123'
160160
}
161-
});
162-
runningProcess.processStatus = ProcessStatus.RUNNING;
161+
}
162+
});
163+
runningProcess.processStatus = ProcessStatus.RUNNING;
163164

164-
const runningProcessPagination: PaginatedList<Process> = Object.assign(new PaginatedList(), {
165-
page: [runningProcess],
166-
_links: {
167-
self: {
168-
href: 'https://rest.api/processesList/456'
169-
}
165+
const runningProcessPagination: PaginatedList<Process> = Object.assign(new PaginatedList(), {
166+
page: [runningProcess],
167+
_links: {
168+
self: {
169+
href: 'https://rest.api/processesList/456'
170170
}
171-
});
171+
}
172+
});
172173

173-
const runningProcessRD = new RemoteData(0, 0, 0, RequestEntryState.Success, null, runningProcessPagination);
174+
const runningProcessRD = new RemoteData(0, 0, 0, RequestEntryState.Success, null, runningProcessPagination);
174175

175-
spyOn(processDataService, 'invalidateByHref');
176-
spyOn(processDataService, 'searchBy').and.returnValue(
177-
of(runningProcessRD)
178-
);
176+
spyOn(processDataService, 'searchBy').and.returnValue(
177+
of(runningProcessRD)
178+
);
179+
180+
expect(processDataService.searchBy).toHaveBeenCalledTimes(0);
181+
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledTimes(0);
182+
183+
let sub = processDataService.autoRefreshingSearchBy('id', 'byProperty', new FindListOptions(), 200).subscribe();
184+
expect(processDataService.searchBy).toHaveBeenCalledTimes(1);
179185

180-
let sub = processDataService.autoRefreshingSearchBy('byProperty', new FindListOptions(), 200).subscribe();
181-
tick(0);
182-
expect(processDataService.searchBy).toHaveBeenCalledTimes(1);
183-
tick(450);
184-
expect(processDataService.searchBy).toHaveBeenCalledTimes(3);
185-
sub.unsubscribe();
186+
tick(250);
186187

187-
flush();
188+
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledTimes(1);
188189

189-
expect(processDataService.invalidateByHref).toHaveBeenCalledTimes(3);
190+
sub.unsubscribe();
190191
}));
191192
});
192193
});

src/app/core/data/processes/process-data.service.ts

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { ObjectCacheService } from '../../cache/object-cache.service';
55
import { HALEndpointService } from '../../shared/hal-endpoint.service';
66
import { Process } from '../../../process-page/processes/process.model';
77
import { PROCESS } from '../../../process-page/processes/process.resource-type';
8-
import { Observable, timer as rxjsTimer, concatMap } from 'rxjs';
9-
import { switchMap, filter, distinctUntilChanged, find, tap } from 'rxjs/operators';
8+
import { Observable, Subscription } from 'rxjs';
9+
import { switchMap, filter, distinctUntilChanged, find } from 'rxjs/operators';
1010
import { PaginatedList } from '../paginated-list.model';
1111
import { Bitstream } from '../../shared/bitstream.model';
1212
import { RemoteData } from '../remote-data';
@@ -19,7 +19,7 @@ import { dataService } from '../base/data-service.decorator';
1919
import { DeleteData, DeleteDataImpl } from '../base/delete-data';
2020
import { NotificationsService } from '../../../shared/notifications/notifications.service';
2121
import { NoContent } from '../../shared/NoContent.model';
22-
import { getAllCompletedRemoteData, getFirstCompletedRemoteData } from '../../shared/operators';
22+
import { getAllCompletedRemoteData } from '../../shared/operators';
2323
import { ProcessStatus } from 'src/app/process-page/processes/process-status.model';
2424
import { hasValue } from '../../../shared/empty.util';
2525
import { SearchData, SearchDataImpl } from '../base/search-data';
@@ -41,6 +41,7 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
4141
private deleteData: DeleteData<Process>;
4242
private searchData: SearchData<Process>;
4343
protected activelyBeingPolled: Map<string, NodeJS.Timeout> = new Map();
44+
protected subs: Map<string, Subscription> = new Map();
4445

4546
constructor(
4647
protected requestService: RequestService,
@@ -129,6 +130,9 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
129130
}
130131

131132
/**
133+
* @param id The id for this auto-refreshing search. Used to stop
134+
* auto-refreshing afterwards, and ensure we're not
135+
* auto-refreshing the same thing multiple times.
132136
* @param searchMethod The search method for the Process
133137
* @param options The FindListOptions object
134138
* @param pollingIntervalInMs The interval by which the search will be repeated
@@ -137,22 +141,41 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
137141
* @return {Observable<RemoteData<PaginatedList<Process>>>}
138142
* Return an observable that emits a paginated list of processes every interval
139143
*/
140-
autoRefreshingSearchBy(searchMethod: string, options?: FindListOptions, pollingIntervalInMs: number = 5000, ...linksToFollow: FollowLinkConfig<Process>[]): Observable<RemoteData<PaginatedList<Process>>> {
141-
// Create observable that emits every pollingInterval
142-
return rxjsTimer(0, pollingIntervalInMs).pipe(
143-
concatMap(() => {
144-
// Every time the timer emits, request the current state of the processes
145-
return this.searchBy(searchMethod, options, false, false, ...linksToFollow).pipe(
146-
getFirstCompletedRemoteData(),
147-
tap((processListRD: RemoteData<PaginatedList<Process>>) => {
148-
// Once the response has been received, invalidate the response right before the next request
149-
setTimeout(() => {
150-
this.invalidateByHref(processListRD.payload._links.self.href);
151-
}, Math.max(pollingIntervalInMs - 100, 0));
152-
}),
153-
);
154-
})
144+
autoRefreshingSearchBy(id: string, searchMethod: string, options?: FindListOptions, pollingIntervalInMs: number = 5000, ...linksToFollow: FollowLinkConfig<Process>[]): Observable<RemoteData<PaginatedList<Process>>> {
145+
146+
const result$ = this.searchBy(searchMethod, options, true, true, ...linksToFollow).pipe(
147+
getAllCompletedRemoteData()
155148
);
149+
150+
const sub = result$.pipe(
151+
filter(() =>
152+
!this.activelyBeingPolled.has(id)
153+
)
154+
).subscribe((processListRd: RemoteData<PaginatedList<Process>>) => {
155+
this.clearCurrentTimeout(id);
156+
const nextTimeout = this.timer(() => {
157+
this.activelyBeingPolled.delete(id);
158+
this.requestService.setStaleByHrefSubstring(processListRd.payload._links.self.href);
159+
}, pollingIntervalInMs);
160+
161+
this.activelyBeingPolled.set(id, nextTimeout);
162+
});
163+
164+
this.subs.set(id, sub);
165+
166+
return result$;
167+
}
168+
169+
/**
170+
* Stop auto-refreshing the request with the given id
171+
* @param id the id of the request to stop automatically refreshing
172+
*/
173+
stopAutoRefreshing(id: string) {
174+
this.clearCurrentTimeout(id);
175+
if (hasValue(this.subs.get(id))) {
176+
this.subs.get(id).unsubscribe();
177+
this.subs.delete(id);
178+
}
156179
}
157180

158181
/**
@@ -181,14 +204,15 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
181204
}
182205

183206
/**
184-
* Clear the timeout for the given process, if that timeout exists
207+
* Clear the timeout for the given id, if that timeout exists
185208
* @protected
186209
*/
187-
protected clearCurrentTimeout(processId: string): void {
188-
const timeout = this.activelyBeingPolled.get(processId);
210+
protected clearCurrentTimeout(id: string): void {
211+
const timeout = this.activelyBeingPolled.get(id);
189212
if (hasValue(timeout)) {
190213
clearTimeout(timeout);
191214
}
215+
this.activelyBeingPolled.delete(id);
192216
}
193217

194218
/**
@@ -229,15 +253,15 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
229253
this.activelyBeingPolled.set(processId, nextTimeout);
230254
});
231255

256+
this.subs.set(processId, sub);
257+
232258
// When the process completes create a one off subscription (the `find` completes the
233259
// observable) that unsubscribes the previous one, removes the processId from the list of
234260
// processes being polled and clears any running timeouts
235261
process$.pipe(
236262
find((processRD: RemoteData<Process>) => ProcessDataService.hasCompletedOrFailed(processRD.payload))
237263
).subscribe(() => {
238-
this.clearCurrentTimeout(processId);
239-
this.activelyBeingPolled.delete(processId);
240-
sub.unsubscribe();
264+
this.stopAutoRefreshing(processId);
241265
});
242266

243267
return process$.pipe(

src/app/process-page/detail/process-detail.component.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { HttpClient } from '@angular/common/http';
2-
import { Component, Inject, NgZone, OnInit, PLATFORM_ID } from '@angular/core';
2+
import { Component, Inject, NgZone, OnInit, PLATFORM_ID, OnDestroy } from '@angular/core';
33
import { ActivatedRoute, Router } from '@angular/router';
44
import { BehaviorSubject, Observable } from 'rxjs';
55
import { finalize, map, switchMap, take, tap, find, startWith } from 'rxjs/operators';
@@ -36,7 +36,7 @@ import { PROCESS_PAGE_FOLLOW_LINKS } from '../process-page.resolver';
3636
/**
3737
* A component displaying detailed information about a DSpace Process
3838
*/
39-
export class ProcessDetailComponent implements OnInit {
39+
export class ProcessDetailComponent implements OnInit, OnDestroy {
4040

4141
/**
4242
* The AlertType enumeration
@@ -80,6 +80,8 @@ export class ProcessDetailComponent implements OnInit {
8080

8181
isRefreshing$: Observable<boolean>;
8282

83+
protected autoRefreshingID: string;
84+
8385
/**
8486
* Reference to NgbModal
8587
*/
@@ -108,7 +110,8 @@ export class ProcessDetailComponent implements OnInit {
108110
this.processRD$ = this.route.data.pipe(
109111
switchMap((data) => {
110112
if (isPlatformBrowser(this.platformId)) {
111-
return this.processService.autoRefreshUntilCompletion(this.route.snapshot.params.id, 5000, ...PROCESS_PAGE_FOLLOW_LINKS);
113+
this.autoRefreshingID = this.route.snapshot.params.id;
114+
return this.processService.autoRefreshUntilCompletion(this.autoRefreshingID, 5000, ...PROCESS_PAGE_FOLLOW_LINKS);
112115
} else {
113116
return [data.process as RemoteData<Process>];
114117
}
@@ -128,6 +131,15 @@ export class ProcessDetailComponent implements OnInit {
128131
);
129132
}
130133

134+
/**
135+
* Make sure the autoRefreshUntilCompletion is cleaned up properly
136+
*/
137+
ngOnDestroy() {
138+
if (hasValue(this.autoRefreshingID)) {
139+
this.processService.stopAutoRefreshing(this.autoRefreshingID);
140+
}
141+
}
142+
131143
/**
132144
* Get the name of a bitstream
133145
* @param bitstream

src/app/process-page/overview/process-overview.service.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,21 @@ export class ProcessOverviewService {
6262
}, findListOptions);
6363

6464
if (hasValue(autoRefreshingIntervalInMs) && autoRefreshingIntervalInMs > 0) {
65-
return this.processDataService.autoRefreshingSearchBy('byProperty', options, autoRefreshingIntervalInMs);
65+
this.processDataService.stopAutoRefreshing(processStatus);
66+
return this.processDataService.autoRefreshingSearchBy(processStatus, 'byProperty', options, autoRefreshingIntervalInMs);
6667
} else {
6768
return this.processDataService.searchBy('byProperty', options);
6869
}
6970
}
7071

72+
/**
73+
* Stop auto-refreshing the process with the given status
74+
* @param processStatus the processStatus of the request to stop automatically refreshing
75+
*/
76+
stopAutoRefreshing(processStatus: ProcessStatus) {
77+
this.processDataService.stopAutoRefreshing(processStatus);
78+
}
79+
7180
/**
7281
* Map the provided paginationOptions to FindListOptions
7382
* @param paginationOptions the PaginationComponentOptions to map

0 commit comments

Comments
 (0)