mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[canvas] fix parent API should implement unified search interface with react embeddables (#183772)
Closes https://github.com/elastic/kibana/issues/183766
### test instructions
1. start kibana with `yarn start --run-examples`
2. install sample web logs data set
3. create new canvas work pad
4. Click "Add element" -> "Filter" -> "Dropdown select"
5. Select "data" tab and configure filter to pull from logs sample data
and `machine.os.keyword` field.
6. Select "display" tab and "Value column" and "Filter column" to
`machine.os.keyword`.
7. Click "Add element" -> "Filter" -> "Time filter"
9. Click "Select type" -> "Search example"
10. Click panel context menu -> "Settings" and turn off custom time
range
11. Click "Expression editor" button in lower right corner to open the
expression editor for the map and prefix the expression with `kibana |
selectFilter | embeddable`
<img width="500" alt="Screenshot 2024-05-17 at 11 50 42 AM"
src="453096d5
-c254-4f43-a09c-386586aa05a1">
12. Interact with drop down filter and time range filter. Verify Search
react embeddable updates as expected
This commit is contained in:
parent
a7cb2271e1
commit
f8c582da25
6 changed files with 64 additions and 18 deletions
|
@ -27,10 +27,16 @@ export const registerAddSearchPanelAction = (uiActions: UiActionsStart) => {
|
|||
embeddable.addNewPanel<SearchSerializedState>(
|
||||
{
|
||||
panelType: SEARCH_EMBEDDABLE_ID,
|
||||
initialState: {},
|
||||
},
|
||||
true
|
||||
);
|
||||
},
|
||||
});
|
||||
uiActions.attachAction('ADD_PANEL_TRIGGER', ADD_SEARCH_ACTION_ID);
|
||||
if (uiActions.hasTrigger('ADD_CANVAS_ELEMENT_TRIGGER')) {
|
||||
// Because Canvas is not enabled in Serverless, this trigger might not be registered - only attach
|
||||
// the create action if the Canvas-specific trigger does indeed exist.
|
||||
uiActions.attachAction('ADD_CANVAS_ELEMENT_TRIGGER', ADD_SEARCH_ACTION_ID);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import React, { useMemo } from 'react';
|
||||
import { TimeRange } from '@kbn/es-query';
|
||||
import { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public';
|
||||
import { useSearchApi } from '@kbn/presentation-publishing';
|
||||
import type { SearchApi, SearchSerializedState } from './types';
|
||||
import { SEARCH_EMBEDDABLE_ID } from './constants';
|
||||
|
||||
|
@ -28,23 +28,15 @@ export function SearchEmbeddableRenderer(props: Props) {
|
|||
// only run onMount
|
||||
}, []);
|
||||
|
||||
const parentApi = useMemo(() => {
|
||||
return {
|
||||
timeRange$: new BehaviorSubject(props.timeRange),
|
||||
getSerializedStateForChild: () => initialState,
|
||||
};
|
||||
// only run onMount
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
parentApi.timeRange$.next(props.timeRange);
|
||||
}, [props.timeRange, parentApi.timeRange$]);
|
||||
const searchApi = useSearchApi({ timeRange: props.timeRange });
|
||||
|
||||
return (
|
||||
<ReactEmbeddableRenderer<SearchSerializedState, SearchApi>
|
||||
type={SEARCH_EMBEDDABLE_ID}
|
||||
getParentApi={() => parentApi}
|
||||
getParentApi={() => ({
|
||||
...searchApi,
|
||||
getSerializedStateForChild: () => initialState,
|
||||
})}
|
||||
hidePanelChrome={true}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -39,6 +39,7 @@ export {
|
|||
apiPublishesTimeRange,
|
||||
apiPublishesUnifiedSearch,
|
||||
apiPublishesWritableUnifiedSearch,
|
||||
useSearchApi,
|
||||
type PublishesTimeRange,
|
||||
type PublishesUnifiedSearch,
|
||||
type PublishesWritableUnifiedSearch,
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { TimeRange, Filter, Query, AggregateQuery } from '@kbn/es-query';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { PublishingSubject } from '../../publishing_subject';
|
||||
|
||||
export interface PublishesTimeRange {
|
||||
|
@ -68,3 +70,40 @@ export const apiPublishesWritableUnifiedSearch = (
|
|||
typeof (unknownApi as PublishesWritableUnifiedSearch).setQuery === 'function'
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* React hook that converts search props into search observable API
|
||||
*/
|
||||
export function useSearchApi({
|
||||
filters,
|
||||
query,
|
||||
timeRange,
|
||||
}: {
|
||||
filters?: Filter[];
|
||||
query?: Query | AggregateQuery;
|
||||
timeRange?: TimeRange;
|
||||
}) {
|
||||
const searchApi = useMemo(() => {
|
||||
return {
|
||||
filters$: new BehaviorSubject<Filter[] | undefined>(filters),
|
||||
query$: new BehaviorSubject<Query | AggregateQuery | undefined>(query),
|
||||
timeRange$: new BehaviorSubject<TimeRange | undefined>(timeRange),
|
||||
};
|
||||
// filters, query, timeRange only used as initial values. Changes do not effect memoized value
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
searchApi.filters$.next(filters);
|
||||
}, [filters, searchApi.filters$]);
|
||||
|
||||
useEffect(() => {
|
||||
searchApi.query$.next(query);
|
||||
}, [query, searchApi.query$]);
|
||||
|
||||
useEffect(() => {
|
||||
searchApi.timeRange$.next(timeRange);
|
||||
}, [timeRange, searchApi.timeRange$]);
|
||||
|
||||
return searchApi;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import React, { FC } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { useSearchApi } from '@kbn/presentation-publishing';
|
||||
import { omit } from 'lodash';
|
||||
import { pluginServices } from '../../../public/services';
|
||||
import { CANVAS_EMBEDDABLE_CLASSNAME } from '../../../common/lib';
|
||||
|
@ -57,6 +58,7 @@ const renderReactEmbeddable = ({
|
|||
// wrap in functional component to allow usage of hooks
|
||||
const RendererWrapper: FC<{}> = () => {
|
||||
const getAppContext = useGetAppContext(core);
|
||||
const searchApi = useSearchApi({ filters: input.filters });
|
||||
|
||||
return (
|
||||
<ReactEmbeddableRenderer
|
||||
|
@ -66,8 +68,9 @@ const renderReactEmbeddable = ({
|
|||
...container,
|
||||
getAppContext,
|
||||
getSerializedStateForChild: () => ({
|
||||
rawState: omit(input, 'disableTriggers'),
|
||||
rawState: omit(input, ['disableTriggers', 'filters']),
|
||||
}),
|
||||
...searchApi,
|
||||
})}
|
||||
key={`${type}_${uuid}`}
|
||||
onAnyStateChange={(newState) => {
|
||||
|
|
|
@ -8,7 +8,12 @@
|
|||
import type { TimeRange } from '@kbn/es-query';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { EmbeddableInput as Input } from '@kbn/embeddable-plugin/common';
|
||||
import { HasAppContext, HasDisableTriggers, PublishesViewMode } from '@kbn/presentation-publishing';
|
||||
import {
|
||||
HasAppContext,
|
||||
HasDisableTriggers,
|
||||
PublishesViewMode,
|
||||
PublishesUnifiedSearch,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { CanAddNewPanel, HasSerializedChildState } from '@kbn/presentation-containers';
|
||||
|
||||
export type EmbeddableInput = Input & {
|
||||
|
@ -21,4 +26,4 @@ export type CanvasContainerApi = PublishesViewMode &
|
|||
CanAddNewPanel &
|
||||
HasDisableTriggers &
|
||||
HasSerializedChildState &
|
||||
Partial<HasAppContext>;
|
||||
Partial<HasAppContext & PublishesUnifiedSearch>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue