mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
# Backport This will backport the following commits from `main` to `8.16`: - [[canvas] fix All embeddables rebuilt on refresh (#209677)](https://github.com/elastic/kibana/pull/209677) <!--- Backport version: 9.6.4 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Nathan Reese","email":"reese.nathan@elastic.co"},"sourceCommit":{"committedDate":"2025-02-05T17:34:44Z","message":"[canvas] fix All embeddables rebuilt on refresh (#209677)\n\nFixes https://github.com/elastic/kibana/issues/209566\r\n\r\n### Problem\r\nAny input change causes Canvas embeddable's to get re-created. This\r\nmeans that setting a filter control or clicking the refresh button\r\ncauses embeddables to get re-created.\r\n\r\nIn the old embeddable system, the Canvas would only call\r\n`embeddable.updateInput` and `embeddable.reload` on [input\r\nchanges](https://github.com/elastic/kibana/blob/8.13/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx#L163).\r\n\r\n### Solution\r\nPR updates embeddable renderer to store embeddable API. Then, on input\r\nchanges, Canvas calls `embeddable.setFilters`.\r\n\r\nThere is no `embeddable.updateInput` equivalent in the new embeddable\r\nsystem. Instead, each state key needs to be updated by a setter. The\r\n[Canvas\r\ndocumentation](https://www.elastic.co/guide/en/kibana/current/canvas-function-reference.html#embeddable_fn)\r\nstates that the embeddable function only accepts `filters`. Therefore,\r\nthe only key that is expected to change from the input is `filters`.\r\nPlease correct me if this is an incorrect assumption.\r\n\r\n### Test instructions\r\n1) install sample web logs\r\n2) install canvas saved object and reload kibana (otherwise canvas is\r\nnot available in the nav menu)\r\n3) open new canvas\r\n4) add map embeddable\r\n5) add filter control. set source to sample web logs and field to\r\n`geo.dest`.\r\n<img width=\"200\" alt=\"Screenshot 2025-02-04 at 2 58 01 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/6862f0bc-4f61-4f16-aa7c-ea8008cfdbf9\"\r\n/>\r\n6) prefix map element expression with `kibana | selectFilter` so it\r\nlooks like `kibana | selectFilter | embeddable config=...`\r\n7) change filter. Verify map updates but map embeddable is not\r\nre-created.\r\n8) click refresh button, Verify map updates but is not re-created.\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"fe9023efffc2671cec0597b14950cc2a204e7ade","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Presentation","v9.0.0","backport:version","v8.18.0","v8.16.4","v8.17.2","v9.1.0","v8.19.0"],"title":"[canvas] fix All embeddables rebuilt on refresh","number":209677,"url":"https://github.com/elastic/kibana/pull/209677","mergeCommit":{"message":"[canvas] fix All embeddables rebuilt on refresh (#209677)\n\nFixes https://github.com/elastic/kibana/issues/209566\r\n\r\n### Problem\r\nAny input change causes Canvas embeddable's to get re-created. This\r\nmeans that setting a filter control or clicking the refresh button\r\ncauses embeddables to get re-created.\r\n\r\nIn the old embeddable system, the Canvas would only call\r\n`embeddable.updateInput` and `embeddable.reload` on [input\r\nchanges](https://github.com/elastic/kibana/blob/8.13/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx#L163).\r\n\r\n### Solution\r\nPR updates embeddable renderer to store embeddable API. Then, on input\r\nchanges, Canvas calls `embeddable.setFilters`.\r\n\r\nThere is no `embeddable.updateInput` equivalent in the new embeddable\r\nsystem. Instead, each state key needs to be updated by a setter. The\r\n[Canvas\r\ndocumentation](https://www.elastic.co/guide/en/kibana/current/canvas-function-reference.html#embeddable_fn)\r\nstates that the embeddable function only accepts `filters`. Therefore,\r\nthe only key that is expected to change from the input is `filters`.\r\nPlease correct me if this is an incorrect assumption.\r\n\r\n### Test instructions\r\n1) install sample web logs\r\n2) install canvas saved object and reload kibana (otherwise canvas is\r\nnot available in the nav menu)\r\n3) open new canvas\r\n4) add map embeddable\r\n5) add filter control. set source to sample web logs and field to\r\n`geo.dest`.\r\n<img width=\"200\" alt=\"Screenshot 2025-02-04 at 2 58 01 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/6862f0bc-4f61-4f16-aa7c-ea8008cfdbf9\"\r\n/>\r\n6) prefix map element expression with `kibana | selectFilter` so it\r\nlooks like `kibana | selectFilter | embeddable config=...`\r\n7) change filter. Verify map updates but map embeddable is not\r\nre-created.\r\n8) click refresh button, Verify map updates but is not re-created.\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"fe9023efffc2671cec0597b14950cc2a204e7ade"}},"sourceBranch":"main","suggestedTargetBranches":["8.16","8.17"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/209855","number":209855,"state":"OPEN"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/209853","number":209853,"state":"OPEN"},{"branch":"8.16","label":"v8.16.4","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.17","label":"v8.17.2","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/209677","number":209677,"mergeCommit":{"message":"[canvas] fix All embeddables rebuilt on refresh (#209677)\n\nFixes https://github.com/elastic/kibana/issues/209566\r\n\r\n### Problem\r\nAny input change causes Canvas embeddable's to get re-created. This\r\nmeans that setting a filter control or clicking the refresh button\r\ncauses embeddables to get re-created.\r\n\r\nIn the old embeddable system, the Canvas would only call\r\n`embeddable.updateInput` and `embeddable.reload` on [input\r\nchanges](https://github.com/elastic/kibana/blob/8.13/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx#L163).\r\n\r\n### Solution\r\nPR updates embeddable renderer to store embeddable API. Then, on input\r\nchanges, Canvas calls `embeddable.setFilters`.\r\n\r\nThere is no `embeddable.updateInput` equivalent in the new embeddable\r\nsystem. Instead, each state key needs to be updated by a setter. The\r\n[Canvas\r\ndocumentation](https://www.elastic.co/guide/en/kibana/current/canvas-function-reference.html#embeddable_fn)\r\nstates that the embeddable function only accepts `filters`. Therefore,\r\nthe only key that is expected to change from the input is `filters`.\r\nPlease correct me if this is an incorrect assumption.\r\n\r\n### Test instructions\r\n1) install sample web logs\r\n2) install canvas saved object and reload kibana (otherwise canvas is\r\nnot available in the nav menu)\r\n3) open new canvas\r\n4) add map embeddable\r\n5) add filter control. set source to sample web logs and field to\r\n`geo.dest`.\r\n<img width=\"200\" alt=\"Screenshot 2025-02-04 at 2 58 01 PM\"\r\nsrc=\"https://github.com/user-attachments/assets/6862f0bc-4f61-4f16-aa7c-ea8008cfdbf9\"\r\n/>\r\n6) prefix map element expression with `kibana | selectFilter` so it\r\nlooks like `kibana | selectFilter | embeddable config=...`\r\n7) change filter. Verify map updates but map embeddable is not\r\nre-created.\r\n8) click refresh button, Verify map updates but is not re-created.\r\n\r\n---------\r\n\r\nCo-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>","sha":"fe9023efffc2671cec0597b14950cc2a204e7ade"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"url":"https://github.com/elastic/kibana/pull/209854","number":209854,"state":"OPEN"}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
baf44fa969
commit
7bd84beb05
5 changed files with 80 additions and 26 deletions
|
@ -35,6 +35,7 @@ export {
|
|||
initializeTimeRange,
|
||||
type SerializedTimeRange,
|
||||
} from './interfaces/fetch/initialize_time_range';
|
||||
export { apiPublishesReload, type PublishesReload } from './interfaces/fetch/publishes_reload';
|
||||
export {
|
||||
apiPublishesFilters,
|
||||
apiPublishesPartialUnifiedSearch,
|
||||
|
|
|
@ -15,10 +15,18 @@ 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 {
|
||||
|
@ -35,11 +43,13 @@ import { embeddableService } from '../../../public/services/kibana_services';
|
|||
|
||||
const { embeddable: strings } = RendererStrings;
|
||||
|
||||
// registry of references to embeddables on the workpad
|
||||
// registry of references to legacy embeddables on the workpad
|
||||
const embeddablesRegistry: {
|
||||
[key: string]: IEmbeddable | Promise<IEmbeddable>;
|
||||
} = {};
|
||||
|
||||
const children: Record<string, { setFilters: (filters: Filter[] | undefined) => void }> = {};
|
||||
|
||||
const renderReactEmbeddable = ({
|
||||
type,
|
||||
uuid,
|
||||
|
@ -58,7 +68,13 @@ 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
|
||||
|
@ -82,6 +98,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);
|
||||
}
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -138,26 +170,32 @@ export const embeddableRendererFactory = (
|
|||
);
|
||||
|
||||
if (embeddableService.reactEmbeddableRegistryHasKey(embeddableType)) {
|
||||
/**
|
||||
* Prioritize React embeddables
|
||||
*/
|
||||
ReactDOM.render(
|
||||
renderReactEmbeddable({
|
||||
input,
|
||||
handlers,
|
||||
uuid: uniqueId,
|
||||
type: embeddableType,
|
||||
container: canvasApi,
|
||||
core,
|
||||
}),
|
||||
domNode,
|
||||
() => handlers.done()
|
||||
);
|
||||
const api = children[uniqueId];
|
||||
if (!api) {
|
||||
/**
|
||||
* Prioritize React embeddables
|
||||
*/
|
||||
ReactDOM.render(
|
||||
renderReactEmbeddable({
|
||||
input,
|
||||
handlers,
|
||||
uuid: uniqueId,
|
||||
type: embeddableType,
|
||||
container: canvasApi,
|
||||
core,
|
||||
}),
|
||||
domNode,
|
||||
() => handlers.done()
|
||||
);
|
||||
|
||||
handlers.onDestroy(() => {
|
||||
handlers.onEmbeddableDestroyed();
|
||||
return ReactDOM.unmountComponentAtNode(domNode);
|
||||
});
|
||||
handlers.onDestroy(() => {
|
||||
delete children[uniqueId];
|
||||
handlers.onEmbeddableDestroyed();
|
||||
return ReactDOM.unmountComponentAtNode(domNode);
|
||||
});
|
||||
} else {
|
||||
api.setFilters(input.filters);
|
||||
}
|
||||
} else if (!embeddablesRegistry[uniqueId]) {
|
||||
/**
|
||||
* Handle legacy embeddables - embeddable does not exist in registry
|
||||
|
|
|
@ -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