Returns RemoteData when retrieving a submission and handles error response

This commit is contained in:
Giuseppe Digilio
2019-01-23 13:10:19 +01:00
parent 4505f6e525
commit 491a8f7d2b
8 changed files with 128 additions and 81 deletions

View File

@@ -5,7 +5,6 @@ import { RestRequest } from '../data/request.models';
import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
import { ErrorResponse, RestResponse, SubmissionSuccessResponse } from '../cache/response-cache.models';
import { isEmpty, isNotEmpty, isNotNull } from '../../shared/empty.util';
import { ConfigObject } from '../config/models/config.model';
import { BaseResponseParsingService } from '../data/base-response-parsing.service';
import { GLOBAL_CONFIG } from '../../../config';
@@ -18,9 +17,6 @@ import { NormalizedWorkspaceItem } from './models/normalized-workspaceitem.model
import { normalizeSectionData } from './models/workspaceitem-sections.model';
import { NormalizedWorkflowItem } from './models/normalized-workflowitem.model';
import { NormalizedEditItem } from './models/normalized-edititem.model';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { NormalizedSubmissionObject } from './models/normalized-submission-object.model';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
@Injectable()
export class SubmissionResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
@@ -57,7 +53,7 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService
const normalizedDefinition = Array.of();
const processedList = Array.isArray(dataDefinition) ? dataDefinition : Array.of(dataDefinition);
processedList.forEach((item, index) => {
processedList.forEach((item) => {
let normalizedItem = Object.assign({}, item);
// In case data is an Instance of NormalizedWorkspaceItem normalize field value of all the section of type form

View File

@@ -16,6 +16,7 @@ import { getMockTranslateService } from '../../shared/mocks/mock-translate.servi
import { RouterStub } from '../../shared/testing/router-stub';
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
import { mockSubmissionObject } from '../../shared/mocks/mock-submission';
import { RemoteData } from '../../core/data/remote-data';
describe('SubmissionEditComponent Component', () => {
@@ -65,7 +66,14 @@ describe('SubmissionEditComponent Component', () => {
it('should init properly when a valid SubmissionObject has been retrieved', fakeAsync(() => {
route.testParams = { id: submissionId };
submissionServiceStub.retrieveSubmission.and.returnValue(observableOf(submissionObject));
submissionServiceStub.retrieveSubmission.and.returnValue(observableOf(
new RemoteData(
false,
false,
true,
null,
submissionObject)
));
fixture.detectChanges();
@@ -80,7 +88,14 @@ describe('SubmissionEditComponent Component', () => {
it('should redirect to mydspace when an empty SubmissionObject has been retrieved', fakeAsync(() => {
route.testParams = { id: submissionId };
submissionServiceStub.retrieveSubmission.and.returnValue(observableOf({}));
submissionServiceStub.retrieveSubmission.and.returnValue(observableOf(
new RemoteData(
false,
false,
true,
null,
{})
));
fixture.detectChanges();

View File

@@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { flatMap, tap } from 'rxjs/operators';
import { filter, switchMap } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model';
@@ -12,6 +12,7 @@ import { SubmissionService } from '../submission.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { SubmissionObject } from '../../core/submission/models/submission-object.model';
import { Collection } from '../../core/shared/collection.model';
import { RemoteData } from '../../core/data/remote-data';
@Component({
selector: 'ds-submission-edit',
@@ -42,21 +43,28 @@ export class SubmissionEditComponent implements OnDestroy, OnInit {
ngOnInit() {
this.subs.push(this.route.paramMap.pipe(
flatMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id')))
).subscribe((submissionObject: SubmissionObject) => {
// NOTE new submission is retrieved on the browser side only
if (isNotNull(submissionObject)) {
if (isEmpty(submissionObject)) {
switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))),
// NOTE new submission is retrieved on the browser side only, so get null on server side rendering
filter((submissionObjectRD: RemoteData<SubmissionObject>) => isNotNull(submissionObjectRD))
).subscribe((submissionObjectRD: RemoteData<SubmissionObject>) => {
if (submissionObjectRD.hasSucceeded) {
if (isEmpty(submissionObjectRD.payload)) {
this.notificationsService.info(null, this.translate.get('submission.general.cannot_submit'));
this.router.navigate(['/mydspace']);
} else {
this.submissionId = submissionObject.id;
this.collectionId = (submissionObject.collection as Collection).id;
this.selfUrl = submissionObject.self;
this.sections = submissionObject.sections;
this.submissionDefinition = (submissionObject.submissionDefinition as SubmissionDefinitionsModel);
this.submissionId = submissionObjectRD.payload.id.toString();
this.collectionId = (submissionObjectRD.payload.collection as Collection).id;
this.selfUrl = submissionObjectRD.payload.self;
this.sections = submissionObjectRD.payload.sections;
this.submissionDefinition = (submissionObjectRD.payload.submissionDefinition as SubmissionDefinitionsModel);
this.changeDetectorRef.detectChanges();
}
} else {
if (submissionObjectRD.error.statusCode === 404) {
// redirect to not found page
this.router.navigate(['/404'], { skipLocationChange: true });
}
// TODO handle generic error
}
}));
}

View File

@@ -1,38 +1,38 @@
import { hasValue, isNotEmpty, isNotNull, isUndefined } from '../../shared/empty.util';
import { findKey, uniqWith, isEqual, differenceWith } from 'lodash';
import { differenceWith, findKey, isEqual, uniqWith } from 'lodash';
import {
ChangeSubmissionCollectionAction,
CompleteInitSubmissionFormAction,
DeleteSectionErrorsAction,
DeleteUploadedFileAction,
DepositSubmissionAction,
DepositSubmissionErrorAction,
DepositSubmissionSuccessAction,
DisableSectionAction,
EditFileDataAction,
EnableSectionAction,
NewUploadedFileAction,
InertSectionErrorsAction,
InitSectionAction,
InitSubmissionFormAction,
NewUploadedFileAction,
RemoveSectionErrorsAction,
ResetSubmissionFormAction,
SaveAndDepositSubmissionAction,
SaveForLaterSubmissionFormAction,
SaveForLaterSubmissionFormErrorAction,
SaveForLaterSubmissionFormSuccessAction,
SaveSubmissionFormAction,
SaveSubmissionFormErrorAction,
SaveSubmissionFormSuccessAction,
SaveSubmissionSectionFormAction,
SaveSubmissionSectionFormErrorAction,
SaveSubmissionSectionFormSuccessAction,
SectionStatusChangeAction,
SetActiveSectionAction,
SubmissionObjectAction,
SubmissionObjectActionTypes,
InertSectionErrorsAction,
DeleteSectionErrorsAction,
ResetSubmissionFormAction,
UpdateSectionDataAction,
SaveSubmissionFormAction,
SetActiveSectionAction,
SaveSubmissionSectionFormAction,
DepositSubmissionAction,
DepositSubmissionSuccessAction,
DepositSubmissionErrorAction,
ChangeSubmissionCollectionAction,
SaveSubmissionFormSuccessAction,
SaveSubmissionFormErrorAction,
SaveSubmissionSectionFormSuccessAction,
SaveSubmissionSectionFormErrorAction,
InitSectionAction,
RemoveSectionErrorsAction,
SaveForLaterSubmissionFormAction,
SaveAndDepositSubmissionAction,
SaveForLaterSubmissionFormSuccessAction,
SaveForLaterSubmissionFormErrorAction
UpdateSectionDataAction
} from './submission-objects.actions';
import { WorkspaceitemSectionDataType } from '../../core/submission/models/workspaceitem-sections.model';
import { WorkspaceitemSectionUploadObject } from '../../core/submission/models/workspaceitem-section-upload.model';
@@ -117,7 +117,6 @@ export function submissionObjectReducer(state = initialState, action: Submission
}
case SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_SUCCESS:
case SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM_SUCCESS:
case SubmissionObjectActionTypes.SAVE_SUBMISSION_SECTION_FORM_SUCCESS:
case SubmissionObjectActionTypes.SAVE_SUBMISSION_FORM_ERROR:
case SubmissionObjectActionTypes.SAVE_FOR_LATER_SUBMISSION_FORM_ERROR:

View File

@@ -4,6 +4,7 @@ import { Observable, of as observableOf } from 'rxjs';
import { SubmissionService } from './submission.service';
import { SubmissionObject } from '../core/submission/models/submission-object.model';
import { RemoteData } from '../core/data/remote-data';
@Injectable()
export class ServerSubmissionService extends SubmissionService {
@@ -12,7 +13,7 @@ export class ServerSubmissionService extends SubmissionService {
return observableOf(null);
}
retrieveSubmission(submissionId): Observable<SubmissionObject> {
retrieveSubmission(submissionId): Observable<RemoteData<SubmissionObject>> {
return observableOf(null);
}

View File

@@ -6,7 +6,7 @@ import { distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/
import { ResponseCacheService } from '../core/cache/response-cache.service';
import { RequestService } from '../core/data/request.service';
import { ResponseCacheEntry } from '../core/cache/response-cache.reducer';
import { SubmissionSuccessResponse } from '../core/cache/response-cache.models';
import { ErrorResponse, RestResponse, SubmissionSuccessResponse } from '../core/cache/response-cache.models';
import { isNotEmpty } from '../shared/empty.util';
import {
DeleteRequest,
@@ -36,25 +36,15 @@ export class SubmissionRestService {
protected submitData(request: RestRequest): Observable<SubmitDataResponseDefinitionObject> {
const responses = this.responseCache.get(request.href).pipe(map((entry: ResponseCacheEntry) => entry.response));
const errorResponses = responses.pipe(
filter((response: SubmissionSuccessResponse) => !response.isSuccessful),
mergeMap(() => observableThrowError(new Error(`Couldn't send data to server`)))
filter((response: RestResponse) => !response.isSuccessful),
mergeMap((error: ErrorResponse) => observableThrowError(error))
);
const successResponses = responses.pipe(
filter((response: SubmissionSuccessResponse) => response.isSuccessful),
filter((response: RestResponse) => response.isSuccessful),
map((response: SubmissionSuccessResponse) => response.dataDefinition as any),
distinctUntilChanged()
);
return observableMerge(errorResponses, successResponses);
/* const [successResponse, errorResponse] = this.responseCache.get(request.href)
.map((entry: ResponseCacheEntry) => entry.response)
.partition((response: RestResponse) => response.isSuccessful);
return Observable.merge(
errorResponse.flatMap((response: ErrorResponse) =>
observableThrowError(new Error(`Couldn't send data to server`))),
successResponse
.filter((response: SubmissionSuccessResponse) => isNotEmpty(response))
.map((response: SubmissionSuccessResponse) => response.dataDefinition)
.distinctUntilChanged());*/
}
protected fetchRequest(request: RestRequest): Observable<SubmitDataResponseDefinitionObject> {
@@ -62,8 +52,8 @@ export class SubmissionRestService {
map((entry: ResponseCacheEntry) => entry.response),
tap(() => this.responseCache.remove(request.href)));
const errorResponses = responses.pipe(
filter((response: SubmissionSuccessResponse) => !response.isSuccessful),
mergeMap(() => observableThrowError(new Error(`Couldn't retrieve the data`)))
filter((response: RestResponse) => !response.isSuccessful),
mergeMap((error: ErrorResponse) => observableThrowError(error))
);
const successResponses = responses.pipe(
filter((response: SubmissionSuccessResponse) => response.isSuccessful && isNotEmpty(response)),
@@ -71,18 +61,6 @@ export class SubmissionRestService {
distinctUntilChanged()
);
return observableMerge(errorResponses, successResponses);
/* const [successResponse, errorResponse] = this.responseCache.get(request.href)
.map((entry: ResponseCacheEntry) => entry.response)
.do(() => this.responseCache.remove(request.href))
.partition((response: RestResponse) => response.isSuccessful);
return Observable.merge(
errorResponse.flatMap((response: ErrorResponse) =>
observableThrowError(new Error(`Couldn't retrieve the data`))),
successResponse
.filter((response: SubmissionSuccessResponse) => isNotEmpty(response))
.map((response: SubmissionSuccessResponse) => response.dataDefinition)
.distinctUntilChanged());*/
}
protected getEndpointByIDHref(endpoint, resourceID): string {

View File

@@ -30,9 +30,13 @@ import {
ResetSubmissionFormAction,
SaveAndDepositSubmissionAction,
SaveForLaterSubmissionFormAction,
SaveSubmissionFormAction, SaveSubmissionSectionFormAction,
SaveSubmissionFormAction,
SaveSubmissionSectionFormAction,
SetActiveSectionAction
} from './objects/submission-objects.actions';
import { RemoteData } from '../core/data/remote-data';
import { RemoteDataError } from '../core/data/remote-data-error';
import { throwError as observableThrowError } from 'rxjs/internal/observable/throwError';
describe('SubmissionService test suite', () => {
const config = MOCK_SUBMISSION_CONFIG;
@@ -363,7 +367,7 @@ describe('SubmissionService test suite', () => {
beforeEach(() => {
service = TestBed.get(SubmissionService);
spyOn((service as any).store, 'dispatch').and.callThrough()
spyOn((service as any).store, 'dispatch').and.callThrough();
});
describe('changeSubmissionCollection', () => {
@@ -840,11 +844,36 @@ describe('SubmissionService test suite', () => {
const result = service.retrieveSubmission('826');
const expected = cold('(b|)', {
b: mockSubmissionRestResponse[0]
b: new RemoteData(
false,
false,
true,
null,
mockSubmissionRestResponse[0])
});
expect(result).toBeObservable(expected);
});
it('should catch error from REST endpoint', () => {
(service as any).restService.getDataById.and.callFake(
() => observableThrowError({
statusCode: 500,
statusText: 'Internal Server Error',
errorMessage: 'Error message'
})
);
const result = service.retrieveSubmission('826').subscribe((r) => {
expect(r).toEqual(new RemoteData(
false,
false,
false,
new RemoteDataError(500, 'Internal Server Error', 'Error message'),
null
))
});
});
});
describe('setActiveSection', () => {

View File

@@ -17,7 +17,8 @@ import {
ResetSubmissionFormAction,
SaveAndDepositSubmissionAction,
SaveForLaterSubmissionFormAction,
SaveSubmissionFormAction, SaveSubmissionSectionFormAction,
SaveSubmissionFormAction,
SaveSubmissionSectionFormAction,
SetActiveSectionAction
} from './objects/submission-objects.actions';
import {
@@ -39,6 +40,9 @@ import { SectionsType } from './sections/sections-type';
import { NotificationsService } from '../shared/notifications/notifications.service';
import { SubmissionDefinitionsModel } from '../core/config/models/config-submission-definitions.model';
import { WorkspaceitemSectionsObject } from '../core/submission/models/workspaceitem-sections.model';
import { RemoteData } from '../core/data/remote-data';
import { ErrorResponse } from '../core/cache/response-cache.models';
import { RemoteDataError } from '../core/data/remote-data-error';
@Injectable()
export class SubmissionService {
@@ -252,7 +256,9 @@ export class SubmissionService {
}
redirectToMyDSpace() {
this.routeService.getPreviousUrl().subscribe((previousUrl: string) => {
this.routeService.getPreviousUrl().pipe(
first()
).subscribe((previousUrl: string) => {
if (isEmpty(previousUrl) || !previousUrl.startsWith('/mydspace')) {
this.router.navigate(['/mydspace']);
} else {
@@ -276,11 +282,26 @@ export class SubmissionService {
this.store.dispatch(new ResetSubmissionFormAction(collectionId, submissionId, selfUrl, sections, submissionDefinition));
}
retrieveSubmission(submissionId): Observable<SubmissionObject> {
retrieveSubmission(submissionId): Observable<RemoteData<SubmissionObject>> {
return this.restService.getDataById(this.getSubmissionObjectLinkName(), submissionId).pipe(
filter((submissionObjects: SubmissionObject[]) => isNotUndefined(submissionObjects)),
first(),
map((submissionObjects: SubmissionObject[]) => submissionObjects[0]));
map((submissionObjects: SubmissionObject[]) => new RemoteData(
false,
false,
true,
null,
submissionObjects[0])),
catchError((errorResponse: ErrorResponse) => {
return observableOf(new RemoteData(
false,
false,
false,
new RemoteDataError(errorResponse.statusCode, errorResponse.statusText, errorResponse.errorMessage),
null
))
})
);
}
setActiveSection(submissionId, sectionId) {