-
Notifications
You must be signed in to change notification settings - Fork 8.5k
[Lens] Visualization validation and better error messages #81439
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 12 commits
1bed7c0
bb93701
0aea6c5
d389d85
4c48b23
91a700a
b1d71dd
be2312d
c64603a
8e77401
64a22ef
7089b2f
86cc058
4e0c179
6672302
ee80a6b
f1127f4
95f9daf
ce7f857
d88d55d
694757b
0277aa5
822a23e
957bd98
7bfabd1
2fd7a90
4464285
e039c4f
a84fad2
d06f099
135c40d
b12a320
9f5d951
f984d5d
5e265ae
4b3c36a
c7fdd61
d0bce6b
cb0214e
d3fabc2
6c27723
9437162
4ac3bd8
6798112
8d31435
97dc1ae
33b71fa
0a0f5d3
37dca5a
fa32b19
1a440a9
cdb7054
11caf4a
8ccd1d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,16 @@ import classNames from 'classnames'; | |
| import { FormattedMessage } from '@kbn/i18n/react'; | ||
| import { Ast } from '@kbn/interpreter/common'; | ||
| import { i18n } from '@kbn/i18n'; | ||
| import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiButtonEmpty, EuiLink } from '@elastic/eui'; | ||
| import { | ||
| EuiFlexGroup, | ||
| EuiFlexItem, | ||
| EuiIcon, | ||
| EuiText, | ||
| EuiTextColor, | ||
| EuiButtonEmpty, | ||
| EuiLink, | ||
| EuiTitle, | ||
| } from '@elastic/eui'; | ||
| import { CoreStart, CoreSetup } from 'kibana/public'; | ||
| import { ExecutionContextSearch } from 'src/plugins/expressions'; | ||
| import { | ||
|
|
@@ -66,7 +75,8 @@ export interface WorkspacePanelProps { | |
| } | ||
|
|
||
| interface WorkspaceState { | ||
| expressionBuildError: string | undefined; | ||
| configurationValidationError?: Array<{ shortMessage: string; longMessage: string }>; | ||
| expressionBuildError?: Array<{ shortMessage: string; longMessage: string }>; | ||
| expandError: boolean; | ||
| } | ||
|
|
||
|
|
@@ -117,15 +127,40 @@ export function WorkspacePanel({ | |
| ); | ||
|
|
||
| const [localState, setLocalState] = useState<WorkspaceState>({ | ||
| expressionBuildError: undefined as string | undefined, | ||
| configurationValidationError: undefined, | ||
| expressionBuildError: undefined, | ||
| expandError: false, | ||
| }); | ||
|
|
||
| const activeVisualization = activeVisualizationId | ||
| ? visualizationMap[activeVisualizationId] | ||
| : null; | ||
|
|
||
| const expression = useMemo( | ||
| () => { | ||
| if (activeDatasourceId) { | ||
| const activeDatasource = activeDatasourceId ? datasourceMap[activeDatasourceId] : null; | ||
| const dataMessages = activeDatasource?.getErrorMessages( | ||
| datasourceStates[activeDatasourceId]?.state | ||
| ); | ||
| if (dataMessages) { | ||
| setLocalState((s) => ({ | ||
| ...s, | ||
| configurationValidationError: dataMessages, | ||
| })); | ||
| // TS will infer "void" if this is omitted | ||
| return; | ||
| } | ||
| } | ||
| const vizMessages = activeVisualization?.getErrorMessages(visualizationState, framePublicAPI); | ||
| if (vizMessages || localState.configurationValidationError) { | ||
| setLocalState((s) => ({ | ||
| ...s, | ||
| configurationValidationError: vizMessages, | ||
| })); | ||
| // TS will infer "void" if this is omitted | ||
| return; | ||
| } | ||
| try { | ||
| return buildExpression({ | ||
| visualization: activeVisualization, | ||
|
|
@@ -135,8 +170,19 @@ export function WorkspacePanel({ | |
| datasourceLayers: framePublicAPI.datasourceLayers, | ||
| }); | ||
| } catch (e) { | ||
| const buildMessages = activeVisualization?.getErrorMessages( | ||
| visualizationState, | ||
| framePublicAPI | ||
| ); | ||
| const defaultMessage = { | ||
| shortMessage: 'One error occurred in the expression', | ||
| longMessage: e.toString(), | ||
| }; | ||
| // Most likely an error in the expression provided by a datasource or visualization | ||
| setLocalState((s) => ({ ...s, expressionBuildError: e.toString() })); | ||
| setLocalState((s) => ({ | ||
| ...s, | ||
| expressionBuildError: buildMessages ?? [defaultMessage], | ||
| })); | ||
| } | ||
| }, | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
|
|
@@ -319,6 +365,36 @@ export const InnerVisualizationWrapper = ({ | |
| ] | ||
| ); | ||
|
|
||
| if (localState.configurationValidationError) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was caused on the same issue as the "building phase" error above. Fixing that, fixed this too.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is still an issue if there's a dimension configured for the break down by, but no metrics and the user starts dragging a field
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, thinking about it again what should happen is to blur the error message like a rendered chart if the user hovers over the workspace with their field. |
||
| return ( | ||
| <EuiFlexGroup style={{ maxWidth: '100%' }} direction="column" alignItems="center"> | ||
| <EuiFlexItem> | ||
| <EuiIcon type="alert" size="xl" color="danger" /> | ||
| </EuiFlexItem> | ||
| <EuiFlexItem data-test-subj="configuration-failure"> | ||
| <EuiTitle size="s"> | ||
| <EuiTextColor color="danger"> | ||
| <FormattedMessage | ||
| id="xpack.lens.editorFrame.configurationFailure" | ||
| defaultMessage="Invalid configuration" | ||
| /> | ||
| </EuiTextColor> | ||
| </EuiTitle> | ||
| </EuiFlexItem> | ||
| <EuiFlexItem>{localState.configurationValidationError[0].shortMessage}</EuiFlexItem> | ||
| <EuiFlexItem className="eui-textBreakAll"> | ||
| {localState.configurationValidationError[0].longMessage} | ||
| {localState.configurationValidationError.length > 1 | ||
| ? i18n.translate('xpack.lens.xyVisualization.dataFailureYLong', { | ||
| defaultMessage: ` + {errors} {errors, plural, one {error} other {errors}}`, | ||
| values: { errors: localState.configurationValidationError.length - 1 }, | ||
| }) | ||
| : null} | ||
| </EuiFlexItem> | ||
| </EuiFlexGroup> | ||
| ); | ||
| } | ||
|
|
||
| if (localState.expressionBuildError) { | ||
| return ( | ||
| <EuiFlexGroup style={{ maxWidth: '100%' }} direction="column" alignItems="center"> | ||
|
|
@@ -331,7 +407,7 @@ export const InnerVisualizationWrapper = ({ | |
| defaultMessage="An error occurred in the expression" | ||
| /> | ||
| </EuiFlexItem> | ||
| <EuiFlexItem grow={false}>{localState.expressionBuildError}</EuiFlexItem> | ||
| <EuiFlexItem grow={false}>{localState.expressionBuildError[0].longMessage}</EuiFlexItem> | ||
| </EuiFlexGroup> | ||
| ); | ||
| } | ||
|
|
@@ -346,6 +422,7 @@ export const InnerVisualizationWrapper = ({ | |
| onEvent={onEvent} | ||
| renderError={(errorMessage?: string | null, error?: ExpressionRenderError | null) => { | ||
| const visibleErrorMessage = getOriginalRequestErrorMessage(error) || errorMessage; | ||
|
|
||
| return ( | ||
| <EuiFlexGroup style={{ maxWidth: '100%' }} direction="column" alignItems="center"> | ||
| <EuiFlexItem> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,7 +39,7 @@ import { | |
| getDatasourceSuggestionsForVisualizeField, | ||
| } from './indexpattern_suggestions'; | ||
|
|
||
| import { isDraggedField, normalizeOperationDataType } from './utils'; | ||
| import { getInvalidReferences, isDraggedField, normalizeOperationDataType } from './utils'; | ||
| import { LayerPanel } from './layerpanel'; | ||
| import { IndexPatternColumn } from './operations'; | ||
| import { | ||
|
|
@@ -341,6 +341,32 @@ export function getIndexPatternDatasource({ | |
| }, | ||
| getDatasourceSuggestionsFromCurrentState, | ||
| getDatasourceSuggestionsForVisualizeField, | ||
|
|
||
| getErrorMessages(state) { | ||
| if (state) { | ||
|
||
| const invalidLayers = getInvalidReferences(state); | ||
| if (invalidLayers.length > 0) { | ||
| const realIndex = Object.values(state.layers) | ||
| .map((layer, i) => { | ||
| if (invalidLayers.includes(layer)) { | ||
| return i + 1; | ||
| } | ||
| }) | ||
| .filter(Boolean) as number[]; | ||
| return [ | ||
|
||
| { | ||
| shortMessage: i18n.translate('xpack.lens.indexPattern.dataReferenceFailureShort', { | ||
| defaultMessage: 'Invalid references', | ||
| }), | ||
| longMessage: i18n.translate('xpack.lens.indexPattern.dataReferenceFailureLong', { | ||
| defaultMessage: `{layers, plural, one {Layer} other {Layers}} {layersList} {layers, plural, one {has} other {have}} invalid reference`, | ||
| values: { layers: realIndex.length, layersList: realIndex.join(', ') }, | ||
| }), | ||
| }, | ||
| ]; | ||
| } | ||
| } | ||
| }, | ||
| }; | ||
|
|
||
| return indexPatternDatasource; | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This kicks in on intial state with an empty datatable:
I'm on the fence whether this is a "building state" (showing the "please drop field illustration") or an error state (showing the error message). If the user drags fields to the dimensions instead of the workspace, they will see this even if they didn't do anything wrong.
Your call if we want to treat it as a config error.
If yes: Please add a condition to this so it's not showing right away:
sortedColumns.length > 0If no: Let's remove this and simply return
nullinstead of an expression intoExpressionso no chart is renderedThinking about it I'm leaning towards treating it as a building error because that's what we do for xy charts:
