src/platform/plugins/shared/embeddable/README.md
Embeddables require registration in both server and public.
Server registration defines the embeddable schema and other information required to include embeddables in public REST APIs like "dashboards as code".
Public registration defines the embeddable's UI. Embeddable UI consumes and returns state in the shape defined by the embeddable schema registered in the server.
Kibana is a React application, and the minimum unit of sharing is the React component. Embeddables enforce this by requiring a React component during registration.
Rather than an inheritance-based system with classes, imperative APIs are plain old typescript objects that implement any number of shared interfaces. Interfaces are enforced via type guards and are shared via Packages.
Each embeddable manages its own state. This is because the embeddable system allows a page to render a registry of embeddable types that can change over time. This makes it untenable for a single page to manage state for every type of embeddable. The page is only responsible for persisting and providing the last persisted state to the embeddable on startup. For implementation details, see Embeddable state management example.
An embeddable API shares state via a publishing subject, a read only RxJS Observable. An embeddable API shares setter methods for updating state.
Embeddables are React components that manage their own state, can be serialized and deserialized, and return an API that can be used to interact with them imperatively.
Plugins register new embeddable types with the embeddable plugin.
embeddablePublicSetup.registerEmbeddablePublicDefinition('my_embeddable_type', async () => {
const { myEmbeddableFactory } = await import('./embeddable_module');
return myEmbeddableFactory;
});
Embeddables are rendered with EmbeddableRenderer React component.
An embeddable API is a plain old typescript object that implements any number of shared interfaces. A shared interface is defined by a publishing package. A publishing package also provides a type guard that is used to check if a given object fulfills the interface.
The table below lists interface implemenations provided by EmbeddableRenderer React component. An embeddable does not need to implement these interfaces as they are already provided.
| Interface | Description |
|---|---|
| HasType | Interface for accessing embeddable type |
| PublishesPhaseEvents | Interface for accessing embeddable phase such as loaded, rendered, or error |
| HasUniqueId | Interface for accessing embeddable uuid |
| CanLockHoverActions | Interface for locking hover actions for an embeddable |
The table below lists required publishing package interfaces. An embeddable must implement these interfaces.
| Interface | Description |
|---|---|
| HasSerializableState | Interface for serializing embeddable state |
The table below lists optional publishing package interfaces. Embeddables may implement these interfaces. Embeddables without interface implemenations will not show UiActions that require an interface.
| Interface | Description | Used by |
|---|---|---|
| HasDrilldowns | Interface for accessing drilldowns. Drilldowns manage their own state. Drilldown state is stored in embeddable state but managed by drilldown manager. | OPEN_FLYOUT_ADD_DRILLDOWN, OPEN_FLYOUT_EDIT_DRILLDOWN |
| HasEditCapabilities | Interface for editing embeddable state | ACTION_EDIT_PANEL |
| HasInspectorAdapters | Interface for accessing embeddable inspector adaptors | ACTION_INSPECT_PANEL, ACTION_EXPORT_CSV |
| HasLibraryTransforms | Interface for linking to and unlinking from the library | ACTION_ADD_TO_LIBRARY, ACTION_UNLINK_FROM_LIBRARY |
| HasReadOnlyCapabilities | Interface for showing the embeddable configuration for read only users | ACTION_SHOW_CONFIG_PANEL |
| HasSupportedTriggers | Inteface for publishing drilldown triggers | OPEN_FLYOUT_ADD_DRILLDOWN, OPEN_FLYOUT_EDIT_DRILLDOWN |
| PublishesBlockingError | Interface for publishing unrecoverable errors | Embeddable panel to display error state |
| PublishesDataLoading | Interface for publishing when embeddable is loading | Auto refresh |
| PublishesDataViews | Interface for accessing embeddable data views | Unified search bar type ahead, ACTION_CUSTOMIZE_PANEL, ACTION_EXPLORE_DATA |
| PublishesDescription | Interface for accessing embeddable description | Panel title bar tooltip content, ACTION_CUSTOMIZE_PANEL |
| PublishesWritableDescription | Interface for setting embeddable description | ACTION_CUSTOMIZE_PANEL |
| PublishesRendered | Interface for publishing rendered complete | |
| PublishesSavedObjectId | Interface for surfacing saved object id | |
| PublishesTimeRange | Interface for accessing time range state | ACTION_CUSTOMIZE_PANEL, CUSTOM_TIME_RANGE_BADGE |
| PublishesWritableTimeRange | Interface for setting time range state | ACTION_CUSTOMIZE_PANEL |
| PublishesTitle | Interface for accessing embeddable title | Panel title bar content, ACTION_CUSTOMIZE_PANEL |
| PublishesWritableTitle | Interface for setting embeddable title | ACTION_CUSTOMIZE_PANEL |
| PublishesUnifiedSearch | Interface for publishing unified search state | BADGE_FILTERS_NOTIFICATION |
| PublishesUnsavedChanges | Interface for publishing when embeddable has unsaved changes | Dashboard unsaved chnages notification and reset |
Embeddables can expose interfaces unique to a single embeddable implemenation. The table below lists interfaces that only apply to single embeddable types. These interfaces may be used by actions to imperatively interact with specific embeddable types.
| Interface | Description | Used by |
|---|---|---|
| HasVisualizeConfig | Interface for accessing Visualize embeddable state | ACTION_EDIT_IN_LENS, CONVERT_LEGACY_MARKDOWN_ACTION_ID, ACTION_DEPRECATION_BADGE |
| LensApiCallbacks | Inteface implements Lens API | ADD_TO_EXISTING_CASE_ACTION_ID, ACTION_OPEN_IN_DISCOVER |
| PublishesSavedSearch | Interface for accessing Discover session embeddable state | generateCsvReport |
The EmbeddableRenderer React component wraps the embeddable component in an embeddable panel. The embeddable panel provides UI elements for interacting with the embeddable.
The embeddable panel uses UiActions and Triggers registry to make the embeddable UI extensible. The table below lists the trigger events used by the embeddable panel.
| Trigger | Description |
|---|---|
| ON_OPEN_PANEL_MENU | trigger to add an action to a panel's context menu or hover action menu. Only actions listed in QUICK_ACTION_IDS are displayed in hover action menu. |
| PANEL_BADGE_TRIGGER | trigger to add a badge to a panel's title bar |
| PANEL_NOTIFICATION_TRIGGER | trigger to add a notification to the top-right corner of a panel |
The embeddable panel passes the embeddable API to UiActions. Each UiAction uses its isCompatable method to exclude embeddable API's that do not implement the required shared interfaces. An action is not displayed when isCompatable returns false.
The table below lists the UiActions registered to embeddable panel triggers.
| UiAction | Description | Trigger | Optional interfaces required by action |
|---|---|---|---|
| saveToLibrary | Converts by-value panel to by-reference panel and stores panel configuration to library | ON_OPEN_PANEL_MENU | HasLibraryTransforms |
| clonePanel | Clones panel in page | ON_OPEN_PANEL_MENU | |
| copyToDashboard | Opens "copy to dashboard" modal | ON_OPEN_PANEL_MENU | |
| ACTION_CUSTOMIZE_PANEL | Opens panel settings flyout | ON_OPEN_PANEL_MENU | PublishesDataViews, PublishesDescription, PublishesWritableDescription, PublishesTitle, PublishesWritableTitle, PublishesTimeRange, PublishesWritableTimeRange |
| ACTION_INPUT_CONTROL_DEPRECATION_BADGE | Displays deprecation badge for Visualize embeddable input controls | PANEL_BADGE_TRIGGER | HasVisualizeConfig |
| ACTION_EDIT_IN_LENS | Opens Visualize embeddable in lens editor | ON_OPEN_PANEL_MENU | HasVisualizeConfig |
| editPanel | Opens embeddable editor | ON_OPEN_PANEL_MENU | HasEditCapabilities |
| togglePanel | Expands panel so page only displays single panel | ON_OPEN_PANEL_MENU | |
| ACTION_EXPLORE_DATA | Explore underlying data | ON_OPEN_PANEL_MENU | PublishesDataViews |
| ACTION_EXPORT_CSV | Exports raw data table to CSV | ON_OPEN_PANEL_MENU | HasInspectorAdapters |
| openInspector | Opens inspector flyout | ON_OPEN_PANEL_MENU | HasInspectorAdapters |
| ACTION_OPEN_IN_DISCOVER | Opens Discover application with Lens embeddable data request context | ON_OPEN_PANEL_MENU | LensApiCallbacks |
| deletePanel | Removes embeddable from page | ON_OPEN_PANEL_MENU | |
| ACTION_SHOW_CONFIG_PANEL | Opens read-only view of embeddable configuration | ON_OPEN_PANEL_MENU | HasReadOnlyCapabilities |
| unlinkFromLibrary | Converts by-reference panel to by-value panel | ON_OPEN_PANEL_MENU | HasLibraryTransforms |
| ACTION_VIEW_SAVED_SEARCH | Open in Discover session in Discover application | ON_OPEN_PANEL_MENU | |
| embeddable_addToExistingCase | Add to case | ON_OPEN_PANEL_MENU | LensApiCallbacks |
| alertRule | Create an alert rule from panel | ON_OPEN_PANEL_MENU | |
| ACTION_FILTERS_NOTIFICATION | Displays filters notification badge | PANEL_NOTIFICATION_TRIGGER | Partial<PublishesUnifiedSearch> |
| CONVERT_LEGACY_MARKDOWN | Converts markdown visualize panel to markdown panel | ON_OPEN_PANEL_MENU | HasVisualizeConfig |
| create-ml-ad-job-action | Detect anomalies | ON_OPEN_PANEL_MENU | |
| FILTER_BY_MAP_EXTENT | Filters page by map bounds | ON_OPEN_PANEL_MENU | |
| generateCsvReport | Starts CSV reporting job for Discover session | ON_OPEN_PANEL_MENU | PublishesSavedSearch, PublishesTitle |
| open-change-point-in-ml-app | Open change point chart embeddable in AIOps Labs | ON_OPEN_PANEL_MENU | |
| open-in-anomaly-explorer | Open in Anomaly Explorer | ON_OPEN_PANEL_MENU | |
| open-in-single-metric-viewer | Open in Single Metric Viewer | ON_OPEN_PANEL_MENU | |
| CUSTOM_TIME_RANGE_BADGE | Displays custom time range badge | PANEL_BADGE_TRIGGER | PublishesTimeRange |
| OPEN_FLYOUT_ADD_DRILLDOWN | Create drilldown | ON_OPEN_PANEL_MENU | HasDrilldowns, HasSupportedTriggers |
| OPEN_FLYOUT_EDIT_DRILLDOWN | Edit drilldown | ON_OPEN_PANEL_MENU | HasDrilldowns, HasSupportedTriggers |
| SYNCHRONIZE_MOVEMENT_ACTION | Synchronize maps, so that if you zoom and pan in one map, the movement is reflected in other maps | ON_OPEN_PANEL_MENU | |
| URL_DRILLDOWN | Go to URL | ON_OPEN_PANEL_MENU |
Only create an embeddable if your Component is rendered on a page that persists embeddable state and renders multiple embeddable types. For example, create an embeddable to render your Component on a Dashboard. Otherwise, use a vanilla React Component to share Components between plugins.
Break your Component into a Package or another plugin to avoid circular plugin dependencies.
Embeddable APIs are accessable to all Kibana systems and all embeddable siblings and parents. Functions and state that are internal to an embeddable including any child components should not be added to the API. Consider passing internal state to child as props or react context.
Embeddables should never throw. Instead, use PublishesBlockingError interface to surface unrecoverable errors. When an embeddable publishes a blocking error, the parent component will display an error component instead of the embeddable Component. Be thoughtful about which errors are surfaced with the PublishesBlockingError interface. If the embeddable can still render, use less invasive error handling such as a warning toast or notifications in the embeddable Component UI.
Examples available at /examples/embeddable_examples
Run examples with yarn start --run-examples
Use the following examples to learn how to create new Embeddable types. To access new Embeddable types, create a new dashboard, click "Add panel" and finally select "Embeddable examples".
Use the following examples to render embeddables in your application. To run embeddable examples, navigate to http://localhost:5601/app/embeddablesApp
Containers, such as Dashboard, incorporate embeddable state in REST APIs and store embeddable state in saved objects.
Embeddable serialized state requires additional restrictions and planning since it is incorporated into public REST APIs.
Embeddable serialized state can not be modified with breaking changes. Fields can not be deleted or change type. Optional fields can not be changed to required. Optional additive changes are allowed.
Kibana's REST APIs require snake_case. Therefore, embeddable serialized state must be in snake_case.
Avoid unnecessary information to keep public REST APIs concise. Do not store duplicate information. Derived fields can be created in public when initializing an embeddable.
POST, PUT, and GET requests should return complete data. Apply defaults in your embeddable schemas by using the defaultValue key.
Any key that has a defaultValue should not be wrapped
in a schema.maybe, as this will cause the defaults to not be applied at validation time.
When a field is required with a default, instead of failing the request for not supplying a required field, route validation supplies the default value to the handler.
//
// Use this pattern
//
// When route validation recieves "{}", validation passes "{ foo: '' }" to handler
//
const myEmbeddlabeStateSchema = schema: schema.object({
foo: schema.string({
defaultValue: ''
}),
});
When a field is optional with a default, route validation does not supply the default value to the handler.
//
// Avoid this pattern
//
// When route validation recieves "{}", validation passes "{}" to route handler
//
const myEmbeddlabeStateSchema = schema: schema.object({
foo: schema.maybe(schema.string({
defaultValue: ''
})),
});
Derive Embeddable state typescript types from REST API schemas to ensure type consistency.
//
// Example deriving type MyEmbeddableState from schema myEmbeddlabeStateSchema
//
import type { TypeOf } from '@kbn/config-schema';
const myEmbeddlabeStateSchema = schema: schema.object({
foo: schema.string(),
});
export type MyEmbeddableState = TypeOf<typeof myEmbeddlabeStateSchema>;
//
// Example deriving type MyEmbeddableState from function
//
import type { GetDrilldownsSchemaFnType } from '@kbn/embeddable-plugin/server';
import type { TypeOf } from '@kbn/config-schema';
export const getMyEmbeddableSchema = (getDrilldownsSchema: GetDrilldownsSchemaFnType) => {
return schema.allOf([
serializedTitlesSchema,
getDrilldownsSchema(MY_EMBEDDABLE_SUPPORTED_TRIGGERS),
schema.object({
foo: schema.string(),
})
])
};
export type MyEmbeddableState = TypeOf<ReturnType<typeof getMyEmbeddableSchema>>;
Then, in common and public, replace existing embeddable types with derived types.
//
// Example showing how to use derived type when registering embeddable factory in public
//
import type { DefaultEmbeddableApi } from '@kbn/embeddable-plugin/public';
import type { MyEmbeddableState } from '../../server';
export type MyEditorApi = DefaultEmbeddableApi<MyEmbeddableState>;
export const myEmbeddableFactory: EmbeddableFactory<
MyEmbeddableState,
MyEmbeddableApi
> = {
type: MY_EMBEDDABLE_TYPE,
buildEmbeddable: async (initialState) => {
//
// initialState passed to Embeddable as MyEmbeddableState
//
return {
api: {
serializeState: () => {
// Embeddable serializes state as MyEmbeddableState
return {
foo: 'hello world'
}
}
},
component: mockComponent
}
},
Transforms decouple REST API state from stored state, allowing embeddables to have one shape for REST APIs and another for storage.
Note: Transforms are optional and only required when an embeddable has references or a container has stored legacy embeddable state that needs to converted into new schema defined shape.
Note: If your embeddable registeres transformOut, then you must also register the same function with embeddablePublicSetup.registerLegacyURLTransform. Dashboard allows users to share unsaved dashboard changes. This feature stores embeddable state in URLs.
Containers use schemas to
embeddableServerSetup.registerEmbeddableServerDefinition(
'my_embeddable_type',
{
title: 'My embeddable',
getTransforms: (drilldownTransfroms) => ({
transformIn: (state: EmbeddableState) => {
return {
state: convertToStoredState(state),
references: extractReferences(state)
};
},
transformOut: (state: StoredEmbeddableState, references?: Reference[]): EmbeddableState => {
return convertAndInjectReferences(state, references);
},
}),
getSchema: (getDrilldownsSchema) => schema: schema.object({
foo: schema.string(),
})
}
});