mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[canvas] fix All embeddables rebuilt on refresh (#209677)
Fixes https://github.com/elastic/kibana/issues/209566 ### Problem Any input change causes Canvas embeddable's to get re-created. This means that setting a filter control or clicking the refresh button causes embeddables to get re-created. In the old embeddable system, the Canvas would only call `embeddable.updateInput` and `embeddable.reload` on [input changes](https://github.com/elastic/kibana/blob/8.13/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx#L163). ### Solution PR updates embeddable renderer to store embeddable API. Then, on input changes, Canvas calls `embeddable.setFilters`. There is no `embeddable.updateInput` equivalent in the new embeddable system. Instead, each state key needs to be updated by a setter. The [Canvas documentation](https://www.elastic.co/guide/en/kibana/current/canvas-function-reference.html#embeddable_fn) states that the embeddable function only accepts `filters`. Therefore, the only key that is expected to change from the input is `filters`. Please correct me if this is an incorrect assumption. ### Test instructions 1) install sample web logs 2) install canvas saved object and reload kibana (otherwise canvas is not available in the nav menu) 3) open new canvas 4) add map embeddable 5) add filter control. set source to sample web logs and field to `geo.dest`. <img width="200" alt="Screenshot 2025-02-04 at 2 58 01 PM" src="https://github.com/user-attachments/assets/6862f0bc-4f61-4f16-aa7c-ea8008cfdbf9" /> 6) prefix map element expression with `kibana | selectFilter` so it looks like `kibana | selectFilter | embeddable config=...` 7) change filter. Verify map updates but map embeddable is not re-created. 8) click refresh button, Verify map updates but is not re-created. --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
ba0b1eca91
commit
fe9023efff
4 changed files with 77 additions and 23 deletions
|
@ -8,10 +8,18 @@
|
|||
import { CoreStart } from '@kbn/core/public';
|
||||
import { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import React, { FC } from 'react';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { useSearchApi } from '@kbn/presentation-publishing';
|
||||
import { omit } from 'lodash';
|
||||
import {
|
||||
AggregateQuery,
|
||||
COMPARE_ALL_OPTIONS,
|
||||
Filter,
|
||||
Query,
|
||||
TimeRange,
|
||||
onlyDisabledFiltersChanged,
|
||||
} from '@kbn/es-query';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { CANVAS_EMBEDDABLE_CLASSNAME } from '../../../common/lib';
|
||||
import { RendererStrings } from '../../../i18n';
|
||||
import {
|
||||
|
@ -27,6 +35,8 @@ import { useGetAppContext } from './use_get_app_context';
|
|||
|
||||
const { embeddable: strings } = RendererStrings;
|
||||
|
||||
const children: Record<string, { setFilters: (filters: Filter[] | undefined) => void }> = {};
|
||||
|
||||
const renderReactEmbeddable = ({
|
||||
type,
|
||||
uuid,
|
||||
|
@ -45,7 +55,14 @@ const renderReactEmbeddable = ({
|
|||
// wrap in functional component to allow usage of hooks
|
||||
const RendererWrapper: FC<{}> = () => {
|
||||
const getAppContext = useGetAppContext(core);
|
||||
const searchApi = useSearchApi({ filters: input.filters });
|
||||
|
||||
const searchApi = useMemo(() => {
|
||||
return {
|
||||
filters$: new BehaviorSubject<Filter[] | undefined>(input.filters),
|
||||
query$: new BehaviorSubject<Query | AggregateQuery | undefined>(undefined),
|
||||
timeRange$: new BehaviorSubject<TimeRange | undefined>(undefined),
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ReactEmbeddableRenderer
|
||||
|
@ -69,6 +86,22 @@ const renderReactEmbeddable = ({
|
|||
);
|
||||
if (newExpression) handlers.onEmbeddableInputChange(newExpression);
|
||||
}}
|
||||
onApiAvailable={(api) => {
|
||||
children[uuid] = {
|
||||
...api,
|
||||
setFilters: (filters: Filter[] | undefined) => {
|
||||
if (
|
||||
!onlyDisabledFiltersChanged(searchApi.filters$.getValue(), filters, {
|
||||
...COMPARE_ALL_OPTIONS,
|
||||
// do not compare $state to avoid refreshing when filter is pinned/unpinned (which does not impact results)
|
||||
state: false,
|
||||
})
|
||||
) {
|
||||
searchApi.filters$.next(filters);
|
||||
}
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -95,24 +128,30 @@ export const embeddableRendererFactory = (
|
|||
help: strings.getHelpDescription(),
|
||||
reuseDomNode: true,
|
||||
render: async (domNode, { input, embeddableType, canvasApi }, handlers) => {
|
||||
const uniqueId = handlers.getElementId();
|
||||
ReactDOM.render(
|
||||
renderReactEmbeddable({
|
||||
input,
|
||||
handlers,
|
||||
uuid: uniqueId,
|
||||
type: embeddableType,
|
||||
container: canvasApi,
|
||||
core,
|
||||
}),
|
||||
domNode,
|
||||
() => handlers.done()
|
||||
);
|
||||
const uuid = handlers.getElementId();
|
||||
const api = children[uuid];
|
||||
if (!api) {
|
||||
ReactDOM.render(
|
||||
renderReactEmbeddable({
|
||||
input,
|
||||
handlers,
|
||||
uuid,
|
||||
type: embeddableType,
|
||||
container: canvasApi,
|
||||
core,
|
||||
}),
|
||||
domNode,
|
||||
() => handlers.done()
|
||||
);
|
||||
|
||||
handlers.onDestroy(() => {
|
||||
handlers.onEmbeddableDestroyed();
|
||||
return ReactDOM.unmountComponentAtNode(domNode);
|
||||
});
|
||||
handlers.onDestroy(() => {
|
||||
delete children[uuid];
|
||||
handlers.onEmbeddableDestroyed();
|
||||
return ReactDOM.unmountComponentAtNode(domNode);
|
||||
});
|
||||
} else {
|
||||
api.setFilters(input.filters);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
|
||||
import { EmbeddableInput } from '@kbn/embeddable-plugin/common';
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
|
@ -19,6 +19,8 @@ import { METRIC_TYPE, trackCanvasUiMetric } from '../../lib/ui_metric';
|
|||
import { addElement } from '../../state/actions/elements';
|
||||
import { getSelectedPage } from '../../state/selectors/workpad';
|
||||
|
||||
const reload$ = new Subject<void>();
|
||||
|
||||
export const useCanvasApi: () => CanvasContainerApi = () => {
|
||||
const selectedPageId = useSelector(getSelectedPage);
|
||||
const dispatch = useDispatch();
|
||||
|
@ -38,6 +40,10 @@ export const useCanvasApi: () => CanvasContainerApi = () => {
|
|||
|
||||
const getCanvasApi = useCallback((): CanvasContainerApi => {
|
||||
return {
|
||||
reload$,
|
||||
reload: () => {
|
||||
reload$.next();
|
||||
},
|
||||
viewMode$: new BehaviorSubject<ViewMode>('edit'), // always in edit mode
|
||||
addNewPanel: async ({
|
||||
panelType,
|
||||
|
|
|
@ -15,6 +15,7 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||
import { fetchAllRenderables } from '../../../state/actions/elements';
|
||||
import { getInFlight } from '../../../state/selectors/resolved_args';
|
||||
import { ToolTipShortcut } from '../../tool_tip_shortcut';
|
||||
import { useCanvasApi } from '../../hooks/use_canvas_api';
|
||||
|
||||
const strings = {
|
||||
getRefreshAriaLabel: () =>
|
||||
|
@ -30,7 +31,11 @@ const strings = {
|
|||
export const RefreshControl = () => {
|
||||
const dispatch = useDispatch();
|
||||
const inFlight = useSelector(getInFlight);
|
||||
const doRefresh = useCallback(() => dispatch(fetchAllRenderables()), [dispatch]);
|
||||
const canvasApi = useCanvasApi();
|
||||
const doRefresh = useCallback(() => {
|
||||
canvasApi.reload();
|
||||
dispatch(fetchAllRenderables());
|
||||
}, [canvasApi, dispatch]);
|
||||
|
||||
return (
|
||||
<EuiToolTip
|
||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
|||
HasAppContext,
|
||||
HasDisableTriggers,
|
||||
HasType,
|
||||
PublishesReload,
|
||||
PublishesViewMode,
|
||||
PublishesUnifiedSearch,
|
||||
} from '@kbn/presentation-publishing';
|
||||
|
@ -28,4 +29,7 @@ export type CanvasContainerApi = PublishesViewMode &
|
|||
HasDisableTriggers &
|
||||
HasType &
|
||||
HasSerializedChildState &
|
||||
Partial<HasAppContext & PublishesUnifiedSearch>;
|
||||
PublishesReload &
|
||||
Partial<HasAppContext & PublishesUnifiedSearch> & {
|
||||
reload: () => void;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue