-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
[Lens] Visualization validation and better error messages #81439
[Lens] Visualization validation and better error messages #81439
Conversation
As users can get details of this error in the panel on the right, how about removing "Show details of error" and going with text like this: Missing X-axis Missing X-axis Missing metric @mdefazio Your thoughts? |
I am in favor of removing the extra click! Am I understanding this correctly that there would also be 'lower priority' errors that aren't shown in the main portion, but would appear on the right? If so, is another line like '+2 more errors' also needed? |
We could collect most of the errors on the right hand side in the main portion.
It could be an idea. I'll give it a try. |
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.
Tested the behavior and did some code review, and I have two main issues with the current behavior:
-
The new messages don't appear before running expressions. I was expecting that if you get into an invalid configuration, we would prevent the expression from running while showing help text. This means that we never get errors for XY charts.
-
The rendering of these looks way more like an error than a helpful piece of reminder text. I would prefer having two separate designs, one for "something bad" and one for "helpful reminder".
@@ -372,7 +388,7 @@ export const InnerVisualizationWrapper = ({ | |||
})} | |||
</EuiButtonEmpty> | |||
|
|||
{localState.expandError ? visibleErrorMessage : null} | |||
{localState.expandError ? longMessage || visibleErrorMessage : null} |
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.
I don't like the way this message is presented for anything but error messages. I would strongly prefer that we split this into 3 states, not 2:
- Visualization warning with all messages visible, but not a "scary" message
- Error state with warning triangle
2b. Error state with expanded details
|
||
getErrorMessage(state, frame) { | ||
// Is it possible to break it? | ||
return undefined; |
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.
I'm fine with requiring the function, but why return undefined;
? What about no return statement?
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.
The type system has a distinction between void
and undefined
values:
Type 'void' is not assignable to type '{ shortMessage: string; longMessage: string; } | undefined'.
createMockFramePublicAPI() | ||
) | ||
).not.toBeDefined(); | ||
}); |
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.
I know you were talking about this with @timroes, but I would like to get a summary of the decision about this one. I can imagine several ways of solving it, including putting it off for another PR.
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.
shortMessage: 'Some layers are missing the X dimension', | ||
longMessage: 'Layers 2, 3 have no dimension field set for the X axis', | ||
}); | ||
}); |
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.
I wasn't able to reproduce this error
).toEqual({ | ||
shortMessage: 'Some layers are missing the Y dimension', | ||
longMessage: 'Layer 1 has no dimension field set for the Y axis', | ||
}); |
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.
I wasn't able to reproduce this error
Discussed this with @dej611 and to keep this PR focus, we concluded on this approach:
As there are open questions about how missing dimension should be treated, let's start by just handling the case of broken field references in indexpattern datasource operations due to indexpattern updates. This can be done using this helper:
For visualizations, let's roll out usage of this new API in a separate PR |
Improved validation based on some of the feedback:
|
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.
I think we can reduce the text. Here is my suggestion.
First example
Invalid configuration
Layer 1 requires a field for the X-axis.
+1 error
"+1 error" might be easier to parse on a separate line
Singe invalid reference
Invalid configuration
Layer 1 has an invalid reference.
Multiple invalid references
Invalid configuration
Layer 1 has invalid references.
@elasticmachine merge upstream |
@@ -26,8 +26,8 @@ export const toExpression = ( | |||
state.layers.forEach((layer) => { | |||
metadata[layer.layerId] = {}; | |||
const datasource = datasourceLayers[layer.layerId]; | |||
datasource.getTableSpec().forEach((column) => { | |||
const operation = datasourceLayers[layer.layerId].getOperationForColumnId(column.columnId); | |||
datasource?.getTableSpec().forEach((column) => { |
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.
Why are these changes necessary? I see how they are fixing the functional test failure, but it's very concerning this is necessary. toExpression
should never be called without consistent datasource layers. I can't figure out what change in your PR caused that to happen.
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.
Please check the comment I left about the hook dependencies.
This is what I did:
- Reverted changes in
x-pack/plugins/lens/public/xy_visualization/to_expression.ts
- Follow instructions of failing functional test in
it('should transition from a multi-layer stacked bar to donut chart using suggestions', async () => { - Lens breaks
- Remove
frame
from the dependency list of the suggestionuseMemo
hook - Follow instructions of failing functional test again
- Switch works fine
It seems like we have some issue in there in how the UI updates (and an inconsistent state during switching) but let's move the fix for that to another PR to get these changes in.
const suggestionVisualization = visualizationMap[visualizationId]; | ||
// filter out visualizations with errors | ||
return ( | ||
suggestionVisualization.getErrorMessages(suggestionVisualizationState, frame) == null |
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 is not checking whether the datasource contains errors - we should use the same logic here as for the workspace panel.
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.
The check is below the new suggestion creation.
It may make sense to exit earlier one if that fails before producing new suggestions: I'll push an update.
@@ -202,14 +232,19 @@ export function SuggestionPanel({ | |||
) | |||
: undefined; | |||
|
|||
return { suggestions: newSuggestions, currentStateExpression: newStateExpression }; | |||
// eslint-disable-next-line react-hooks/exhaustive-deps |
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 was kept on purpose - otherwise the suggestions are recalculated too often because the frame object gets recreated on each render. This hook should not depend on the frame object. Maybe this is causing the issue with toExpression
I mentioned in the other comment.
I tracked the error down - it's happening because the suggestion panel is re-rendering with the datasource switch already applied but the visualization state update still pending. This causes the xy visualization trying to render the two layer visualization state based on the already one layered datasource state (which crashes). I will open a separate issue for this, for this PR, please remove |
activeDatasourceId, | ||
datasourceMap, | ||
datasourceStates, | ||
framePublicAPI, |
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.
SHouldn't be part of dependency list
const newSuggestions = getSuggestions({ | ||
datasourceMap, | ||
datasourceStates: currentDatasourceStates, | ||
visualizationMap, | ||
activeVisualizationId: currentVisualizationId, | ||
visualizationState: currentVisualizationState, | ||
}) | ||
.filter(({ visualizationId, visualizationState: suggestionVisualizationState }) => { |
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.
New order:
- filter out hidden suggestions
- filter out invalid suggestions
- slice
- map preview expression
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.
Tested and this LGTM, thanks a lot for all the iterations.
As we were concerned about performance, I tested this PR with a metricbeat indexpattern (4k fields). It's a little slow, but the validation is not a terrible contributor - in a simple drop event, it's only contributing 0.4ms, while getOperationSupportMatrix
takes 50ms. If we want to optimize, we should probably start there.
@@ -335,6 +341,81 @@ export function getIndexPatternDatasource({ | |||
}, | |||
getDatasourceSuggestionsFromCurrentState, | |||
getDatasourceSuggestionsForVisualizeField, | |||
|
|||
getErrorMessages(state) { | |||
if (state) { |
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.
minor nit: This function can be unnested by returning early making it a little easier to follow:
if (!state) return;
const invalidLayers = getInvalidReferences(state);
if (invaludLayers.length === 0) return;
...
💚 Build SucceededMetrics [docs]async chunks size
History
To update your PR or re-run it, just comment with: |
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.
LGTM!
}); | ||
}); | ||
|
||
it('should detect and batch missing references in a layer', () => { |
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.
I was able to test this, and also validated that if I didn't refresh the index pattern we would get a request error.
* master: (127 commits) [ILM] Fix breadcrumbs (elastic#82594) [UX]Swap env filter with percentile (elastic#82246) Add platform's missing READMEs (elastic#82268) [Discover] Adding uiMetric to track Visualize link click (elastic#82344) [Search] Add used index pattern name to the search agg error field (elastic#82604) improve client-side SO client get pooling (elastic#82603) [Security Solution] Unskips Overview tests (elastic#82459) Embeddables/migrations (elastic#82296) [Enterprise Search] Refactor product server route registrations to their own files/folders (elastic#82663) Moving reinstall function outside of promise.all (elastic#82672) Load choropleth layer correctly (elastic#82628) Master backport elastic#81233 (elastic#82642) [Fleet] Allow snake cased Kibana assets (elastic#77515) Reduce saved objects authorization checks (elastic#82204) [data.search] Add request handler context and asScoped pattern (elastic#80775) [ML] Fixes formatting of fields in index data visualizer (elastic#82593) Usage collector readme (elastic#82548) [Lens] Visualization validation and better error messages (elastic#81439) [ML] Add annotation markers to time series brush area to indicate annotations exist outside of selected range (elastic#81490) chore(NA): install microdnf in UBI docker build only (elastic#82611) ...
…) (#82811) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Summary
This PR is a partial take on the #80127, focused only at the visualization level, providing a better error message to the user in specific scenarios. A new PR will address also the problem at the datasource level next.
The code in this PR covers only the visualization expression building and evaluation phase.
getErrorMessage
methodXY Visualization
Datatable
Other chart types
There's currently no way to "break" other visualization types (pie and metric at the moment).
Building phase and errors
During a visualization building phase it's possible to fall into incomplete states, where no errors are shown. For an example the right hand side panel error message on the Y-axis dimension:
It would be nice to have a distinct state for this "incomplete" state from the "invalid" state, where a better message (rather than simply an error) is shown to the user.
Checklist