Skip to content

Commit a226502

Browse files
authored
Merge pull request #4387 from atmire/w2p-127655_fix-submission-form-getting-stuck-in-loop_contribute-7.6
[Port dspace-7_x] Fix infinite loading submission forms
2 parents 4f79574 + d3876c7 commit a226502

File tree

6 files changed

+72
-32
lines changed

6 files changed

+72
-32
lines changed

src/app/core/submission/submission-rest.service.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
SubmissionRequest
1515
} from '../data/request.models';
1616
import { FormFieldMetadataValueObject } from '../../shared/form/builder/models/form-field-metadata-value.model';
17+
import { of } from 'rxjs';
18+
import { RequestEntry } from '../data/request-entry.model';
1719

1820
describe('SubmissionRestService test suite', () => {
1921
let scheduler: TestScheduler;
@@ -38,7 +40,9 @@ describe('SubmissionRestService test suite', () => {
3840
}
3941

4042
beforeEach(() => {
41-
requestService = getMockRequestService();
43+
requestService = getMockRequestService(of(Object.assign(new RequestEntry(), {
44+
request: new SubmissionRequest('mock-request-uuid', 'mock-request-href'),
45+
})));
4246
rdbService = getMockRemoteDataBuildService();
4347
scheduler = getTestScheduler();
4448
halService = new HALEndpointServiceStub(resourceEndpointURL);
@@ -62,7 +66,7 @@ describe('SubmissionRestService test suite', () => {
6266
scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe());
6367
scheduler.flush();
6468

65-
expect(requestService.send).toHaveBeenCalledWith(expected);
69+
expect(requestService.send).toHaveBeenCalledWith(expected, false);
6670
});
6771
});
6872

src/app/core/submission/submission-rest.service.ts

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { Injectable } from '@angular/core';
22

3-
import { Observable } from 'rxjs';
4-
import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators';
3+
import { Observable, skipWhile } from 'rxjs';
4+
import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators';
55

66
import { RequestService } from '../data/request.service';
7-
import { hasValue, isNotEmpty } from '../../shared/empty.util';
7+
import { hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util';
88
import {
99
DeleteRequest,
1010
PostRequest,
@@ -19,11 +19,25 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
1919
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
2020
import { getFirstCompletedRemoteData } from '../shared/operators';
2121
import { URLCombiner } from '../url-combiner/url-combiner';
22-
import { ErrorResponse } from '../cache/response.models';
2322
import { RemoteData } from '../data/remote-data';
2423
import { SubmissionResponse } from './submission-response.model';
25-
import { RequestError } from '../data/request-error.model';
26-
import { RestRequest } from '../data/rest-request.model';
24+
25+
/**
26+
* Retrieve the first emitting payload's dataDefinition, or throw an error if the request failed
27+
*/
28+
export const getFirstDataDefinition = () =>
29+
(source: Observable<RemoteData<SubmissionResponse>>): Observable<SubmitDataResponseDefinitionObject> =>
30+
source.pipe(
31+
getFirstCompletedRemoteData(),
32+
map((response: RemoteData<SubmissionResponse>) => {
33+
if (response.hasFailed) {
34+
throw new Error(response.errorMessage);
35+
} else {
36+
return hasValue(response?.payload?.dataDefinition) ? response.payload.dataDefinition : [response.payload];
37+
}
38+
}),
39+
distinctUntilChanged(),
40+
);
2741

2842
/**
2943
* The service handling all submission REST requests
@@ -48,15 +62,7 @@ export class SubmissionRestService {
4862
*/
4963
protected fetchRequest(requestId: string): Observable<SubmitDataResponseDefinitionObject> {
5064
return this.rdbService.buildFromRequestUUID<SubmissionResponse>(requestId).pipe(
51-
getFirstCompletedRemoteData(),
52-
map((response: RemoteData<SubmissionResponse>) => {
53-
if (response.hasFailed) {
54-
throw new ErrorResponse({ statusText: response.errorMessage, statusCode: response.statusCode } as RequestError);
55-
} else {
56-
return hasValue(response.payload) ? response.payload.dataDefinition : response.payload;
57-
}
58-
}),
59-
distinctUntilChanged()
65+
getFirstDataDefinition(),
6066
);
6167
}
6268

@@ -108,21 +114,52 @@ export class SubmissionRestService {
108114
* The endpoint link name
109115
* @param id
110116
* The submission Object to retrieve
117+
* @param useCachedVersionIfAvailable
118+
* If this is true, the request will only be sent if there's no valid & cached version. Defaults to false
111119
* @return Observable<SubmitDataResponseDefinitionObject>
112120
* server response
113121
*/
114-
public getDataById(linkName: string, id: string): Observable<SubmitDataResponseDefinitionObject> {
115-
const requestId = this.requestService.generateRequestId();
122+
public getDataById(linkName: string, id: string, useCachedVersionIfAvailable = false): Observable<SubmitDataResponseDefinitionObject> {
116123
return this.halService.getEndpoint(linkName).pipe(
117124
map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, id)),
118125
filter((href: string) => isNotEmpty(href)),
119126
distinctUntilChanged(),
120-
map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)),
121-
tap((request: RestRequest) => {
122-
this.requestService.send(request);
127+
mergeMap((endpointURL: string) => {
128+
this.sendGetDataRequest(endpointURL, useCachedVersionIfAvailable);
129+
const startTime: number = new Date().getTime();
130+
return this.requestService.getByHref(endpointURL).pipe(
131+
map((requestEntry) => requestEntry?.request?.uuid),
132+
hasValueOperator(),
133+
distinctUntilChanged(),
134+
switchMap((requestId) => this.rdbService.buildFromRequestUUID<SubmissionResponse>(requestId)),
135+
// This skip ensures that if a stale object is present in the cache when you do a
136+
// call it isn't immediately returned, but we wait until the remote data for the new request
137+
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
138+
// cached completed object
139+
skipWhile((rd: RemoteData<SubmissionResponse>) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)),
140+
tap((rd: RemoteData<SubmissionResponse>) => {
141+
if (hasValue(rd) && rd.isStale) {
142+
this.sendGetDataRequest(endpointURL, useCachedVersionIfAvailable);
143+
}
144+
})
145+
);
123146
}),
124-
mergeMap(() => this.fetchRequest(requestId)),
125-
distinctUntilChanged());
147+
getFirstDataDefinition(),
148+
);
149+
}
150+
151+
/**
152+
* Send a GET SubmissionRequest
153+
*
154+
* @param href
155+
* Endpoint URL of the submission data
156+
* @param useCachedVersionIfAvailable
157+
* If this is true, the request will only be sent if there's no valid & cached version. Defaults to false
158+
*/
159+
private sendGetDataRequest(href: string, useCachedVersionIfAvailable = false) {
160+
const requestId = this.requestService.generateRequestId();
161+
const request = new SubmissionRequest(requestId, href);
162+
this.requestService.send(request, useCachedVersionIfAvailable);
126163
}
127164

128165
/**

src/app/submission/form/submission-form.component.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { TestScheduler } from 'rxjs/testing';
2727
import { SectionsService } from '../sections/sections.service';
2828
import { VisibilityType } from '../sections/visibility-type';
2929

30-
describe('SubmissionFormComponent Component', () => {
30+
describe('SubmissionFormComponent', () => {
3131

3232
let comp: SubmissionFormComponent;
3333
let compAsAny: any;
@@ -197,7 +197,6 @@ describe('SubmissionFormComponent Component', () => {
197197
});
198198
scheduler.flush();
199199

200-
expect(comp.collectionId).toEqual(submissionObjectNew.collection.id);
201200
expect(comp.submissionDefinition).toEqual(submissionObjectNew.submissionDefinition);
202201
expect(comp.definitionId).toEqual(submissionObjectNew.submissionDefinition.name);
203202
expect(comp.sections).toEqual(submissionObjectNew.sections);
@@ -235,7 +234,6 @@ describe('SubmissionFormComponent Component', () => {
235234
});
236235
scheduler.flush();
237236

238-
expect(comp.collectionId).toEqual('45f2f3f1-ba1f-4f36-908a-3f1ea9a557eb');
239237
expect(submissionServiceStub.resetSubmissionObject).not.toHaveBeenCalled();
240238
done();
241239
});

src/app/submission/form/submission-form.component.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,12 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
250250
* new submission object
251251
*/
252252
onCollectionChange(submissionObject: SubmissionObject) {
253-
this.collectionId = (submissionObject.collection as Collection).id;
254253
if (this.definitionId !== (submissionObject.submissionDefinition as SubmissionDefinitionsModel).name) {
255254
this.sections = submissionObject.sections;
256255
this.submissionDefinition = (submissionObject.submissionDefinition as SubmissionDefinitionsModel);
257256
this.definitionId = this.submissionDefinition.name;
258257
this.submissionService.resetSubmissionObject(
259-
this.collectionId,
258+
(submissionObject.collection as Collection).id,
260259
this.submissionId,
261260
submissionObject._links.self.href,
262261
this.submissionDefinition,

src/app/workflowitems-edit-page/workflow-item-page.resolver.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export class WorkflowItemPageResolver implements Resolve<RemoteData<WorkflowItem
2727
true,
2828
false,
2929
followLink('item'),
30+
followLink('collection'),
3031
).pipe(
3132
getFirstCompletedRemoteData(),
3233
);

src/app/workspaceitems-edit-page/workspace-item-page.resolver.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import { Observable } from 'rxjs';
44
import { RemoteData } from '../core/data/remote-data';
55
import { followLink } from '../shared/utils/follow-link-config.model';
66
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
7-
import { WorkflowItem } from '../core/submission/models/workflowitem.model';
7+
import { WorkspaceItem } from '../core/submission/models/workspaceitem.model';
88
import { getFirstCompletedRemoteData } from '../core/shared/operators';
99

1010
/**
1111
* This class represents a resolver that requests a specific workflow item before the route is activated
1212
*/
1313
@Injectable()
14-
export class WorkspaceItemPageResolver implements Resolve<RemoteData<WorkflowItem>> {
14+
export class WorkspaceItemPageResolver implements Resolve<RemoteData<WorkspaceItem>> {
1515
constructor(private workspaceItemService: WorkspaceitemDataService) {
1616
}
1717

@@ -22,11 +22,12 @@ export class WorkspaceItemPageResolver implements Resolve<RemoteData<WorkflowIte
2222
* @returns Observable<<RemoteData<Item>> Emits the found workflow item based on the parameters in the current route,
2323
* or an error if something went wrong
2424
*/
25-
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<WorkflowItem>> {
25+
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<WorkspaceItem>> {
2626
return this.workspaceItemService.findById(route.params.id,
2727
true,
2828
false,
2929
followLink('item'),
30+
followLink('collection'),
3031
).pipe(
3132
getFirstCompletedRemoteData(),
3233
);

0 commit comments

Comments
 (0)