-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Ingest Pipelines] Error messages (#70167)
* improved error messages * traverse recursive error struct * add check for object with keys * update button position and copy * size adjustments * Refactor i18n texts and change wording Also added missing translation and refactored maximum errors in collapsed state to external constant * use io-ts, add CIT and unit tests * refactor error utilities to separate file Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
- Loading branch information
1 parent
603ed13
commit 385ed6d
Showing
14 changed files
with
473 additions
and
42 deletions.
There are no files selected for viewing
117 changes: 117 additions & 0 deletions
117
x-pack/plugins/ingest_pipelines/__jest__/client_integration/fixtures.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
export const nestedProcessorsErrorFixture = { | ||
attributes: { | ||
error: { | ||
root_cause: [ | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
suppressed: [ | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
suppressed: [ | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'csv', | ||
}, | ||
], | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
], | ||
}, | ||
], | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
suppressed: [ | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
suppressed: [ | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'csv', | ||
}, | ||
], | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
{ | ||
type: 'parse_exception', | ||
reason: '[field] required property is missing', | ||
property_name: 'field', | ||
processor_type: 'circle', | ||
}, | ||
], | ||
}, | ||
status: 400, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 0 additions & 34 deletions
34
...gins/ingest_pipelines/public/application/components/pipeline_form/pipeline_form_error.tsx
This file was deleted.
Oops, something went wrong.
67 changes: 67 additions & 0 deletions
67
...lines/public/application/components/pipeline_form/pipeline_form_error/error_utils.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import { toKnownError } from './error_utils'; | ||
import { nestedProcessorsErrorFixture } from '../../../../../__jest__/client_integration/fixtures'; | ||
|
||
describe('toKnownError', () => { | ||
test('undefined, null, numbers, arrays and bad objects', () => { | ||
expect(toKnownError(undefined)).toEqual({ errors: [{ reason: 'An unknown error occurred.' }] }); | ||
expect(toKnownError(null)).toEqual({ errors: [{ reason: 'An unknown error occurred.' }] }); | ||
expect(toKnownError(123)).toEqual({ errors: [{ reason: 'An unknown error occurred.' }] }); | ||
expect(toKnownError([])).toEqual({ errors: [{ reason: 'An unknown error occurred.' }] }); | ||
expect(toKnownError({})).toEqual({ errors: [{ reason: 'An unknown error occurred.' }] }); | ||
expect(toKnownError({ attributes: {} })).toEqual({ | ||
errors: [{ reason: 'An unknown error occurred.' }], | ||
}); | ||
}); | ||
|
||
test('non-processors errors', () => { | ||
expect(toKnownError(new Error('my error'))).toEqual({ errors: [{ reason: 'my error' }] }); | ||
expect(toKnownError({ message: 'my error' })).toEqual({ errors: [{ reason: 'my error' }] }); | ||
}); | ||
|
||
test('processors errors', () => { | ||
expect(toKnownError(nestedProcessorsErrorFixture)).toMatchInlineSnapshot(` | ||
Object { | ||
"errors": Array [ | ||
Object { | ||
"processorType": "circle", | ||
"reason": "[field] required property is missing", | ||
}, | ||
Object { | ||
"processorType": "circle", | ||
"reason": "[field] required property is missing", | ||
}, | ||
Object { | ||
"processorType": "circle", | ||
"reason": "[field] required property is missing", | ||
}, | ||
Object { | ||
"processorType": "csv", | ||
"reason": "[field] required property is missing", | ||
}, | ||
Object { | ||
"processorType": "circle", | ||
"reason": "[field] required property is missing", | ||
}, | ||
Object { | ||
"processorType": "circle", | ||
"reason": "[field] required property is missing", | ||
}, | ||
Object { | ||
"processorType": "circle", | ||
"reason": "[field] required property is missing", | ||
}, | ||
Object { | ||
"processorType": "circle", | ||
"reason": "[field] required property is missing", | ||
}, | ||
], | ||
} | ||
`); | ||
}); | ||
}); |
85 changes: 85 additions & 0 deletions
85
..._pipelines/public/application/components/pipeline_form/pipeline_form_error/error_utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License; | ||
* you may not use this file except in compliance with the Elastic License. | ||
*/ | ||
|
||
import * as t from 'io-ts'; | ||
import { flow } from 'fp-ts/lib/function'; | ||
import { isRight } from 'fp-ts/lib/Either'; | ||
|
||
import { i18nTexts } from './i18n_texts'; | ||
|
||
export interface PipelineError { | ||
reason: string; | ||
processorType?: string; | ||
} | ||
interface PipelineErrors { | ||
errors: PipelineError[]; | ||
} | ||
|
||
interface ErrorNode { | ||
reason: string; | ||
processor_type?: string; | ||
suppressed?: ErrorNode[]; | ||
} | ||
|
||
// This is a runtime type (RT) for an error node which is a recursive type | ||
const errorNodeRT = t.recursion<ErrorNode>('ErrorNode', (ErrorNode) => | ||
t.intersection([ | ||
t.interface({ | ||
reason: t.string, | ||
}), | ||
t.partial({ | ||
processor_type: t.string, | ||
suppressed: t.array(ErrorNode), | ||
}), | ||
]) | ||
); | ||
|
||
// This is a runtime type for the attributes object we expect to receive from the server | ||
// for processor errors | ||
const errorAttributesObjectRT = t.interface({ | ||
attributes: t.interface({ | ||
error: t.interface({ | ||
root_cause: t.array(errorNodeRT), | ||
}), | ||
}), | ||
}); | ||
|
||
const isProcessorsError = flow(errorAttributesObjectRT.decode, isRight); | ||
|
||
type ErrorAttributesObject = t.TypeOf<typeof errorAttributesObjectRT>; | ||
|
||
const flattenErrorsTree = (node: ErrorNode): PipelineError[] => { | ||
const result: PipelineError[] = []; | ||
const recurse = (_node: ErrorNode) => { | ||
result.push({ reason: _node.reason, processorType: _node.processor_type }); | ||
if (_node.suppressed && Array.isArray(_node.suppressed)) { | ||
_node.suppressed.forEach(recurse); | ||
} | ||
}; | ||
recurse(node); | ||
return result; | ||
}; | ||
|
||
export const toKnownError = (error: unknown): PipelineErrors => { | ||
if (typeof error === 'object' && error != null && isProcessorsError(error)) { | ||
const errorAttributes = error as ErrorAttributesObject; | ||
const rootCause = errorAttributes.attributes.error.root_cause[0]; | ||
return { errors: flattenErrorsTree(rootCause) }; | ||
} | ||
|
||
if (typeof error === 'string') { | ||
return { errors: [{ reason: error }] }; | ||
} | ||
|
||
if ( | ||
error instanceof Error || | ||
(typeof error === 'object' && error != null && (error as any).message) | ||
) { | ||
return { errors: [{ reason: (error as any).message }] }; | ||
} | ||
|
||
return { errors: [{ reason: i18nTexts.errors.unknownError }] }; | ||
}; |
Oops, something went wrong.