-
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
Expressions service fixes: better error and loading states handling #51183
Merged
Dosant
merged 11 commits into
elastic:master
from
Dosant:bugfix/expression-service-render-swallows-errors
Nov 27, 2019
Merged
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
3655120
fix expressions service render swallows rendering errors #51153
Dosant 21d8c9c
fix expression service missing initial loading
Dosant ef46757
Merge branch 'master' into bugfix/expression-service-render-swallows-…
elasticmachine 33f16d4
Merge branch 'master' of github.com:elastic/kibana into bugfix/expres…
Dosant a9003a3
Merge branch 'bugfix/expression-service-render-swallows-errors' of gi…
Dosant 9a3282f
Render error inline for lens emedbale as it was previosly
Dosant 4351781
Add data-test-subj
Dosant 1a76b6f
Merge branch 'master' of github.com:elastic/kibana into bugfix/expres…
Dosant cca211e
fix ts errors - change RenderResult to RenderId
Dosant 4b730df
fix types
Dosant b37a36f
Merge branch 'master' into bugfix/expression-service-render-swallows-…
elasticmachine File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,12 +17,15 @@ | |
* under the License. | ||
*/ | ||
|
||
import { useRef, useEffect, useState } from 'react'; | ||
import { useRef, useEffect, useState, useLayoutEffect } from 'react'; | ||
import React from 'react'; | ||
import classNames from 'classnames'; | ||
import { Subscription } from 'rxjs'; | ||
import { filter } from 'rxjs/operators'; | ||
import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; | ||
import theme from '@elastic/eui/dist/eui_theme_light.json'; | ||
import { IExpressionLoaderParams } from './types'; | ||
import { useShallowCompareEffect } from '../../kibana_react/public'; | ||
import { IExpressionLoaderParams, IInterpreterRenderHandlers, RenderError } from './types'; | ||
import { ExpressionAST } from '../common/types'; | ||
import { ExpressionLoader } from './loader'; | ||
|
||
|
@@ -39,7 +42,7 @@ export interface ExpressionRendererProps extends IExpressionLoaderParams { | |
interface State { | ||
isEmpty: boolean; | ||
isLoading: boolean; | ||
error: null | { message: string }; | ||
error: null | RenderError; | ||
} | ||
|
||
export type ExpressionRenderer = React.FC<ExpressionRendererProps>; | ||
|
@@ -53,73 +56,94 @@ const defaultState: State = { | |
export const ExpressionRendererImplementation = ({ | ||
className, | ||
dataAttrs, | ||
expression, | ||
renderError, | ||
padding, | ||
...options | ||
renderError, | ||
expression, | ||
...expressionLoaderOptions | ||
}: ExpressionRendererProps) => { | ||
const mountpoint: React.MutableRefObject<null | HTMLDivElement> = useRef(null); | ||
const handlerRef: React.MutableRefObject<null | ExpressionLoader> = useRef(null); | ||
const [state, setState] = useState<State>({ ...defaultState }); | ||
const hasCustomRenderErrorHandler = !!renderError; | ||
const expressionLoaderRef: React.MutableRefObject<null | ExpressionLoader> = useRef(null); | ||
// flag to skip next render$ notification, | ||
// because of just handled error | ||
const hasHandledErrorRef = useRef(false); | ||
|
||
// Re-fetch data automatically when the inputs change | ||
/* eslint-disable react-hooks/exhaustive-deps */ | ||
useEffect(() => { | ||
if (handlerRef.current) { | ||
handlerRef.current.update(expression, options); | ||
} | ||
}, [ | ||
expression, | ||
options.searchContext, | ||
options.context, | ||
options.variables, | ||
options.disableCaching, | ||
]); | ||
/* eslint-enable react-hooks/exhaustive-deps */ | ||
// will call done() in LayoutEffect when done with rendering custom error state | ||
const errorRenderHandlerRef: React.MutableRefObject<null | IInterpreterRenderHandlers> = useRef( | ||
null | ||
); | ||
|
||
// Initialize the loader only once | ||
/* eslint-disable react-hooks/exhaustive-deps */ | ||
// OK to ignore react-hooks/exhaustive-deps because options update is handled by calling .update() | ||
useEffect(() => { | ||
if (mountpoint.current && !handlerRef.current) { | ||
handlerRef.current = new ExpressionLoader(mountpoint.current, expression, options); | ||
const subs: Subscription[] = []; | ||
expressionLoaderRef.current = new ExpressionLoader(mountpoint.current!, expression, { | ||
...expressionLoaderOptions, | ||
// react component wrapper provides different | ||
// error handling api which is easier to work with from react | ||
// if custom renderError is not provided then we fallback to default error handling from ExpressionLoader | ||
onRenderError: hasCustomRenderErrorHandler | ||
? (domNode, error, handlers) => { | ||
errorRenderHandlerRef.current = handlers; | ||
setState(() => ({ | ||
...defaultState, | ||
isEmpty: false, | ||
error, | ||
})); | ||
|
||
handlerRef.current.loading$.subscribe(() => { | ||
if (!handlerRef.current) { | ||
return; | ||
} | ||
if (expressionLoaderOptions.onRenderError) { | ||
expressionLoaderOptions.onRenderError(domNode, error, handlers); | ||
} | ||
} | ||
: expressionLoaderOptions.onRenderError, | ||
}); | ||
subs.push( | ||
expressionLoaderRef.current.loading$.subscribe(() => { | ||
hasHandledErrorRef.current = false; | ||
setState(prevState => ({ ...prevState, isLoading: true })); | ||
}); | ||
handlerRef.current.render$.subscribe(item => { | ||
if (!handlerRef.current) { | ||
return; | ||
} | ||
if (typeof item !== 'number') { | ||
}), | ||
expressionLoaderRef.current.render$ | ||
.pipe(filter(() => !hasHandledErrorRef.current)) | ||
.subscribe(item => { | ||
setState(() => ({ | ||
...defaultState, | ||
isEmpty: false, | ||
error: item.error, | ||
})); | ||
} else { | ||
setState(() => ({ | ||
...defaultState, | ||
isEmpty: false, | ||
})); | ||
} | ||
}); | ||
} | ||
/* eslint-disable */ | ||
// TODO: Replace mountpoint.current by something else. | ||
}, [mountpoint.current]); | ||
/* eslint-enable */ | ||
}) | ||
); | ||
|
||
useEffect(() => { | ||
// We only want a clean up to run when the entire component is unloaded, not on every render | ||
return function cleanup() { | ||
if (handlerRef.current) { | ||
handlerRef.current.destroy(); | ||
handlerRef.current = null; | ||
return () => { | ||
subs.forEach(s => s.unsubscribe()); | ||
if (expressionLoaderRef.current) { | ||
expressionLoaderRef.current.destroy(); | ||
expressionLoaderRef.current = null; | ||
} | ||
|
||
errorRenderHandlerRef.current = null; | ||
}; | ||
}, []); | ||
}, [hasCustomRenderErrorHandler]); | ||
|
||
// Re-fetch data automatically when the inputs change | ||
useShallowCompareEffect( | ||
() => { | ||
if (expressionLoaderRef.current) { | ||
expressionLoaderRef.current.update(expression, expressionLoaderOptions); | ||
} | ||
}, | ||
// when expression is changed by reference and when any other loaderOption is changed by reference | ||
[{ expression, ...expressionLoaderOptions }] | ||
); | ||
|
||
/* eslint-enable react-hooks/exhaustive-deps */ | ||
// call expression loader's done() handler when finished rendering custom error state | ||
useLayoutEffect(() => { | ||
if (state.error && errorRenderHandlerRef.current) { | ||
hasHandledErrorRef.current = true; | ||
errorRenderHandlerRef.current.done(); | ||
errorRenderHandlerRef.current = null; | ||
} | ||
}, [state.error]); | ||
|
||
const classes = classNames('expExpressionRenderer', { | ||
'expExpressionRenderer-isEmpty': state.isEmpty, | ||
|
@@ -135,15 +159,9 @@ export const ExpressionRendererImplementation = ({ | |
|
||
return ( | ||
<div {...dataAttrs} className={classes}> | ||
{state.isEmpty ? <EuiLoadingChart mono size="l" /> : null} | ||
{state.isLoading ? <EuiProgress size="xs" color="accent" position="absolute" /> : null} | ||
{!state.isLoading && state.error ? ( | ||
renderError ? ( | ||
renderError(state.error.message) | ||
) : ( | ||
<div data-test-subj="expression-renderer-error">{state.error.message}</div> | ||
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.
|
||
) | ||
) : null} | ||
{state.isEmpty && <EuiLoadingChart mono size="l" />} | ||
{state.isLoading && <EuiProgress size="xs" color="accent" position="absolute" />} | ||
{!state.isLoading && state.error && renderError && renderError(state.error.message)} | ||
<div | ||
className="expExpressionRenderer__expression" | ||
style={expressionStyles} | ||
|
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
You've changed the behavior this test was testing- why?
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.
Default error handling of
expression_renderer.tsx
now fallbacks to default error handling ofrender.ts
. This was done primarily to reduce number of code paths to support.Pls see: https://github.com/elastic/kibana/pull/51183/files#r349611820
And that is why test was deleted as we have less code paths in this component.
I guess I indeed missing the change, where I had to explicitly override error handling, where previous default of
expression_renderer.tsx
was expected.I found one missing place here:
x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx
and added error override, but as I understand this is for dashboard page?9a3282f
Any other places I am missing?
I am likely doing it wrong, but I think I am getting the same behaviour as in master. Blank screen. Any suggestions?