mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Lens] Fix Introduce error boundary for visualizations (#137710)
* [Lens] Fix Introduce error boundary for visualizations Closes: #132582 * fix error handling for embeddable * update error_helper.ts * add checks for some methods * add memoizedErrorNotification * add more try/catch'es Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Joe Reuter <johannes.reuter@elastic.co>
This commit is contained in:
parent
fcf3b8bb21
commit
79763f2581
14 changed files with 382 additions and 170 deletions
|
@ -19,8 +19,9 @@ import { i18n } from '@kbn/i18n';
|
|||
import { RecursiveReadonly } from '@kbn/utility-types';
|
||||
import { Capabilities } from '@kbn/core/public';
|
||||
import { partition } from 'lodash';
|
||||
import { showMemoizedErrorNotification } from '../lens_ui_errors';
|
||||
import { TableInspectorAdapter } from '../editor_frame_service/types';
|
||||
import { Datasource } from '../types';
|
||||
import { Datasource, DatasourcePublicAPI } from '../types';
|
||||
|
||||
/**
|
||||
* Joins a series of queries.
|
||||
|
@ -89,11 +90,24 @@ export function getLayerMetaInfo(
|
|||
isVisible,
|
||||
};
|
||||
}
|
||||
const [firstLayerId] = currentDatasource.getLayers(datasourceState);
|
||||
const datasourceAPI = currentDatasource.getPublicAPI({
|
||||
layerId: firstLayerId,
|
||||
state: datasourceState,
|
||||
});
|
||||
|
||||
let datasourceAPI: DatasourcePublicAPI;
|
||||
|
||||
try {
|
||||
const [firstLayerId] = currentDatasource.getLayers(datasourceState);
|
||||
datasourceAPI = currentDatasource.getPublicAPI({
|
||||
layerId: firstLayerId,
|
||||
state: datasourceState,
|
||||
});
|
||||
} catch (error) {
|
||||
showMemoizedErrorNotification(error);
|
||||
|
||||
return {
|
||||
meta: undefined,
|
||||
error: error.message,
|
||||
isVisible,
|
||||
};
|
||||
}
|
||||
// maybe add also datasourceId validation here?
|
||||
if (datasourceAPI.datasourceId !== 'indexpattern') {
|
||||
return {
|
||||
|
|
|
@ -34,6 +34,7 @@ export { createFormulaPublicApi } from './indexpattern_datasource/operations/def
|
|||
|
||||
export * from './indexpattern_datasource';
|
||||
export * from './lens_ui_telemetry';
|
||||
export * from './lens_ui_errors';
|
||||
export * from './editor_frame_service/editor_frame';
|
||||
export * from './editor_frame_service';
|
||||
export * from './embeddable';
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
selectVisualization,
|
||||
} from '../../state_management';
|
||||
import type { LensInspector } from '../../lens_inspector_service';
|
||||
import { ErrorBoundary, showMemoizedErrorNotification } from '../../lens_ui_errors';
|
||||
|
||||
export interface EditorFrameProps {
|
||||
datasourceMap: DatasourceMap;
|
||||
|
@ -50,6 +51,7 @@ export function EditorFrame(props: EditorFrameProps) {
|
|||
const visualizationTypeIsKnown = Boolean(
|
||||
visualization.activeId && props.visualizationMap[visualization.activeId]
|
||||
);
|
||||
|
||||
const framePublicAPI: FramePublicAPI = useLensSelector((state) =>
|
||||
selectFramePublicAPI(state, datasourceMap)
|
||||
);
|
||||
|
@ -85,54 +87,66 @@ export function EditorFrame(props: EditorFrameProps) {
|
|||
[getSuggestionForField, dispatchLens]
|
||||
);
|
||||
|
||||
const onError = useCallback((error: Error) => {
|
||||
showMemoizedErrorNotification(error);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<RootDragDropProvider>
|
||||
<FrameLayout
|
||||
dataPanel={
|
||||
<DataPanelWrapper
|
||||
core={props.core}
|
||||
plugins={props.plugins}
|
||||
datasourceMap={datasourceMap}
|
||||
showNoDataPopover={props.showNoDataPopover}
|
||||
dropOntoWorkspace={dropOntoWorkspace}
|
||||
hasSuggestionForField={hasSuggestionForField}
|
||||
/>
|
||||
<ErrorBoundary onError={onError}>
|
||||
<DataPanelWrapper
|
||||
core={props.core}
|
||||
plugins={props.plugins}
|
||||
datasourceMap={datasourceMap}
|
||||
showNoDataPopover={props.showNoDataPopover}
|
||||
dropOntoWorkspace={dropOntoWorkspace}
|
||||
hasSuggestionForField={hasSuggestionForField}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
}
|
||||
configPanel={
|
||||
areDatasourcesLoaded && (
|
||||
<ConfigPanelWrapper
|
||||
core={props.core}
|
||||
datasourceMap={datasourceMap}
|
||||
visualizationMap={visualizationMap}
|
||||
framePublicAPI={framePublicAPI}
|
||||
uiActions={props.plugins.uiActions}
|
||||
/>
|
||||
<ErrorBoundary onError={onError}>
|
||||
<ConfigPanelWrapper
|
||||
core={props.core}
|
||||
datasourceMap={datasourceMap}
|
||||
visualizationMap={visualizationMap}
|
||||
framePublicAPI={framePublicAPI}
|
||||
uiActions={props.plugins.uiActions}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
workspacePanel={
|
||||
areDatasourcesLoaded &&
|
||||
isVisualizationLoaded && (
|
||||
<WorkspacePanel
|
||||
core={props.core}
|
||||
plugins={props.plugins}
|
||||
ExpressionRenderer={props.ExpressionRenderer}
|
||||
lensInspector={props.lensInspector}
|
||||
datasourceMap={datasourceMap}
|
||||
visualizationMap={visualizationMap}
|
||||
framePublicAPI={framePublicAPI}
|
||||
getSuggestionForField={getSuggestionForField.current}
|
||||
/>
|
||||
<ErrorBoundary onError={onError}>
|
||||
<WorkspacePanel
|
||||
core={props.core}
|
||||
plugins={props.plugins}
|
||||
ExpressionRenderer={props.ExpressionRenderer}
|
||||
lensInspector={props.lensInspector}
|
||||
datasourceMap={datasourceMap}
|
||||
visualizationMap={visualizationMap}
|
||||
framePublicAPI={framePublicAPI}
|
||||
getSuggestionForField={getSuggestionForField.current}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
suggestionsPanel={
|
||||
visualizationTypeIsKnown &&
|
||||
areDatasourcesLoaded && (
|
||||
<SuggestionPanelWrapper
|
||||
ExpressionRenderer={props.ExpressionRenderer}
|
||||
datasourceMap={datasourceMap}
|
||||
visualizationMap={visualizationMap}
|
||||
frame={framePublicAPI}
|
||||
/>
|
||||
<ErrorBoundary onError={onError}>
|
||||
<SuggestionPanelWrapper
|
||||
ExpressionRenderer={props.ExpressionRenderer}
|
||||
datasourceMap={datasourceMap}
|
||||
visualizationMap={visualizationMap}
|
||||
frame={framePublicAPI}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
VisualizeEditorContext,
|
||||
} from '../../types';
|
||||
import { buildExpression } from './expression_helpers';
|
||||
import { showMemoizedErrorNotification } from '../../lens_ui_errors';
|
||||
import { Document } from '../../persistence/saved_object_store';
|
||||
import { getActiveDatasourceIdFromDoc } from '../../utils';
|
||||
import { ErrorMessage } from '../types';
|
||||
|
@ -196,16 +197,29 @@ export const validateDatasourceAndVisualization = (
|
|||
currentVisualizationState: unknown | undefined,
|
||||
frameAPI: Pick<FramePublicAPI, 'datasourceLayers'>
|
||||
): ErrorMessage[] | undefined => {
|
||||
const datasourceValidationErrors = currentDatasourceState
|
||||
? currentDataSource?.getErrorMessages(currentDatasourceState)
|
||||
: undefined;
|
||||
try {
|
||||
const datasourceValidationErrors = currentDatasourceState
|
||||
? currentDataSource?.getErrorMessages(currentDatasourceState)
|
||||
: undefined;
|
||||
|
||||
const visualizationValidationErrors = currentVisualizationState
|
||||
? currentVisualization?.getErrorMessages(currentVisualizationState, frameAPI.datasourceLayers)
|
||||
: undefined;
|
||||
const visualizationValidationErrors = currentVisualizationState
|
||||
? currentVisualization?.getErrorMessages(currentVisualizationState, frameAPI.datasourceLayers)
|
||||
: undefined;
|
||||
|
||||
if (datasourceValidationErrors?.length || visualizationValidationErrors?.length) {
|
||||
return [...(datasourceValidationErrors || []), ...(visualizationValidationErrors || [])];
|
||||
if (datasourceValidationErrors?.length || visualizationValidationErrors?.length) {
|
||||
return [...(datasourceValidationErrors || []), ...(visualizationValidationErrors || [])];
|
||||
}
|
||||
} catch (e) {
|
||||
showMemoizedErrorNotification(e);
|
||||
if (e.message) {
|
||||
return [
|
||||
{
|
||||
shortMessage: e.message,
|
||||
longMessage: e.message,
|
||||
type: 'critical',
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { Datatable } from '@kbn/expressions-plugin/common';
|
||||
import type { PaletteOutput } from '@kbn/coloring';
|
||||
import { VisualizeFieldContext } from '@kbn/ui-actions-plugin/public';
|
||||
import { showMemoizedErrorNotification } from '../../lens_ui_errors';
|
||||
import {
|
||||
Visualization,
|
||||
Datasource,
|
||||
|
@ -85,34 +86,40 @@ export function getSuggestions({
|
|||
const datasourceTableSuggestions = datasources.flatMap(([datasourceId, datasource]) => {
|
||||
const datasourceState = datasourceStates[datasourceId].state;
|
||||
let dataSourceSuggestions;
|
||||
// context is used to pass the state from location to datasource
|
||||
if (visualizeTriggerFieldContext) {
|
||||
// used for navigating from VizEditor to Lens
|
||||
if ('isVisualizeAction' in visualizeTriggerFieldContext) {
|
||||
dataSourceSuggestions = datasource.getDatasourceSuggestionsForVisualizeCharts(
|
||||
|
||||
try {
|
||||
// context is used to pass the state from location to datasource
|
||||
if (visualizeTriggerFieldContext) {
|
||||
// used for navigating from VizEditor to Lens
|
||||
if ('isVisualizeAction' in visualizeTriggerFieldContext) {
|
||||
dataSourceSuggestions = datasource.getDatasourceSuggestionsForVisualizeCharts(
|
||||
datasourceState,
|
||||
visualizeTriggerFieldContext.layers
|
||||
);
|
||||
} else {
|
||||
// used for navigating from Discover to Lens
|
||||
dataSourceSuggestions = datasource.getDatasourceSuggestionsForVisualizeField(
|
||||
datasourceState,
|
||||
visualizeTriggerFieldContext.indexPatternId,
|
||||
visualizeTriggerFieldContext.fieldName
|
||||
);
|
||||
}
|
||||
} else if (field) {
|
||||
dataSourceSuggestions = datasource.getDatasourceSuggestionsForField(
|
||||
datasourceState,
|
||||
visualizeTriggerFieldContext.layers
|
||||
field,
|
||||
(layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]) // a field dragged to workspace should added to data layer
|
||||
);
|
||||
} else {
|
||||
// used for navigating from Discover to Lens
|
||||
dataSourceSuggestions = datasource.getDatasourceSuggestionsForVisualizeField(
|
||||
dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState(
|
||||
datasourceState,
|
||||
visualizeTriggerFieldContext.indexPatternId,
|
||||
visualizeTriggerFieldContext.fieldName
|
||||
(layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]),
|
||||
activeData
|
||||
);
|
||||
}
|
||||
} else if (field) {
|
||||
dataSourceSuggestions = datasource.getDatasourceSuggestionsForField(
|
||||
datasourceState,
|
||||
field,
|
||||
(layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]) // a field dragged to workspace should added to data layer
|
||||
);
|
||||
} else {
|
||||
dataSourceSuggestions = datasource.getDatasourceSuggestionsFromCurrentState(
|
||||
datasourceState,
|
||||
(layerId) => isLayerSupportedByVisualization(layerId, [layerTypes.DATA]),
|
||||
activeData
|
||||
);
|
||||
} catch (error) {
|
||||
showMemoizedErrorNotification(error);
|
||||
return [];
|
||||
}
|
||||
return dataSourceSuggestions.map((suggestion) => ({ ...suggestion, datasourceId }));
|
||||
});
|
||||
|
@ -211,26 +218,31 @@ function getVisualizationSuggestions(
|
|||
isFromContext?: boolean,
|
||||
activeData?: Record<string, Datatable>
|
||||
) {
|
||||
return visualization
|
||||
.getSuggestions({
|
||||
table,
|
||||
state: currentVisualizationState,
|
||||
keptLayerIds: datasourceSuggestion.keptLayerIds,
|
||||
subVisualizationId,
|
||||
mainPalette,
|
||||
isFromContext,
|
||||
activeData,
|
||||
})
|
||||
.map(({ state, ...visualizationSuggestion }) => ({
|
||||
...visualizationSuggestion,
|
||||
visualizationId,
|
||||
visualizationState: state,
|
||||
keptLayerIds: datasourceSuggestion.keptLayerIds,
|
||||
datasourceState: datasourceSuggestion.state,
|
||||
datasourceId: datasourceSuggestion.datasourceId,
|
||||
columns: table.columns.length,
|
||||
changeType: table.changeType,
|
||||
}));
|
||||
try {
|
||||
return visualization
|
||||
.getSuggestions({
|
||||
table,
|
||||
state: currentVisualizationState,
|
||||
keptLayerIds: datasourceSuggestion.keptLayerIds,
|
||||
subVisualizationId,
|
||||
mainPalette,
|
||||
isFromContext,
|
||||
activeData,
|
||||
})
|
||||
.map(({ state, ...visualizationSuggestion }) => ({
|
||||
...visualizationSuggestion,
|
||||
visualizationId,
|
||||
visualizationState: state,
|
||||
keptLayerIds: datasourceSuggestion.keptLayerIds,
|
||||
datasourceState: datasourceSuggestion.state,
|
||||
datasourceId: datasourceSuggestion.datasourceId,
|
||||
columns: table.columns.length,
|
||||
changeType: table.changeType,
|
||||
}));
|
||||
} catch (e) {
|
||||
showMemoizedErrorNotification(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export function switchToSuggestion(
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
} from '../../types';
|
||||
import { getSuggestions, switchToSuggestion } from './suggestion_helpers';
|
||||
import { getDatasourceExpressionsByLayers } from './expression_helpers';
|
||||
import { showMemoizedErrorNotification } from '../../lens_ui_errors/memoized_error_notification';
|
||||
import {
|
||||
getMissingIndexPattern,
|
||||
validateDatasourceAndVisualization,
|
||||
|
@ -493,39 +494,44 @@ function getPreviewExpression(
|
|||
datasourceLayers: { ...frame.datasourceLayers },
|
||||
};
|
||||
|
||||
// use current frame api and patch apis for changed datasource layers
|
||||
if (
|
||||
visualizableState.keptLayerIds &&
|
||||
visualizableState.datasourceId &&
|
||||
visualizableState.datasourceState
|
||||
) {
|
||||
const datasource = datasources[visualizableState.datasourceId];
|
||||
const datasourceState = visualizableState.datasourceState;
|
||||
const updatedLayerApis: DatasourceLayers = pick(
|
||||
frame.datasourceLayers,
|
||||
visualizableState.keptLayerIds
|
||||
try {
|
||||
// use current frame api and patch apis for changed datasource layers
|
||||
if (
|
||||
visualizableState.keptLayerIds &&
|
||||
visualizableState.datasourceId &&
|
||||
visualizableState.datasourceState
|
||||
) {
|
||||
const datasource = datasources[visualizableState.datasourceId];
|
||||
const datasourceState = visualizableState.datasourceState;
|
||||
const updatedLayerApis: DatasourceLayers = pick(
|
||||
frame.datasourceLayers,
|
||||
visualizableState.keptLayerIds
|
||||
);
|
||||
const changedLayers = datasource.getLayers(visualizableState.datasourceState);
|
||||
changedLayers.forEach((layerId) => {
|
||||
if (updatedLayerApis[layerId]) {
|
||||
updatedLayerApis[layerId] = datasource.getPublicAPI({
|
||||
layerId,
|
||||
state: datasourceState,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const datasourceExpressionsByLayers = getDatasourceExpressionsByLayers(
|
||||
datasources,
|
||||
datasourceStates
|
||||
);
|
||||
const changedLayers = datasource.getLayers(visualizableState.datasourceState);
|
||||
changedLayers.forEach((layerId) => {
|
||||
if (updatedLayerApis[layerId]) {
|
||||
updatedLayerApis[layerId] = datasource.getPublicAPI({
|
||||
layerId,
|
||||
state: datasourceState,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return visualization.toPreviewExpression(
|
||||
visualizableState.visualizationState,
|
||||
suggestionFrameApi.datasourceLayers,
|
||||
datasourceExpressionsByLayers ?? undefined
|
||||
);
|
||||
} catch (error) {
|
||||
showMemoizedErrorNotification(error);
|
||||
return null;
|
||||
}
|
||||
|
||||
const datasourceExpressionsByLayers = getDatasourceExpressionsByLayers(
|
||||
datasources,
|
||||
datasourceStates
|
||||
);
|
||||
|
||||
return visualization.toPreviewExpression(
|
||||
visualizableState.visualizationState,
|
||||
suggestionFrameApi.datasourceLayers,
|
||||
datasourceExpressionsByLayers ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
function preparePreviewExpression(
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
Suggestion,
|
||||
} from '../../../types';
|
||||
import { getSuggestions, switchToSuggestion } from '../suggestion_helpers';
|
||||
import { showMemoizedErrorNotification } from '../../../lens_ui_errors';
|
||||
import {
|
||||
insertLayer,
|
||||
removeLayers,
|
||||
|
@ -106,11 +107,23 @@ function computeListHeight(list: SelectableEntry[], maxHeight: number): number {
|
|||
return Math.min(list.length * ENTRY_HEIGHT, maxHeight);
|
||||
}
|
||||
|
||||
function safeFnCall<TReturn>(action: () => TReturn, defaultReturnValue: TReturn): TReturn {
|
||||
try {
|
||||
return action();
|
||||
} catch (error) {
|
||||
showMemoizedErrorNotification(error);
|
||||
return defaultReturnValue;
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentVisualizationId(
|
||||
activeVisualization: Visualization,
|
||||
visualizationState: unknown
|
||||
) {
|
||||
return activeVisualization.getVisualizationTypeId(visualizationState);
|
||||
return safeFnCall(
|
||||
() => activeVisualization.getVisualizationTypeId(visualizationState),
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
export const ChartSwitch = memo(function ChartSwitch(props: Props) {
|
||||
|
@ -150,9 +163,15 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) {
|
|||
subVisualizationId: string
|
||||
): VisualizationSelection {
|
||||
const newVisualization = props.visualizationMap[visualizationId];
|
||||
const switchVisType =
|
||||
props.visualizationMap[visualizationId].switchVisualizationType ||
|
||||
((_type: string, initialState: unknown) => initialState);
|
||||
const switchVisType = (type: string, state: unknown) => {
|
||||
if (props.visualizationMap[visualizationId].switchVisualizationType) {
|
||||
return safeFnCall(
|
||||
() => props.visualizationMap[visualizationId].switchVisualizationType!(type, state),
|
||||
state
|
||||
);
|
||||
}
|
||||
return state;
|
||||
};
|
||||
const layers = Object.entries(props.framePublicAPI.datasourceLayers);
|
||||
const containsData = layers.some(
|
||||
([_layerId, datasource]) => datasource && datasource.getTableSpec().length > 0
|
||||
|
@ -161,7 +180,10 @@ export const ChartSwitch = memo(function ChartSwitch(props: Props) {
|
|||
if (
|
||||
visualization.activeId === visualizationId &&
|
||||
visualization.state &&
|
||||
newVisualization.getVisualizationTypeId(visualization.state) === subVisualizationId
|
||||
safeFnCall(
|
||||
() => newVisualization.getVisualizationTypeId(visualization.state) === subVisualizationId,
|
||||
false
|
||||
)
|
||||
) {
|
||||
return {
|
||||
visualizationId,
|
||||
|
@ -502,7 +524,12 @@ function getTopSuggestion(
|
|||
// to avoid confusing the user.
|
||||
return (
|
||||
suggestion.changeType !== 'extended' &&
|
||||
newVisualization.getVisualizationTypeId(suggestion.visualizationState) === subVisualizationId
|
||||
safeFnCall(
|
||||
() =>
|
||||
newVisualization.getVisualizationTypeId(suggestion.visualizationState) ===
|
||||
subVisualizationId,
|
||||
false
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -143,6 +143,8 @@ export function getOriginalRequestErrorMessages(error?: ExpressionRenderError |
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (error?.message) {
|
||||
errorMessages.push(error?.message);
|
||||
}
|
||||
return errorMessages;
|
||||
}
|
||||
|
|
|
@ -273,7 +273,10 @@ export class Embeddable
|
|||
|
||||
this.lensInspector = getLensInspectorService(deps.inspector);
|
||||
this.expressionRenderer = deps.expressionRenderer;
|
||||
this.initializeSavedVis(initialInput).then(() => this.onContainerStateChanged(initialInput));
|
||||
this.initializeSavedVis(initialInput)
|
||||
.then(() => this.onContainerStateChanged(initialInput))
|
||||
.catch((e) => this.onFatalError(e));
|
||||
|
||||
this.subscription = this.getUpdated$().subscribe(() =>
|
||||
this.onContainerStateChanged(this.input)
|
||||
);
|
||||
|
|
|
@ -18,7 +18,11 @@ import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
|||
import { DataPublicPluginStart, FilterManager, TimefilterContract } from '@kbn/data-plugin/public';
|
||||
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
|
||||
import { ReactExpressionRendererType } from '@kbn/expressions-plugin/public';
|
||||
import { EmbeddableFactoryDefinition, IContainer } from '@kbn/embeddable-plugin/public';
|
||||
import {
|
||||
EmbeddableFactoryDefinition,
|
||||
IContainer,
|
||||
ErrorEmbeddable,
|
||||
} from '@kbn/embeddable-plugin/public';
|
||||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import { Start as InspectorStart } from '@kbn/inspector-plugin/public';
|
||||
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
|
@ -91,57 +95,61 @@ export class EmbeddableFactory implements EmbeddableFactoryDefinition {
|
|||
};
|
||||
|
||||
async create(input: LensEmbeddableInput, parent?: IContainer) {
|
||||
const {
|
||||
data,
|
||||
timefilter,
|
||||
expressionRenderer,
|
||||
documentToExpression,
|
||||
injectFilterReferences,
|
||||
visualizationMap,
|
||||
datasourceMap,
|
||||
uiActions,
|
||||
coreHttp,
|
||||
attributeService,
|
||||
indexPatternService,
|
||||
capabilities,
|
||||
usageCollection,
|
||||
theme,
|
||||
inspector,
|
||||
spaces,
|
||||
uiSettings,
|
||||
} = await this.getStartServices();
|
||||
|
||||
const { Embeddable } = await import('../async_services');
|
||||
|
||||
return new Embeddable(
|
||||
{
|
||||
attributeService,
|
||||
try {
|
||||
const {
|
||||
data,
|
||||
indexPatternService,
|
||||
timefilter,
|
||||
inspector,
|
||||
expressionRenderer,
|
||||
basePath: coreHttp.basePath,
|
||||
getTrigger: uiActions?.getTrigger,
|
||||
getTriggerCompatibleActions: uiActions?.getTriggerCompatibleActions,
|
||||
documentToExpression,
|
||||
injectFilterReferences,
|
||||
visualizationMap,
|
||||
datasourceMap,
|
||||
capabilities: {
|
||||
canSaveDashboards: Boolean(capabilities.dashboard?.showWriteControls),
|
||||
canSaveVisualizations: Boolean(capabilities.visualize.save),
|
||||
navLinks: capabilities.navLinks,
|
||||
discover: capabilities.discover,
|
||||
},
|
||||
uiActions,
|
||||
coreHttp,
|
||||
attributeService,
|
||||
indexPatternService,
|
||||
capabilities,
|
||||
usageCollection,
|
||||
theme,
|
||||
inspector,
|
||||
spaces,
|
||||
uiSettings,
|
||||
},
|
||||
input,
|
||||
parent
|
||||
);
|
||||
} = await this.getStartServices();
|
||||
|
||||
const { Embeddable } = await import('../async_services');
|
||||
|
||||
return new Embeddable(
|
||||
{
|
||||
attributeService,
|
||||
data,
|
||||
indexPatternService,
|
||||
timefilter,
|
||||
inspector,
|
||||
expressionRenderer,
|
||||
basePath: coreHttp.basePath,
|
||||
getTrigger: uiActions?.getTrigger,
|
||||
getTriggerCompatibleActions: uiActions?.getTriggerCompatibleActions,
|
||||
documentToExpression,
|
||||
injectFilterReferences,
|
||||
visualizationMap,
|
||||
datasourceMap,
|
||||
capabilities: {
|
||||
canSaveDashboards: Boolean(capabilities.dashboard?.showWriteControls),
|
||||
canSaveVisualizations: Boolean(capabilities.visualize.save),
|
||||
navLinks: capabilities.navLinks,
|
||||
discover: capabilities.discover,
|
||||
},
|
||||
usageCollection,
|
||||
theme,
|
||||
spaces,
|
||||
uiSettings,
|
||||
},
|
||||
input,
|
||||
parent
|
||||
);
|
||||
} catch (e) {
|
||||
return new ErrorEmbeddable(e, input, parent);
|
||||
}
|
||||
}
|
||||
|
||||
extract = extract;
|
||||
|
|
52
x-pack/plugins/lens/public/lens_ui_errors/error_boundary.tsx
Normal file
52
x-pack/plugins/lens/public/lens_ui_errors/error_boundary.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiErrorBoundary } from '@elastic/eui';
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: JSX.Element;
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
originalError?: Error;
|
||||
}
|
||||
|
||||
/** @internal **/
|
||||
const RecallError = ({ error }: { error: Error }) => {
|
||||
throw error;
|
||||
};
|
||||
|
||||
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
// Update state so the next render will show the fallback UI.
|
||||
return { originalError: error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error) {
|
||||
this.props.onError?.(error);
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
this.setState({ originalError: undefined });
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.state?.originalError ? (
|
||||
<EuiErrorBoundary>
|
||||
<RecallError error={this.state.originalError} />
|
||||
</EuiErrorBoundary>
|
||||
) : (
|
||||
this.props.children
|
||||
);
|
||||
}
|
||||
}
|
12
x-pack/plugins/lens/public/lens_ui_errors/index.ts
Normal file
12
x-pack/plugins/lens/public/lens_ui_errors/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { ErrorBoundary } from './error_boundary';
|
||||
export {
|
||||
initMemoizedErrorNotification,
|
||||
showMemoizedErrorNotification,
|
||||
} from './memoized_error_notification';
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { createGetterSetter } from '@kbn/kibana-utils-plugin/common';
|
||||
|
||||
const [getMemoizedErrorNotification, setMemoizedErrorNotification] = createGetterSetter<
|
||||
ReturnType<typeof memoizedErrorNotificationFactory>
|
||||
>('MemoizedErrorNotification', false);
|
||||
|
||||
const memoizedErrorNotificationFactory = (coreStart: CoreStart) => {
|
||||
const showedErrors = new Map<string, boolean>();
|
||||
return (error: Error) => {
|
||||
const { message } = error;
|
||||
|
||||
if (message) {
|
||||
if (!showedErrors.has(message)) {
|
||||
coreStart.notifications.toasts.addError(error, {
|
||||
title: i18n.translate('xpack.lens.uiErrors.unexpectedError', {
|
||||
defaultMessage: 'An unexpected error occurred.',
|
||||
}),
|
||||
});
|
||||
showedErrors.set(message, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const showMemoizedErrorNotification = (error: Error) => getMemoizedErrorNotification()?.(error);
|
||||
|
||||
const initMemoizedErrorNotification = (coreStart: CoreStart) => {
|
||||
setMemoizedErrorNotification(memoizedErrorNotificationFactory(coreStart));
|
||||
};
|
||||
|
||||
export { showMemoizedErrorNotification, initMemoizedErrorNotification };
|
|
@ -253,7 +253,8 @@ export class LensPlugin {
|
|||
const startServices = createStartServicesGetter(core.getStartServices);
|
||||
|
||||
const getStartServices = async (): Promise<LensEmbeddableStartServices> => {
|
||||
const { getLensAttributeService, setUsageCollectionStart } = await import('./async_services');
|
||||
const { getLensAttributeService, setUsageCollectionStart, initMemoizedErrorNotification } =
|
||||
await import('./async_services');
|
||||
const { core: coreStart, plugins } = startServices();
|
||||
|
||||
await this.initParts(
|
||||
|
@ -272,6 +273,8 @@ export class LensPlugin {
|
|||
setUsageCollectionStart(plugins.usageCollection);
|
||||
}
|
||||
|
||||
initMemoizedErrorNotification(coreStart);
|
||||
|
||||
return {
|
||||
attributeService: getLensAttributeService(coreStart, plugins),
|
||||
capabilities: coreStart.application.capabilities,
|
||||
|
@ -339,13 +342,17 @@ export class LensPlugin {
|
|||
eventAnnotation
|
||||
);
|
||||
|
||||
const { mountApp, getLensAttributeService, setUsageCollectionStart } = await import(
|
||||
'./async_services'
|
||||
);
|
||||
const {
|
||||
mountApp,
|
||||
getLensAttributeService,
|
||||
setUsageCollectionStart,
|
||||
initMemoizedErrorNotification,
|
||||
} = await import('./async_services');
|
||||
|
||||
if (deps.usageCollection) {
|
||||
setUsageCollectionStart(deps.usageCollection);
|
||||
}
|
||||
initMemoizedErrorNotification(coreStart);
|
||||
|
||||
const frameStart = this.editorFrameService!.start(coreStart, deps);
|
||||
return mountApp(core, params, {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue