forked from hazza/dspace-angular
Merge pull request #4060 from alexandrevryghem/w2p-127655_fix-submission-form-getting-stuck-in-loop_contribute-main
Fix infinite loading submission forms
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
import { of } from 'rxjs';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
import { FormFieldMetadataValueObject } from '../../shared/form/builder/models/form-field-metadata-value.model';
|
import { FormFieldMetadataValueObject } from '../../shared/form/builder/models/form-field-metadata-value.model';
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
SubmissionRequest,
|
SubmissionRequest,
|
||||||
} from '../data/request.models';
|
} from '../data/request.models';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { RequestEntry } from '../data/request-entry.model';
|
||||||
import { SubmissionRestService } from './submission-rest.service';
|
import { SubmissionRestService } from './submission-rest.service';
|
||||||
|
|
||||||
describe('SubmissionRestService test suite', () => {
|
describe('SubmissionRestService test suite', () => {
|
||||||
@@ -38,7 +40,9 @@ describe('SubmissionRestService test suite', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService(of(Object.assign(new RequestEntry(), {
|
||||||
|
request: new SubmissionRequest('mock-request-uuid', 'mock-request-href'),
|
||||||
|
})));
|
||||||
rdbService = getMockRemoteDataBuildService();
|
rdbService = getMockRemoteDataBuildService();
|
||||||
scheduler = getTestScheduler();
|
scheduler = getTestScheduler();
|
||||||
halService = new HALEndpointServiceStub(resourceEndpointURL);
|
halService = new HALEndpointServiceStub(resourceEndpointURL);
|
||||||
@@ -62,7 +66,7 @@ describe('SubmissionRestService test suite', () => {
|
|||||||
scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe());
|
scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(requestService.send).toHaveBeenCalledWith(expected);
|
expect(requestService.send).toHaveBeenCalledWith(expected, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,15 +1,20 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import {
|
||||||
|
Observable,
|
||||||
|
skipWhile,
|
||||||
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
filter,
|
filter,
|
||||||
map,
|
map,
|
||||||
mergeMap,
|
mergeMap,
|
||||||
|
switchMap,
|
||||||
tap,
|
tap,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hasValue,
|
hasValue,
|
||||||
|
hasValueOperator,
|
||||||
isNotEmpty,
|
isNotEmpty,
|
||||||
} from '../../shared/empty.util';
|
} from '../../shared/empty.util';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
@@ -25,7 +30,6 @@ import {
|
|||||||
} from '../data/request.models';
|
} from '../data/request.models';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { RequestError } from '../data/request-error.model';
|
import { RequestError } from '../data/request-error.model';
|
||||||
import { RestRequest } from '../data/rest-request.model';
|
|
||||||
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { getFirstCompletedRemoteData } from '../shared/operators';
|
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||||
@@ -33,6 +37,23 @@ import { SubmitDataResponseDefinitionObject } from '../shared/submit-data-respon
|
|||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||||
import { SubmissionResponse } from './submission-response.model';
|
import { SubmissionResponse } from './submission-response.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the first emitting payload's dataDefinition, or throw an error if the request failed
|
||||||
|
*/
|
||||||
|
export const getFirstDataDefinition = () =>
|
||||||
|
(source: Observable<RemoteData<SubmissionResponse>>): Observable<SubmitDataResponseDefinitionObject> =>
|
||||||
|
source.pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
map((response: RemoteData<SubmissionResponse>) => {
|
||||||
|
if (response.hasFailed) {
|
||||||
|
throw new ErrorResponse({ statusText: response.errorMessage, statusCode: response.statusCode } as RequestError);
|
||||||
|
} else {
|
||||||
|
return hasValue(response?.payload?.dataDefinition) ? response.payload.dataDefinition : [response.payload];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The service handling all submission REST requests
|
* The service handling all submission REST requests
|
||||||
*/
|
*/
|
||||||
@@ -56,15 +77,7 @@ export class SubmissionRestService {
|
|||||||
*/
|
*/
|
||||||
protected fetchRequest(requestId: string): Observable<SubmitDataResponseDefinitionObject> {
|
protected fetchRequest(requestId: string): Observable<SubmitDataResponseDefinitionObject> {
|
||||||
return this.rdbService.buildFromRequestUUID<SubmissionResponse>(requestId).pipe(
|
return this.rdbService.buildFromRequestUUID<SubmissionResponse>(requestId).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstDataDefinition(),
|
||||||
map((response: RemoteData<SubmissionResponse>) => {
|
|
||||||
if (response.hasFailed) {
|
|
||||||
throw new ErrorResponse({ statusText: response.errorMessage, statusCode: response.statusCode } as RequestError);
|
|
||||||
} else {
|
|
||||||
return hasValue(response.payload) ? response.payload.dataDefinition : response.payload;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
distinctUntilChanged(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,21 +129,52 @@ export class SubmissionRestService {
|
|||||||
* The endpoint link name
|
* The endpoint link name
|
||||||
* @param id
|
* @param id
|
||||||
* The submission Object to retrieve
|
* The submission Object to retrieve
|
||||||
|
* @param useCachedVersionIfAvailable
|
||||||
|
* If this is true, the request will only be sent if there's no valid & cached version. Defaults to false
|
||||||
* @return Observable<SubmitDataResponseDefinitionObject>
|
* @return Observable<SubmitDataResponseDefinitionObject>
|
||||||
* server response
|
* server response
|
||||||
*/
|
*/
|
||||||
public getDataById(linkName: string, id: string): Observable<SubmitDataResponseDefinitionObject> {
|
public getDataById(linkName: string, id: string, useCachedVersionIfAvailable = false): Observable<SubmitDataResponseDefinitionObject> {
|
||||||
const requestId = this.requestService.generateRequestId();
|
|
||||||
return this.halService.getEndpoint(linkName).pipe(
|
return this.halService.getEndpoint(linkName).pipe(
|
||||||
map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, id)),
|
map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, id)),
|
||||||
filter((href: string) => isNotEmpty(href)),
|
filter((href: string) => isNotEmpty(href)),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)),
|
mergeMap((endpointURL: string) => {
|
||||||
tap((request: RestRequest) => {
|
this.sendGetDataRequest(endpointURL, useCachedVersionIfAvailable);
|
||||||
this.requestService.send(request);
|
const startTime: number = new Date().getTime();
|
||||||
|
return this.requestService.getByHref(endpointURL).pipe(
|
||||||
|
map((requestEntry) => requestEntry?.request?.uuid),
|
||||||
|
hasValueOperator(),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
switchMap((requestId) => this.rdbService.buildFromRequestUUID<SubmissionResponse>(requestId)),
|
||||||
|
// 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
|
||||||
|
// cached completed object
|
||||||
|
skipWhile((rd: RemoteData<SubmissionResponse>) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)),
|
||||||
|
tap((rd: RemoteData<SubmissionResponse>) => {
|
||||||
|
if (hasValue(rd) && rd.isStale) {
|
||||||
|
this.sendGetDataRequest(endpointURL, useCachedVersionIfAvailable);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
mergeMap(() => this.fetchRequest(requestId)),
|
getFirstDataDefinition(),
|
||||||
distinctUntilChanged());
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a GET SubmissionRequest
|
||||||
|
*
|
||||||
|
* @param href
|
||||||
|
* Endpoint URL of the submission data
|
||||||
|
* @param useCachedVersionIfAvailable
|
||||||
|
* If this is true, the request will only be sent if there's no valid & cached version. Defaults to false
|
||||||
|
*/
|
||||||
|
private sendGetDataRequest(href: string, useCachedVersionIfAvailable = false) {
|
||||||
|
const requestId = this.requestService.generateRequestId();
|
||||||
|
const request = new SubmissionRequest(requestId, href);
|
||||||
|
this.requestService.send(request, useCachedVersionIfAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -96,6 +96,7 @@ import {
|
|||||||
getRemoteDataPayload,
|
getRemoteDataPayload,
|
||||||
} from '../../../../core/shared/operators';
|
} from '../../../../core/shared/operators';
|
||||||
import { SubmissionObject } from '../../../../core/submission/models/submission-object.model';
|
import { SubmissionObject } from '../../../../core/submission/models/submission-object.model';
|
||||||
|
import { SUBMISSION_LINKS_TO_FOLLOW } from '../../../../core/submission/resolver/submission-links-to-follow';
|
||||||
import { SubmissionObjectDataService } from '../../../../core/submission/submission-object-data.service';
|
import { SubmissionObjectDataService } from '../../../../core/submission/submission-object-data.service';
|
||||||
import { paginatedRelationsToItems } from '../../../../item-page/simple/item-types/shared/item-relationships-utils';
|
import { paginatedRelationsToItems } from '../../../../item-page/simple/item-types/shared/item-relationships-utils';
|
||||||
import { SubmissionService } from '../../../../submission/submission.service';
|
import { SubmissionService } from '../../../../submission/submission.service';
|
||||||
@@ -450,7 +451,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
*/
|
*/
|
||||||
private setItem() {
|
private setItem() {
|
||||||
const submissionObject$ = this.submissionObjectService
|
const submissionObject$ = this.submissionObjectService
|
||||||
.findById(this.model.submissionId, true, true, followLink('item'), followLink('collection')).pipe(
|
.findById(this.model.submissionId, true, true, ...SUBMISSION_LINKS_TO_FOLLOW).pipe(
|
||||||
getAllSucceededRemoteData(),
|
getAllSucceededRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
);
|
);
|
||||||
|
@@ -226,7 +226,6 @@ describe('SubmissionFormComponent', () => {
|
|||||||
});
|
});
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(comp.collectionId).toEqual(submissionObjectNew.collection.id);
|
|
||||||
expect(comp.submissionDefinition).toEqual(submissionObjectNew.submissionDefinition);
|
expect(comp.submissionDefinition).toEqual(submissionObjectNew.submissionDefinition);
|
||||||
expect(comp.definitionId).toEqual(submissionObjectNew.submissionDefinition.name);
|
expect(comp.definitionId).toEqual(submissionObjectNew.submissionDefinition.name);
|
||||||
expect(comp.sections).toEqual(submissionObjectNew.sections);
|
expect(comp.sections).toEqual(submissionObjectNew.sections);
|
||||||
@@ -264,7 +263,6 @@ describe('SubmissionFormComponent', () => {
|
|||||||
});
|
});
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
|
||||||
expect(comp.collectionId).toEqual('45f2f3f1-ba1f-4f36-908a-3f1ea9a557eb');
|
|
||||||
expect(submissionServiceStub.resetSubmissionObject).not.toHaveBeenCalled();
|
expect(submissionServiceStub.resetSubmissionObject).not.toHaveBeenCalled();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@@ -288,13 +288,12 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
|
|||||||
* new submission object
|
* new submission object
|
||||||
*/
|
*/
|
||||||
onCollectionChange(submissionObject: SubmissionObject) {
|
onCollectionChange(submissionObject: SubmissionObject) {
|
||||||
this.collectionId = (submissionObject.collection as Collection).id;
|
|
||||||
if (this.definitionId !== (submissionObject.submissionDefinition as SubmissionDefinitionsModel).name) {
|
if (this.definitionId !== (submissionObject.submissionDefinition as SubmissionDefinitionsModel).name) {
|
||||||
this.sections = submissionObject.sections;
|
this.sections = submissionObject.sections;
|
||||||
this.submissionDefinition = (submissionObject.submissionDefinition as SubmissionDefinitionsModel);
|
this.submissionDefinition = (submissionObject.submissionDefinition as SubmissionDefinitionsModel);
|
||||||
this.definitionId = this.submissionDefinition.name;
|
this.definitionId = this.submissionDefinition.name;
|
||||||
this.submissionService.resetSubmissionObject(
|
this.submissionService.resetSubmissionObject(
|
||||||
this.collectionId,
|
(submissionObject.collection as Collection).id,
|
||||||
this.submissionId,
|
this.submissionId,
|
||||||
submissionObject._links.self.href,
|
submissionObject._links.self.href,
|
||||||
this.submissionDefinition,
|
this.submissionDefinition,
|
||||||
|
@@ -9,8 +9,8 @@ import { Observable } from 'rxjs';
|
|||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { WorkflowItem } from '../core/submission/models/workflowitem.model';
|
import { WorkflowItem } from '../core/submission/models/workflowitem.model';
|
||||||
|
import { SUBMISSION_LINKS_TO_FOLLOW } from '../core/submission/resolver/submission-links-to-follow';
|
||||||
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
|
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
|
||||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
|
||||||
|
|
||||||
export const workflowItemPageResolver: ResolveFn<RemoteData<WorkflowItem>> = (
|
export const workflowItemPageResolver: ResolveFn<RemoteData<WorkflowItem>> = (
|
||||||
route: ActivatedRouteSnapshot,
|
route: ActivatedRouteSnapshot,
|
||||||
@@ -21,7 +21,7 @@ export const workflowItemPageResolver: ResolveFn<RemoteData<WorkflowItem>> = (
|
|||||||
route.params.id,
|
route.params.id,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
followLink('item'),
|
...SUBMISSION_LINKS_TO_FOLLOW,
|
||||||
).pipe(
|
).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
);
|
);
|
||||||
|
@@ -9,8 +9,8 @@ import { Observable } from 'rxjs';
|
|||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { WorkspaceItem } from '../core/submission/models/workspaceitem.model';
|
import { WorkspaceItem } from '../core/submission/models/workspaceitem.model';
|
||||||
|
import { SUBMISSION_LINKS_TO_FOLLOW } from '../core/submission/resolver/submission-links-to-follow';
|
||||||
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
||||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method for resolving a workflow item based on the parameters in the current route
|
* Method for resolving a workflow item based on the parameters in the current route
|
||||||
@@ -28,7 +28,7 @@ export const workspaceItemPageResolver: ResolveFn<RemoteData<WorkspaceItem>> = (
|
|||||||
return workspaceItemService.findById(route.params.id,
|
return workspaceItemService.findById(route.params.id,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
followLink('item'),
|
...SUBMISSION_LINKS_TO_FOLLOW,
|
||||||
).pipe(
|
).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user