fix(imex): show validation error details on import failure

Include failed field paths in DataValidationFailedError message and
display them in the snackbar, making it easier to debug import issues.
This commit is contained in:
Johannes Millan 2026-01-09 15:43:39 +01:00
parent 10d652a382
commit d2a13d21e3
3 changed files with 58 additions and 5 deletions

View file

@ -28,6 +28,7 @@ import {
DialogConfirmUrlImportData,
} from '../dialog-confirm-url-import/dialog-confirm-url-import.component';
import { Log } from '../../core/log';
import { DataValidationFailedError } from '../../pfapi/api/errors/errors';
@Component({
selector: 'file-imex',
@ -186,10 +187,19 @@ export class FileImexComponent implements OnInit {
// this._snackService.open({ type: 'SUCCESS', msg: 'Data imported successfully!' });
} catch (e) {
Log.err('Import process failed', e);
this._snackService.open({
type: 'ERROR',
msg: T.FILE_IMEX.S_ERR_IMPORT_FAILED,
});
if (e instanceof DataValidationFailedError) {
this._snackService.open({
type: 'ERROR',
msg: `Import failed: ${e.message}`,
isSkipTranslate: true,
});
} else {
this._snackService.open({
type: 'ERROR',
msg: T.FILE_IMEX.S_ERR_IMPORT_FAILED,
});
}
}
}

View file

@ -53,6 +53,8 @@ describe('DataValidationFailedError', () => {
const error = new DataValidationFailedError(validationResult as any);
expect(error.name).toBe('DataValidationFailedError');
expect(error.message).toContain('test.path');
expect(error.message).toContain('another.path');
expect(error.additionalLog).toBeDefined();
expect(error.additionalLog).toContain('test.path');
expect(consoleLogSpy).toHaveBeenCalledWith(
@ -66,6 +68,26 @@ describe('DataValidationFailedError', () => {
);
});
it('should include error paths in message with count for many errors', () => {
const validationResult = {
errors: [
{ path: 'path.one', expected: 'string', value: 1 },
{ path: 'path.two', expected: 'string', value: 2 },
{ path: 'path.three', expected: 'string', value: 3 },
{ path: 'path.four', expected: 'string', value: 4 },
{ path: 'path.five', expected: 'string', value: 5 },
],
};
const error = new DataValidationFailedError(validationResult as any);
expect(error.message).toContain('path.one');
expect(error.message).toContain('path.two');
expect(error.message).toContain('path.three');
expect(error.message).toContain('(+2 more)');
expect(error.message).not.toContain('path.four');
});
it('should truncate long error strings to 400 characters', () => {
const longError = { message: 'x'.repeat(500) };
const validationResult = {
@ -87,6 +109,7 @@ describe('DataValidationFailedError', () => {
const error = new DataValidationFailedError(validationResult as any);
expect(error.name).toBe('DataValidationFailedError');
expect(error.message).toBe('Data validation failed');
expect(consoleLogSpy).toHaveBeenCalledWith(
'[pf]',
'validation result: ',

View file

@ -427,7 +427,8 @@ export class DataValidationFailedError extends Error {
additionalLog?: string;
constructor(validationResult: IValidation<AllModelData<any>>) {
super('DataValidationFailedError');
const errorSummary = DataValidationFailedError._buildErrorSummary(validationResult);
super(errorSummary);
PFLog.log('validation result: ', validationResult);
try {
@ -441,6 +442,25 @@ export class DataValidationFailedError extends Error {
PFLog.err('Failed to stringify validation errors:', e);
}
}
private static _buildErrorSummary(
validationResult: IValidation<AllModelData<any>>,
): string {
try {
if ('errors' in validationResult && Array.isArray(validationResult.errors)) {
const errors = validationResult.errors as IValidation.IError[];
const paths = errors
.slice(0, 3)
.map((e) => e.path)
.join(', ');
const suffix = errors.length > 3 ? ` (+${errors.length - 3} more)` : '';
return `Validation failed at: ${paths}${suffix}`;
}
} catch {
// Fall through to default message
}
return 'Data validation failed';
}
}
export class ModelVersionToImportNewerThanLocalError extends AdditionalLogErrorBase {