[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:
Alexey Antonov 2022-08-12 12:41:28 +03:00 committed by GitHub
parent fcf3b8bb21
commit 79763f2581
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 382 additions and 170 deletions

View file

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

View file

@ -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';

View file

@ -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>
)
}
/>

View file

@ -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;
};

View file

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

View file

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

View file

@ -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
)
);
});

View file

@ -143,6 +143,8 @@ export function getOriginalRequestErrorMessages(error?: ExpressionRenderError |
}
}
}
} else if (error?.message) {
errorMessages.push(error?.message);
}
return errorMessages;
}

View file

@ -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)
);

View file

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

View 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
);
}
}

View 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';

View file

@ -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 };

View file

@ -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, {