Back to Kibana

README

src/platform/plugins/shared/embeddable/README.md

9.4.020.0 KB
Original Source

Table of Contents

Embeddable overview

Embeddables require registration in both server and public.

registerEmbeddableServerDefinition

Server registration defines the embeddable schema and other information required to include embeddables in public REST APIs like "dashboards as code".

registerEmbeddablePublicDefinition

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.

Public

Guiding principles

Coupled to React

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.

Composition over inheritence

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.

Internal state management

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.

Embeddable public overview

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.

Publishing packages

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.

Common publishing packages

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.

InterfaceDescription
HasTypeInterface for accessing embeddable type
PublishesPhaseEventsInterface for accessing embeddable phase such as loaded, rendered, or error
HasUniqueIdInterface for accessing embeddable uuid
CanLockHoverActionsInterface for locking hover actions for an embeddable

The table below lists required publishing package interfaces. An embeddable must implement these interfaces.

InterfaceDescription
HasSerializableStateInterface 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.

InterfaceDescriptionUsed by
HasDrilldownsInterface 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
HasEditCapabilitiesInterface for editing embeddable stateACTION_EDIT_PANEL
HasInspectorAdaptersInterface for accessing embeddable inspector adaptorsACTION_INSPECT_PANEL, ACTION_EXPORT_CSV
HasLibraryTransformsInterface for linking to and unlinking from the libraryACTION_ADD_TO_LIBRARY, ACTION_UNLINK_FROM_LIBRARY
HasReadOnlyCapabilitiesInterface for showing the embeddable configuration for read only usersACTION_SHOW_CONFIG_PANEL
HasSupportedTriggersInteface for publishing drilldown triggersOPEN_FLYOUT_ADD_DRILLDOWN, OPEN_FLYOUT_EDIT_DRILLDOWN
PublishesBlockingErrorInterface for publishing unrecoverable errorsEmbeddable panel to display error state
PublishesDataLoadingInterface for publishing when embeddable is loadingAuto refresh
PublishesDataViewsInterface for accessing embeddable data viewsUnified search bar type ahead, ACTION_CUSTOMIZE_PANEL, ACTION_EXPLORE_DATA
PublishesDescriptionInterface for accessing embeddable descriptionPanel title bar tooltip content, ACTION_CUSTOMIZE_PANEL
PublishesWritableDescriptionInterface for setting embeddable descriptionACTION_CUSTOMIZE_PANEL
PublishesRenderedInterface for publishing rendered complete
PublishesSavedObjectIdInterface for surfacing saved object id
PublishesTimeRangeInterface for accessing time range stateACTION_CUSTOMIZE_PANEL, CUSTOM_TIME_RANGE_BADGE
PublishesWritableTimeRangeInterface for setting time range stateACTION_CUSTOMIZE_PANEL
PublishesTitleInterface for accessing embeddable titlePanel title bar content, ACTION_CUSTOMIZE_PANEL
PublishesWritableTitleInterface for setting embeddable titleACTION_CUSTOMIZE_PANEL
PublishesUnifiedSearchInterface for publishing unified search stateBADGE_FILTERS_NOTIFICATION
PublishesUnsavedChangesInterface for publishing when embeddable has unsaved changesDashboard unsaved chnages notification and reset

Custom publishing packages

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.

InterfaceDescriptionUsed by
HasVisualizeConfigInterface for accessing Visualize embeddable stateACTION_EDIT_IN_LENS, CONVERT_LEGACY_MARKDOWN_ACTION_ID, ACTION_DEPRECATION_BADGE
LensApiCallbacksInteface implements Lens APIADD_TO_EXISTING_CASE_ACTION_ID, ACTION_OPEN_IN_DISCOVER
PublishesSavedSearchInterface for accessing Discover session embeddable stategenerateCsvReport

Embeddable panel

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.

TriggerDescription
ON_OPEN_PANEL_MENUtrigger 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_TRIGGERtrigger to add a badge to a panel's title bar
PANEL_NOTIFICATION_TRIGGERtrigger 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.

UiActionDescriptionTriggerOptional interfaces required by action
saveToLibraryConverts by-value panel to by-reference panel and stores panel configuration to libraryON_OPEN_PANEL_MENUHasLibraryTransforms
clonePanelClones panel in pageON_OPEN_PANEL_MENU
copyToDashboardOpens "copy to dashboard" modalON_OPEN_PANEL_MENU
ACTION_CUSTOMIZE_PANELOpens panel settings flyoutON_OPEN_PANEL_MENUPublishesDataViews, PublishesDescription, PublishesWritableDescription, PublishesTitle, PublishesWritableTitle, PublishesTimeRange, PublishesWritableTimeRange
ACTION_INPUT_CONTROL_DEPRECATION_BADGEDisplays deprecation badge for Visualize embeddable input controlsPANEL_BADGE_TRIGGERHasVisualizeConfig
ACTION_EDIT_IN_LENSOpens Visualize embeddable in lens editorON_OPEN_PANEL_MENUHasVisualizeConfig
editPanelOpens embeddable editorON_OPEN_PANEL_MENUHasEditCapabilities
togglePanelExpands panel so page only displays single panelON_OPEN_PANEL_MENU
ACTION_EXPLORE_DATAExplore underlying dataON_OPEN_PANEL_MENUPublishesDataViews
ACTION_EXPORT_CSVExports raw data table to CSVON_OPEN_PANEL_MENUHasInspectorAdapters
openInspectorOpens inspector flyoutON_OPEN_PANEL_MENUHasInspectorAdapters
ACTION_OPEN_IN_DISCOVEROpens Discover application with Lens embeddable data request contextON_OPEN_PANEL_MENULensApiCallbacks
deletePanelRemoves embeddable from pageON_OPEN_PANEL_MENU
ACTION_SHOW_CONFIG_PANELOpens read-only view of embeddable configurationON_OPEN_PANEL_MENUHasReadOnlyCapabilities
unlinkFromLibraryConverts by-reference panel to by-value panelON_OPEN_PANEL_MENUHasLibraryTransforms
ACTION_VIEW_SAVED_SEARCHOpen in Discover session in Discover applicationON_OPEN_PANEL_MENU
embeddable_addToExistingCaseAdd to caseON_OPEN_PANEL_MENULensApiCallbacks
alertRuleCreate an alert rule from panelON_OPEN_PANEL_MENU
ACTION_FILTERS_NOTIFICATIONDisplays filters notification badgePANEL_NOTIFICATION_TRIGGERPartial<PublishesUnifiedSearch>
CONVERT_LEGACY_MARKDOWNConverts markdown visualize panel to markdown panelON_OPEN_PANEL_MENUHasVisualizeConfig
create-ml-ad-job-actionDetect anomaliesON_OPEN_PANEL_MENU
FILTER_BY_MAP_EXTENTFilters page by map boundsON_OPEN_PANEL_MENU
generateCsvReportStarts CSV reporting job for Discover sessionON_OPEN_PANEL_MENUPublishesSavedSearch, PublishesTitle
open-change-point-in-ml-appOpen change point chart embeddable in AIOps LabsON_OPEN_PANEL_MENU
open-in-anomaly-explorerOpen in Anomaly ExplorerON_OPEN_PANEL_MENU
open-in-single-metric-viewerOpen in Single Metric ViewerON_OPEN_PANEL_MENU
CUSTOM_TIME_RANGE_BADGEDisplays custom time range badgePANEL_BADGE_TRIGGERPublishesTimeRange
OPEN_FLYOUT_ADD_DRILLDOWNCreate drilldownON_OPEN_PANEL_MENUHasDrilldowns, HasSupportedTriggers
OPEN_FLYOUT_EDIT_DRILLDOWNEdit drilldownON_OPEN_PANEL_MENUHasDrilldowns, HasSupportedTriggers
SYNCHRONIZE_MOVEMENT_ACTIONSynchronize maps, so that if you zoom and pan in one map, the movement is reflected in other mapsON_OPEN_PANEL_MENU
URL_DRILLDOWNGo to URLON_OPEN_PANEL_MENU

Best practices

Do not use Embeddables to share Components between plugins

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.

Do not use Embeddables to avoid circular plugin dependencies

Break your Component into a Package or another plugin to avoid circular plugin dependencies.

Minimal API surface area

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.

Error handling

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

Examples available at /examples/embeddable_examples

Run examples with yarn start --run-examples

Embeddable factory 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".

Rendering embeddable examples

Use the following examples to render embeddables in your application. To run embeddable examples, navigate to http://localhost:5601/app/embeddablesApp

Server

Containers, such as Dashboard, incorporate embeddable state in REST APIs and store embeddable state in saved objects.

REST APIs considerations

Embeddable serialized state requires additional restrictions and planning since it is incorporated into public REST APIs.

No breaking changes

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.

snake_case

Kibana's REST APIs require snake_case. Therefore, embeddable serialized state must be in snake_case.

Avoid duplicating state

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.

Apply defaults in your schema

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 types from schemas

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

Transforms decouple REST API state from stored state, allowing embeddables to have one shape for REST APIs and another for storage.

  • On read, transformOut is used to convert StoredEmbeddableState and inject references into EmbeddableState.
  • On write, transformIn is used to extract references and convert EmbeddableState into StoredEmbeddableState.

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

  • Include embeddable state schemas in OpenAPI Specification (OAS) documenation.
  • Validate embeddable state, failing REST API requests when schema validation fails.
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(),
    })
  }
});