Skip to content

Commit d8da69f

Browse files
FrancescoMolinaroatarix83
authored andcommitted
Merged in task/dspace-cris-2023_02_x/DSC-1677 (pull request DSpace#1855)
[DSC-1677] implement error visualization for upload section, fix section state update, add tests Approved-by: Giuseppe Digilio
2 parents 2cbd7fe + fece8f3 commit d8da69f

14 files changed

Lines changed: 195 additions & 42 deletions

src/app/shared/form/builder/ds-dynamic-form-ui/models/onebox/dynamic-onebox.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ export class DsDynamicOneboxComponent extends DsDynamicVocabularyComponent imple
323323
this.previousValue = result;
324324
this.cdr.detectChanges();
325325
}
326-
if (hasValue(this.currentValue.otherInformation)) {
326+
if (hasValue(this.currentValue?.otherInformation)) {
327327
const infoKeys = Object.keys(this.currentValue.otherInformation);
328328
this.setMultipleValuesForOtherInfo(infoKeys, this.currentValue);
329329
}

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
mockSubmissionCollectionId,
1212
mockSubmissionId,
1313
mockSubmissionObject,
14-
mockUploadResponse1ParsedErrors,
1514
mockUploadResponse2Errors,
1615
mockUploadResponse2ParsedErrors
1716
} from '../../../shared/mocks/submission.mock';
@@ -154,17 +153,21 @@ describe('SubmissionUploadFilesComponent Component', () => {
154153
compAsAny.uploadEnabled = observableOf(true);
155154
});
156155

157-
it('should show a success notification and call updateSectionData if successful', () => {
158-
const expectedErrors: any = mockUploadResponse1ParsedErrors;
156+
it('should show a success notification and call updateSectionData if successful and no errors are present', () => {
157+
const expectedErrors: any = [];
159158
fixture.detectChanges();
159+
const data = {
160+
upload: {
161+
files: [{url: 'testUrl'}]
162+
}};
160163

161-
comp.onCompleteItem(Object.assign({}, uploadRestResponse, { sections: mockSectionsData }));
164+
comp.onCompleteItem(Object.assign({}, uploadRestResponse, { sections: data }));
162165

163-
Object.keys(mockSectionsData).forEach((sectionId) => {
166+
Object.keys(data).forEach((sectionId) => {
164167
expect(sectionsServiceStub.updateSectionData).toHaveBeenCalledWith(
165168
submissionId,
166169
sectionId,
167-
mockSectionsData[sectionId],
170+
data[sectionId],
168171
expectedErrors[sectionId],
169172
expectedErrors[sectionId]
170173
);

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import parseSectionErrors from '../../utils/parseSectionErrors';
1414
import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service';
1515
import { WorkspaceItem } from '../../../core/submission/models/workspaceitem.model';
1616
import { SectionsType } from '../../sections/sections-type';
17+
import { SubmissionSectionError } from '../../objects/submission-section-error.model';
18+
import { isNumeric } from '../../../shared/numeric.util';
19+
import { difference } from '../../../shared/object.util';
20+
import {
21+
WorkspaceitemSectionUploadFileObject
22+
} from '../../../core/submission/models/workspaceitem-section-upload-file.model';
1723

1824
/**
1925
* This component represents the drop zone that provides to add files to the submission.
@@ -130,17 +136,24 @@ export class SubmissionUploadFilesComponent implements OnChanges {
130136
Object.keys(sections)
131137
.forEach((sectionId) => {
132138
const sectionData = normalizeSectionData(sections[sectionId]);
139+
const sectionWarning = hasValue(sectionData.files) ? this.parseErrorsForWarning(sectionData.files, errorsList[sectionId]) : [];
140+
const errorsForErrorNotification = difference(errorsList[sectionId], sectionWarning) as SubmissionSectionError[];
133141
const sectionErrors = errorsList[sectionId];
142+
134143
this.sectionService.isSectionType(this.submissionId, sectionId, SectionsType.Upload)
135144
.pipe(take(1))
136145
.subscribe((isUpload) => {
137146
if (isUpload) {
138147
// Look for errors on upload
139-
if ((isEmpty(sectionErrors))) {
148+
if ((isEmpty(sectionErrors)) || !isEmpty(sectionWarning)) {
140149
this.notificationsService.success(null, this.translate.get('submission.sections.upload.upload-successful'));
141-
} else {
150+
} else if (!isEmpty(errorsForErrorNotification)) {
142151
this.notificationsService.error(null, this.translate.get('submission.sections.upload.upload-failed'));
143152
}
153+
154+
if (!(isEmpty(sectionWarning))) {
155+
this.notificationsService.warning(null, this.translate.get('submission.sections.upload.upload-warning'));
156+
}
144157
}
145158
});
146159
this.sectionService.updateSectionData(this.submissionId, sectionId, sectionData, sectionErrors, sectionErrors);
@@ -167,4 +180,22 @@ export class SubmissionUploadFilesComponent implements OnChanges {
167180
.filter((subscription) => hasValue(subscription))
168181
.forEach((subscription) => subscription.unsubscribe());
169182
}
183+
184+
/**
185+
* Check if there are errors related to metadata connected to files successfully uploaded
186+
*
187+
* @param files
188+
* @param sectionErrors
189+
* @private
190+
*/
191+
private parseErrorsForWarning(files: WorkspaceitemSectionUploadFileObject[], sectionErrors: SubmissionSectionError[]): SubmissionSectionError[] {
192+
return !isEmpty(sectionErrors) ? sectionErrors.filter((error) => {
193+
const errorPaths = error.path.split('/');
194+
const errorIndexPart = errorPaths[errorPaths.length - 1];
195+
// if index is not a number means that only one item is present
196+
const errorIndex = isNumeric(errorIndexPart) ? parseInt(errorIndexPart, 10) : 0;
197+
//we check if the files as an url with value, meaning the upload has been successfull
198+
return hasValue(files[errorIndex]) && hasValue(files[errorIndex].url);
199+
}) : [];
200+
}
170201
}

src/app/submission/objects/submission-objects.reducer.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -927,12 +927,20 @@ function editFileData(state: SubmissionObjectState, action: EditFileDataAction):
927927
*/
928928
function deleteFile(state: SubmissionObjectState, action: DeleteUploadedFileAction): SubmissionObjectState {
929929
const filesData = state[ action.payload.submissionId ].sections[ action.payload.sectionId ].data as WorkspaceitemSectionUploadObject;
930+
const filesErrorsToShow = state[ action.payload.submissionId ].sections[ action.payload.sectionId ].errorsToShow ?? [];
931+
const filesSeverValidationErrors = state[ action.payload.submissionId ].sections[ action.payload.sectionId ].serverValidationErrors ?? [];
932+
930933
if (hasValue(filesData.files)) {
931934
const fileIndex: any = findKey(
932935
filesData.files,
933936
{uuid: action.payload.fileId});
934937
if (isNotNull(fileIndex)) {
935938
const newData = Array.from(filesData.files);
939+
const newErrorsToShow = filesData.files.length > 1 ? filesErrorsToShow
940+
.filter(errorToShow => !errorToShow.path.includes(fileIndex)) : [];
941+
const newServerErrorsToShow = filesData.files.length > 1 ? filesSeverValidationErrors
942+
.filter(serverError => !serverError.path.includes(fileIndex)) : [];
943+
936944
newData.splice(fileIndex, 1);
937945
return Object.assign({}, state, {
938946
[ action.payload.submissionId ]: Object.assign({}, state[action.payload.submissionId], {
@@ -941,7 +949,9 @@ function deleteFile(state: SubmissionObjectState, action: DeleteUploadedFileActi
941949
[ action.payload.sectionId ]: Object.assign({}, state[ action.payload.submissionId ].sections[ action.payload.sectionId ], {
942950
data: Object.assign({}, state[ action.payload.submissionId ].sections[ action.payload.sectionId ].data, {
943951
files: newData
944-
})
952+
}),
953+
errorsToShow: newErrorsToShow,
954+
serverValidationErrors: newServerErrorsToShow,
945955
})
946956
})
947957
)

src/app/submission/sections/sections.directive.ts

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ export class SectionsDirective implements OnDestroy, OnInit {
9292
*/
9393
private subs: Subscription[] = [];
9494

95+
/**
96+
* Dedicated properties for error checking
97+
* @private
98+
*/
99+
private errorsSub: Subscription;
100+
95101
/**
96102
* A boolean representing if section is valid
97103
* @type {boolean}
@@ -119,29 +125,16 @@ export class SectionsDirective implements OnDestroy, OnInit {
119125
map((valid: boolean) => {
120126
if (valid) {
121127
this.resetErrors();
128+
} else if (hasValue(this.errorsSub)) {
129+
//create new subscription to set any possible remaining error, so to avoid timing issue in status update
130+
this.errorsSub.unsubscribe();
131+
this.checkForNewErrors();
122132
}
123133
return valid;
124134
}));
125135

136+
this.errorsSub = this.checkForNewErrors();
126137
this.subs.push(
127-
this.sectionService.getShownSectionErrors(this.submissionId, this.sectionId, this.sectionType)
128-
.subscribe((errors: SubmissionSectionError[]) => {
129-
if (isNotEmpty(errors)) {
130-
errors.forEach((errorItem: SubmissionSectionError) => {
131-
const parsedErrors: SectionErrorPath[] = parseSectionErrorPaths(errorItem.path);
132-
133-
parsedErrors.forEach((error: SectionErrorPath) => {
134-
if (!error.fieldId) {
135-
this.genericSectionErrors = uniq(this.genericSectionErrors.concat(errorItem.message));
136-
} else {
137-
this.allSectionErrors.push(errorItem.message);
138-
}
139-
});
140-
});
141-
} else {
142-
this.resetErrors();
143-
}
144-
}),
145138
this.submissionService.getActiveSectionId(this.submissionId)
146139
.subscribe((activeSectionId) => {
147140
const previousActive = this.active;
@@ -344,4 +337,25 @@ export class SectionsDirective implements OnDestroy, OnInit {
344337
this.allSectionErrors = [];
345338

346339
}
340+
341+
private checkForNewErrors() {
342+
return this.sectionService.getShownSectionErrors(this.submissionId, this.sectionId, this.sectionType)
343+
.subscribe((errors: SubmissionSectionError[]) => {
344+
if (isNotEmpty(errors)) {
345+
errors.forEach((errorItem: SubmissionSectionError) => {
346+
const parsedErrors: SectionErrorPath[] = parseSectionErrorPaths(errorItem.path);
347+
348+
parsedErrors.forEach((error: SectionErrorPath) => {
349+
if (!error.fieldId) {
350+
this.genericSectionErrors = uniq(this.genericSectionErrors.concat(errorItem.message));
351+
} else {
352+
this.allSectionErrors.push(errorItem.message);
353+
}
354+
});
355+
});
356+
} else {
357+
this.resetErrors();
358+
}
359+
});
360+
}
347361
}

src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ import {
4747
} from '../../../../../core/json-patch/builder/json-patch-operation-path-combiner';
4848
import { dateToISOFormat } from '../../../../../shared/date.util';
4949
import { of } from 'rxjs';
50+
import { SectionsService } from '../../../sections.service';
51+
import { SectionsServiceStub } from '../../../../../shared/testing/sections-service.stub';
5052

5153
const jsonPatchOpBuilder: any = jasmine.createSpyObj('jsonPatchOpBuilder', {
5254
add: jasmine.createSpy('add'),
@@ -62,6 +64,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
6264
let compAsAny: any;
6365
let fixture: ComponentFixture<SubmissionSectionUploadFileEditComponent>;
6466
let submissionServiceStub: SubmissionServiceStub;
67+
let sectionServiceStub: SectionsServiceStub;
6568
let formbuilderService: any;
6669
let operationsBuilder: any;
6770
let operationsService: any;
@@ -100,6 +103,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
100103
{ provide: SubmissionJsonPatchOperationsService, useValue: submissionJsonPatchOperationsServiceStub },
101104
{ provide: JsonPatchOperationsBuilder, useValue: jsonPatchOpBuilder },
102105
{ provide: SectionUploadService, useValue: getMockSectionUploadService() },
106+
{ provide: SectionsService, useClass: SectionsServiceStub },
103107
FormBuilderService,
104108
ChangeDetectorRef,
105109
SubmissionSectionUploadFileEditComponent,
@@ -151,6 +155,7 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
151155
comp = fixture.componentInstance;
152156
compAsAny = comp;
153157
submissionServiceStub = TestBed.inject(SubmissionService as any);
158+
sectionServiceStub = TestBed.inject(SectionsService as any);
154159
formbuilderService = TestBed.inject(FormBuilderService);
155160
operationsBuilder = TestBed.inject(JsonPatchOperationsBuilder);
156161
operationsService = TestBed.inject(SubmissionJsonPatchOperationsService);
@@ -325,7 +330,6 @@ describe('SubmissionSectionUploadFileEditComponent test suite', () => {
325330
expect(uploadService.updateFileData).not.toHaveBeenCalled();
326331

327332
}));
328-
329333
});
330334
});
331335

src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ import { DynamicFormControlCondition } from '@ng-dynamic-forms/core/lib/model/mi
6161
import { DynamicDateControlValue } from '@ng-dynamic-forms/core/lib/model/dynamic-date-control.model';
6262
import { DynamicSelectModelConfig } from '@ng-dynamic-forms/core/lib/model/select/dynamic-select.model';
6363
import { TranslateService } from '@ngx-translate/core';
64+
import parseSectionErrors from '../../../../utils/parseSectionErrors';
65+
import { SectionsService } from '../../../sections.service';
66+
import { normalizeSectionData } from '../../../../../core/submission/submission-response-parsing.service';
6467

6568
/**
6669
* This component represents the edit form for bitstream
@@ -181,6 +184,8 @@ export class SubmissionSectionUploadFileEditComponent
181184
* @param {JsonPatchOperationsBuilder} operationsBuilder
182185
* @param {SubmissionJsonPatchOperationsService} operationsService
183186
* @param {SectionUploadService} uploadService
187+
* @param translate
188+
* @param sectionService
184189
*/
185190
constructor(
186191
protected activeModal: NgbActiveModal,
@@ -192,6 +197,7 @@ export class SubmissionSectionUploadFileEditComponent
192197
private operationsService: SubmissionJsonPatchOperationsService,
193198
private uploadService: SectionUploadService,
194199
private translate: TranslateService,
200+
private sectionService: SectionsService,
195201
) {
196202
}
197203

@@ -478,12 +484,20 @@ export class SubmissionSectionUploadFileEditComponent
478484
})
479485
).subscribe((result: SubmissionObject[]) => {
480486
if (result[0].sections[this.sectionId]) {
481-
const uploadSection = (result[0].sections[this.sectionId] as WorkspaceitemSectionUploadObject);
487+
const resultSection = result[0].sections[this.sectionId];
488+
const uploadSection = (resultSection as WorkspaceitemSectionUploadObject);
489+
const { errors } = result[0];
490+
const errorsList = parseSectionErrors(errors);
491+
const sectionData = normalizeSectionData(resultSection);
492+
const sectionErrors = errorsList[this.sectionId];
493+
482494
Object.keys(uploadSection.files)
483495
.filter((key) => uploadSection.files[key].uuid === this.fileId)
484496
.forEach((key) => this.uploadService.updateFileData(
485497
this.submissionId, this.sectionId, this.fileId, uploadSection.files[key])
486498
);
499+
500+
this.sectionService.updateSectionData(this.submissionId, this.sectionId, sectionData, sectionErrors, sectionErrors);
487501
}
488502
this.isSaving = false;
489503
this.activeModal.close();

src/app/submission/sections/upload/file/section-upload-file.component.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<ng-container *ngIf="fileData">
2-
<div class="row">
2+
<div
3+
class="row py-1"
4+
>
35
<div class="col-md-12">
46
<div class="float-left ml-3 mb-2 badge badge-pill bg-primary text-white" *ngIf="(vocabularyFileType$ | async)">
57
{{(vocabularyFileType$ | async)}}
@@ -35,6 +37,12 @@ <h4>{{fileName}} <span class="text-secondary">({{fileData?.sizeBytes | dsFileSiz
3537
<ds-submission-section-upload-file-view [fileData]="fileData"></ds-submission-section-upload-file-view>
3638
</div>
3739
</div>
40+
41+
<div class="pt-4" *ngIf="(getFileHasErrors() | async)">
42+
<ds-alert [type]="AlertTypeEnum.Error" [dismissible]="false">
43+
<span [innerHTML]="'submission.sections.upload.genericError' | translate"></span>
44+
</ds-alert>
45+
</div>
3846
</ng-container>
3947

4048
<ng-template #content let-c="close" let-d="dismiss">

0 commit comments

Comments
 (0)