Skip to content
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

Merged
merged 54 commits into from
Nov 4, 2020

Conversation

dej611
Copy link
Contributor

@dej611 dej611 commented Oct 22, 2020

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.

  • ✨ all visualization have now a getErrorMessage method
  • ✨ some visualizations are showing a more accurate error message depending on the context, giving user's guidance on how to solve it
    • if the same error happens on multiple levels, these are batched and shown in a single message
    • there's a priority order for error message, so only one message per type is shown at the time to guide the user to a valid configuration
  • ✨ initial validation logic bootstrapped at visualization level
  • ✅ add basic testing for the visualization validation logic

XY Visualization

Screenshot 2020-10-21 at 18 11 50

Screenshot 2020-10-21 at 18 13 06

Datatable

Screenshot 2020-10-21 at 18 13 32

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:

Screenshot 2020-10-21 at 18 11 28

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

@dej611 dej611 added release_note:enhancement Team:Visualizations Visualization editors, elastic-charts and infrastructure v8.0.0 Feature:Lens v7.11.0 labels Oct 22, 2020
@dej611 dej611 requested a review from gchaps October 22, 2020 08:12
@gchaps
Copy link
Contributor

gchaps commented Oct 22, 2020

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
Layer 2 requires a field for the X-axis.

Missing X-axis
Layers 1,3 require fields for the X-axis.

Missing metric
Add a field in the Metrics panel.

@mdefazio Your thoughts?

@mdefazio
Copy link
Contributor

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
Layer 2 requires a field for the X-axis.

Missing X-axis
Layers 1,3 require fields for the X-axis.

Missing metric
Add a field in the Metrics panel.

@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?

@dej611
Copy link
Contributor Author

dej611 commented Oct 26, 2020

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?

We could collect most of the errors on the right hand side in the main portion.

If so, is another line like '+2 more errors' also needed?

It could be an idea. I'll give it a try.

Copy link
Contributor

@wylieconlon wylieconlon left a 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:

  1. 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.

  2. 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}
Copy link
Contributor

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:

  1. Visualization warning with all messages visible, but not a "scary" message
  2. Error state with warning triangle
    2b. Error state with expanded details


getErrorMessage(state, frame) {
// Is it possible to break it?
return undefined;
Copy link
Contributor

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?

Copy link
Contributor Author

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();
});
Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had an idea about, maybe it can be implemented in here. @timroes opened the #81789 issue about meanwhile.

shortMessage: 'Some layers are missing the X dimension',
longMessage: 'Layers 2, 3 have no dimension field set for the X axis',
});
});
Copy link
Contributor

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',
});
Copy link
Contributor

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

@flash1293
Copy link
Contributor

flash1293 commented Oct 27, 2020

Discussed this with @dej611 and to keep this PR focus, we concluded on this approach:

  • User changes something about configuration (a.k.a there's a state update)
  • Call getErrorMessages of datasource
  • Call getErrorMessages of visualization
  • If one of them returns error messages, show them in the workspace (example: inconsistent configuration error, fixable by the user by changing configuration)
  • If there are no error messages, build the expression
  • If building fails for some reason, show the exception error message in the workspace (example: buggy datasource/visualization, likely not fixable by the user, open an issue)
  • If building expression works, execute expression
  • If execution fails, show exception error message in workspace (example: runtime error, problem outside of Lens - index pattern issues, network issues, permission issues, ...)

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:

export function hasInvalidReference(state: IndexPatternPrivateState) {

For visualizations, let's roll out usage of this new API in a separate PR

@dej611
Copy link
Contributor Author

dej611 commented Oct 27, 2020

Improved validation based on some of the feedback:

  • getErrorMessage = > getErrorMessages with additional + X errors as suggested by @mdefazio

Screenshot 2020-10-27 at 18 56 40

  • ✨ New basic Datasource.getErrorMessages for invalid references as suggested by @flash1293

Screenshot 2020-10-27 at 18 36 33

Screenshot 2020-10-27 at 18 54 30

Copy link
Contributor

@gchaps gchaps left a 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.

@dej611
Copy link
Contributor Author

dej611 commented Oct 28, 2020

Updated the layout in case of more errors:

Screenshot 2020-10-28 at 13 00 35

@mdefazio
Copy link
Contributor

Updated the layout in case of more errors:

Screenshot 2020-10-28 at 13 00 35

Can we center the '+1 more' like we have the other messages?

@flash1293
Copy link
Contributor

@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) => {
Copy link
Contributor

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.

Copy link
Contributor

@flash1293 flash1293 left a 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 () => {
    ("should transition from a multi-layer stacked bar to donut chart using suggestions")
  • Lens breaks
  • Remove frame from the dependency list of the suggestion useMemo 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
Copy link
Contributor

@flash1293 flash1293 Nov 3, 2020

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.

Copy link
Contributor Author

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
Copy link
Contributor

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.

@flash1293
Copy link
Contributor

flash1293 commented Nov 3, 2020

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 frame from the useMemo dependency list and revert the to_expression changes to keep the current approach working.

activeDatasourceId,
datasourceMap,
datasourceStates,
framePublicAPI,
Copy link
Contributor

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 }) => {
Copy link
Contributor

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

Copy link
Contributor

@flash1293 flash1293 left a 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) {
Copy link
Contributor

@flash1293 flash1293 Nov 4, 2020

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;
...

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

async chunks size

id before after diff
lens 1020.4KB 1.0MB +12.5KB

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

Copy link
Contributor

@wylieconlon wylieconlon left a 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', () => {
Copy link
Contributor

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.

@dej611 dej611 merged commit 53ea090 into elastic:master Nov 4, 2020
@dej611 dej611 deleted the feature/lens/display-error-workspace branch November 4, 2020 17:28
gmmorris added a commit to gmmorris/kibana that referenced this pull request Nov 5, 2020
* 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)
  ...
dej611 added a commit to dej611/kibana that referenced this pull request Nov 6, 2020
)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
dej611 added a commit that referenced this pull request Nov 6, 2020
…) (#82811)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Lens release_note:enhancement Team:Visualizations Visualization editors, elastic-charts and infrastructure v7.11.0 v8.0.0
Projects
None yet
7 participants