diff --git a/src/app/core/submission/submission-rest.service.spec.ts b/src/app/core/submission/submission-rest.service.spec.ts index 49715c6a1e..01cb886fa2 100644 --- a/src/app/core/submission/submission-rest.service.spec.ts +++ b/src/app/core/submission/submission-rest.service.spec.ts @@ -14,6 +14,8 @@ import { SubmissionRequest } from '../data/request.models'; import { FormFieldMetadataValueObject } from '../../shared/form/builder/models/form-field-metadata-value.model'; +import { of } from 'rxjs'; +import { RequestEntry } from '../data/request-entry.model'; describe('SubmissionRestService test suite', () => { let scheduler: TestScheduler; @@ -38,7 +40,9 @@ describe('SubmissionRestService test suite', () => { } beforeEach(() => { - requestService = getMockRequestService(); + requestService = getMockRequestService(of(Object.assign(new RequestEntry(), { + request: new SubmissionRequest('mock-request-uuid', 'mock-request-href'), + }))); rdbService = getMockRemoteDataBuildService(); scheduler = getTestScheduler(); halService = new HALEndpointServiceStub(resourceEndpointURL); @@ -62,7 +66,7 @@ describe('SubmissionRestService test suite', () => { scheduler.schedule(() => service.getDataById(resourceEndpoint, resourceScope).subscribe()); scheduler.flush(); - expect(requestService.send).toHaveBeenCalledWith(expected); + expect(requestService.send).toHaveBeenCalledWith(expected, false); }); }); diff --git a/src/app/core/submission/submission-rest.service.ts b/src/app/core/submission/submission-rest.service.ts index 9292d474a0..dfdc8449e0 100644 --- a/src/app/core/submission/submission-rest.service.ts +++ b/src/app/core/submission/submission-rest.service.ts @@ -1,10 +1,10 @@ import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; -import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; +import { Observable, skipWhile } from 'rxjs'; +import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; -import { hasValue, isNotEmpty } from '../../shared/empty.util'; +import { hasValue, hasValueOperator, isNotEmpty } from '../../shared/empty.util'; import { DeleteRequest, PostRequest, @@ -19,11 +19,25 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { getFirstCompletedRemoteData } from '../shared/operators'; import { URLCombiner } from '../url-combiner/url-combiner'; -import { ErrorResponse } from '../cache/response.models'; import { RemoteData } from '../data/remote-data'; import { SubmissionResponse } from './submission-response.model'; -import { RequestError } from '../data/request-error.model'; -import { RestRequest } from '../data/rest-request.model'; + +/** + * Retrieve the first emitting payload's dataDefinition, or throw an error if the request failed + */ +export const getFirstDataDefinition = () => + (source: Observable>): Observable => + source.pipe( + getFirstCompletedRemoteData(), + map((response: RemoteData) => { + if (response.hasFailed) { + throw new Error(response.errorMessage); + } else { + return hasValue(response?.payload?.dataDefinition) ? response.payload.dataDefinition : [response.payload]; + } + }), + distinctUntilChanged(), + ); /** * The service handling all submission REST requests @@ -48,15 +62,7 @@ export class SubmissionRestService { */ protected fetchRequest(requestId: string): Observable { return this.rdbService.buildFromRequestUUID(requestId).pipe( - getFirstCompletedRemoteData(), - map((response: RemoteData) => { - 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() + getFirstDataDefinition(), ); } @@ -108,21 +114,52 @@ export class SubmissionRestService { * The endpoint link name * @param id * 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 * server response */ - public getDataById(linkName: string, id: string): Observable { - const requestId = this.requestService.generateRequestId(); + public getDataById(linkName: string, id: string, useCachedVersionIfAvailable = false): Observable { return this.halService.getEndpoint(linkName).pipe( map((endpointURL: string) => this.getEndpointByIDHref(endpointURL, id)), filter((href: string) => isNotEmpty(href)), distinctUntilChanged(), - map((endpointURL: string) => new SubmissionRequest(requestId, endpointURL)), - tap((request: RestRequest) => { - this.requestService.send(request); + mergeMap((endpointURL: string) => { + this.sendGetDataRequest(endpointURL, useCachedVersionIfAvailable); + const startTime: number = new Date().getTime(); + return this.requestService.getByHref(endpointURL).pipe( + map((requestEntry) => requestEntry?.request?.uuid), + hasValueOperator(), + distinctUntilChanged(), + switchMap((requestId) => this.rdbService.buildFromRequestUUID(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) => rd.isStale || (!useCachedVersionIfAvailable && rd.lastUpdated < startTime)), + tap((rd: RemoteData) => { + if (hasValue(rd) && rd.isStale) { + this.sendGetDataRequest(endpointURL, useCachedVersionIfAvailable); + } + }) + ); }), - mergeMap(() => this.fetchRequest(requestId)), - distinctUntilChanged()); + getFirstDataDefinition(), + ); + } + + /** + * 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); } /** diff --git a/src/app/submission/form/submission-form.component.spec.ts b/src/app/submission/form/submission-form.component.spec.ts index cc77c44afb..d4499d87ae 100644 --- a/src/app/submission/form/submission-form.component.spec.ts +++ b/src/app/submission/form/submission-form.component.spec.ts @@ -27,7 +27,7 @@ import { TestScheduler } from 'rxjs/testing'; import { SectionsService } from '../sections/sections.service'; import { VisibilityType } from '../sections/visibility-type'; -describe('SubmissionFormComponent Component', () => { +describe('SubmissionFormComponent', () => { let comp: SubmissionFormComponent; let compAsAny: any; @@ -197,7 +197,6 @@ describe('SubmissionFormComponent Component', () => { }); scheduler.flush(); - expect(comp.collectionId).toEqual(submissionObjectNew.collection.id); expect(comp.submissionDefinition).toEqual(submissionObjectNew.submissionDefinition); expect(comp.definitionId).toEqual(submissionObjectNew.submissionDefinition.name); expect(comp.sections).toEqual(submissionObjectNew.sections); @@ -235,7 +234,6 @@ describe('SubmissionFormComponent Component', () => { }); scheduler.flush(); - expect(comp.collectionId).toEqual('45f2f3f1-ba1f-4f36-908a-3f1ea9a557eb'); expect(submissionServiceStub.resetSubmissionObject).not.toHaveBeenCalled(); done(); }); diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts index f0cea8d6b2..49b5eea0f0 100644 --- a/src/app/submission/form/submission-form.component.ts +++ b/src/app/submission/form/submission-form.component.ts @@ -250,13 +250,12 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy { * new submission object */ onCollectionChange(submissionObject: SubmissionObject) { - this.collectionId = (submissionObject.collection as Collection).id; if (this.definitionId !== (submissionObject.submissionDefinition as SubmissionDefinitionsModel).name) { this.sections = submissionObject.sections; this.submissionDefinition = (submissionObject.submissionDefinition as SubmissionDefinitionsModel); this.definitionId = this.submissionDefinition.name; this.submissionService.resetSubmissionObject( - this.collectionId, + (submissionObject.collection as Collection).id, this.submissionId, submissionObject._links.self.href, this.submissionDefinition, diff --git a/src/app/workflowitems-edit-page/workflow-item-page.resolver.ts b/src/app/workflowitems-edit-page/workflow-item-page.resolver.ts index 4bb3eac513..c5d6ee9520 100644 --- a/src/app/workflowitems-edit-page/workflow-item-page.resolver.ts +++ b/src/app/workflowitems-edit-page/workflow-item-page.resolver.ts @@ -27,6 +27,7 @@ export class WorkflowItemPageResolver implements Resolve> { +export class WorkspaceItemPageResolver implements Resolve> { constructor(private workspaceItemService: WorkspaceitemDataService) { } @@ -22,11 +22,12 @@ export class WorkspaceItemPageResolver implements Resolve> Emits the found workflow item based on the parameters in the current route, * or an error if something went wrong */ - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> { return this.workspaceItemService.findById(route.params.id, true, false, followLink('item'), + followLink('collection'), ).pipe( getFirstCompletedRemoteData(), );