Kibana Plugin API Changes in 7.4 | Elastic Blog
Engineering

Kibana Plugin API Changes in 7.4

Timefilter - replace SimpleEmitter with observables

Timefilter exposes 5 events:

  • getEnabledUpdated$ - Fired when isTimeRangeSelectorEnabled \ isAutoRefreshSelectorEnabled are toggled.
  • getTimeUpdate$ - Fired when a user changes the timerange.
  • getRefreshIntervalUpdate$ - Fired when a user changes the the autorefresh settings.
  • getAutoRefreshFetch$ - Used when search_poll triggers an auto refresh.
  • getFetch$- Used when data is set, or the first time auto-refresh is enabled.

Old subscription:

timefilter.on('fetch', ...);
timefilter.off('fetch', ...);

New subscription:

const fetchSub = timefilter.getFetch$().subscrible(...);
fetchSub.unsubscribe();

via #43748

Add support for dynamic imports

Plugins may now use dynamic imports in client side code in order to split up their application bundles. This can be used to improve the UX for large UI applications:

const myModule = await import('./my_module');

via #43716

Add x-pack plugin for new platform server licensing information

Add x-pack plugin for new platform server licensing information. This will eventually replace the licensing information consumed via xpack_main. Upon setup, this plugin exposes an observable API for inspecting and making checks against the license information.

license$.subscribe(license => {
   console.log(license.uid);
   console.log(license.isActive);
   console.log(license.type);

   const { check } = license.check('my-plugin', LICENSE_TYPE.gold);

   if (check !== LICENSE_STATUS.Valid) {
     disableSomething();
   }
});

via #43623

Replace CSP 'nonce-<base64>' directive with 'self' directive

Kibana no longer supports the {nonce} notation in the csp.rules configuration. These will be replaced with the 'self' source directive automatically and log a deprecation warning. The {nonce} notation must be removed before upgrading to 8.0.

via #43553

Extend request handler with request scoped core capabilities

Kibana facilitates new Handler context pattern to provide core services API to plugins. The central purpose of this pattern is to hide some implementation details and provide a set of API specific for the current context. For example, RequestHandlerContext contains tailored elasticsearch service API, letting a plugin to perform a request to elasticsearch on behalf of the user requesting Kibana. RequestHandlerContext is exposed to the route handlers as the very first argument.

class MyPlugin {
  setup(core) {
    const router = core.http.createRouter();
    router.get({ path: '/ping', validate: false }, async (context, req, res) => {
      const response = await context.core.elasticsearch.adminClient.callAsInternalUser('ping');
      return res.ok({ body: `Pong: ${response}` });
    });
  }
}

via #43103

add socket.getPeerCertificate to KibanaRequest

KibanaRequest object can provide peer certificate

const cert = request.socket.getPeerCertificate();

via #42929

Allowing individual privileges to be excluded from base privileges

Individual feature's privileges can now be excluded from the base privileges using excludeFromBasePrivilege

via #42470

Unify response interface in handler and request interceptors

New platform plugins may want to extend Kibana capabilities with implementing a custom logic over an incoming request, before it hits a resource endpoint. For this purpose, KIbana introduced interceptors concept. Interceptors cannot change or mutate request object directly but may redirect, reject or allow a request to pass through. An interceptor may be created for different lifecycle stages of an incoming request:

  • before a user requesting a resource is authenticated
  • authentication
  • after a user was successfully authenticated.
// pre-authentication interceptor.
registerOnPreAuth((req, res, toolkit) => {
  if(req.headers.something) return res.badRequest({ body: '"something" headers is required' });
  return toolkit.next();
})
// authentication interceptor 
registerAuth((req, res, toolkit) => {
  const authResult = authenticate(req.headers.authorization);
  if(authResult.succeeded) return toolkit.authenticated(...);
  return res.unauthorized();
});
// post-authentication interceptor.
registerOnPostAuth((req, res, toolkit) => {
  if(deps.security.getUser(req).roles.length === 0) return res.notFound();
  return toolkit.next();
});

via #42442

revert PR 36804 'Create additional HTTP servers'

New platform HTTP service no longer provides createNewServer method.

via #42333

Inspector 👉 New Platform inspector plugin

  • Inspector ui-public module has been moved to a New Platform plugin.
  • You can find its documentation in src/plugins/inspector/README.md.
  • Old Inspector module still works as before, but all methods are now marked as deprecated. During one of upcoming 7.x releases it will be completely deleted.

If you need to use inspector plugin from within src/legacy location, use New Platform plugin backdoor.

import { npSetup, npStart } from 'ui/new_platform';

npSetup.plugins.inspector.registerView(view);

npStart.plugins.inspector.isAvailable(adapters);
npStart.plugins.inspector.open( /* ... */ );

via #42164

Telemetry/opt in welcome screen

Added Telemetry Opt in card to welcome screen. The aim is to increate increase opt in rate. Added tracking to measure the success of this change.

via #42110

Http server route handler implementation

Kibana HTTP Service in the New platform provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs.

To handle an incoming request in your plugin you should:

  • Create a Router instance. Router is already configured to use plugin-id to prefix path segment for your routes.
const router = httpSetup.createRouter();
  • Use @kbn/config-schema package to create a schema to validate the request params, query, and body. Every incoming request will be validated against the created schema. If validation failed, the request is rejected with 400 status and Bad request error without calling the route's handler. To opt out of validating the request, specify false.
import { schema, TypeOf } from '@kbn/config-schema';
const validate = {
  params: schema.object({
    id: schema.string(),
  }),
};
  • Declare a function to respond to incoming request. The function will receive: 1.context runtime context providing access to Kibana API, specific for a route handler. 2.request object containing request details: url, headers, matched route, as well as validated params, query, body. 3.response object instructing HTTP server to create HTTP response with information sent back to the client as the response body, headers, and HTTP status. Unlike, hapi route handler in the Legacy platform, any exception raised during the handler call will generate 500 Server error response and log error details for further investigation. See below for returning custom error responses.
const handler = async (context: RequestHandlerContext, request: KibanaRequest, response: ResponseFactory) => {
  const data = await findObject(request.params.id);
  // creates a command to respond with 'not found' error
  if (!data) return response.notFound();
  // creates a command to send found data to the client and set response headers
  return response.ok({
    body: data,
    headers: {
      'content-type': 'application/json'
    }
  });
}
  • Register route handler for GET request to 'my-app/path/{id}' path
import { schema, TypeOf } from '@kbn/config-schema';
const router = httpSetup.createRouter();

const validate = {
  params: schema.object({
    id: schema.string(),
  }),
};

router.get({
  path: 'path/{id}',
  validate
},
async (context, request, response) => {
  const data = await findObject(request.params.id);
  if (!data) return response.notFound();
  return response.ok({
    body: data,
    headers: {
      'content-type': 'application/json'
    }
  });
});

via #41894

API change of React visualization editor

The properties that get passed into a React visualization editor have slightly been changed:

  • editorState.paramsstateParams
  • stageEditorParams(newParams)setValue(paramName: string, paramValue: unknown) (meaning that you can now set individual parameters in the editor without needing to copy over the previous params.
  • scope has been removed, which gave direct access to an Angular scope.
  • vis has been added to the properties to access the underlying Vis object directly.

You can check the custom visualization sample plugin for an always up to date example of building custom visualizations.

via #41746

Remove notifications plugin

The notifications functionality has been replaced by the features of the actions plugin. This notifications plugin was never actually used by end-user facing features of Kibana, but it may have been in use by third party plugins.

via #41674

GoodBye Notifier

The old notification system called Notifier has been removed. If you used Notifier or notify from the ui/notify package, you need to change that code to use the new toastNotifications from ui/notify instead.

via #41663

Adding "style-src 'unsafe-inline' 'self'" to default CSP rules

Adding style-src 'unsafe-inline' 'self' to the default CSP rules

via #41305

Embeddables -> NP-ready

embeddable_api and dashboard_embeddable_container plugins have been refactored to prepare them for the New Platform Migration.

Embeddable API is still considered unstable and will change during 7.4 and possibly 7.5 releases.

Now you use Embeddables as if they were a New Platform plugin:

import { setup, start } from 'plugins/embeddable_api/np_ready/public/legacy';

setup.registerTrigger(trigger);

start.executeTriggerActions(/* ... */);

In one of the subsequent Embeddables PRs we expect to move them to a real New Platform plugin. In legacy platform you will use Embeddable plugin as follows:

import { npSetup, npStart } from 'ui/new_platform';

npSetup.plugins.embeddable.registerTrigger(trigger);

npStart.plugins.embeddable.executeTriggerActions(/* ... */);

via #41272

Add ContextService

New Platform plugins may now implement their own context API on the server or client.

A IContextContainer can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares.

class MyPlugin {
  private readonly handlers = new Map();
  setup(core) {
    this.contextContainer = core.context.createContextContainer();
    return {
      registerContext(pluginOpaqueId, contextName, provider) {
        this.contextContainer.registerContext(pluginOpaqueId, contextName, provider);
      },
      registerRoute(pluginOpaqueId, path, handler) {
        this.handlers.set(
          path,
          this.contextContainer.createHandler(pluginOpaqueId, handler)
        );
      }
    }
  }
}

via #41251

Schema.deprecate removed

The deprecate boolean flag on Schema when declaring schemas for visualizations has been removed.

via #40866

decouple sessiontStorageFactory creation from registerAuth

Implementing custom authentication logic might require session storage. If you want to use session storage, based on cookie mechanism, you can create one via new platform API:

const { createCookieSessionStorageFactory } = await server.setup(config);
const sessionStorageFactory = await createCookieSessionStorageFactory(cookieOptions);
// in authenticator 
const sessionStorage = sessionStorageFactory.asScoped(request);
await sessionStorage.get();

via #40852

Add scoped services and plugin contracts

New Platform Plugins may now return a factory function for creating an instance of their public contract from lifecycle methods. This allows plugins to offer scoped versions of their APIs to dependencies.

via #40822

expose ES createClient to plugins

New platform plugins may create their custom elasticsearch clients with elasticsearch.createClient.

class MyPlugin {
 public async setup(core: CoreSetup) {
   this.clusterClient = core.elasticsearch.createClient('name', configObject);
   const result = await this.clusterClient.callAsInternalUser('/endpoint');

via #40717

Moved kbnGlobalTimepicker into old kbn_top_nav (both do be deprecated)

  • Deleted search-bar directive
  • Deleted query-bar directive

via #40603

Reactify Top Nav Menu (kbn_top_nav)

Top Nav Menu is a configurable component that conveniently renders the elements that are often shared by applications: Option menus, query input, filters and time range selection.

Show a simple options menu

Define your navigation menu items as an array of TopNavMenuData elements and pass it down to the TopNavMenu component.

import { TopNavMenu, TopNavMenuData } from 'plugins/kibana_react';

const navConfig: TopNavMenuData[] = [{
  id: 'new-item', 
  label: 'New',
  run: () => { 
     showNewItemModal() 
  }
}, {
  id: 'save-item',
  label: 'Save',
  run: () => { 
     if(hasChanges()) {
        saveChanges();
     }
  },
  disableButton: () => {
    return !hasChanges();
  },
  tooltip: () => {
    return !hasChanges() ? 'Nothing to save' : '';
  }
}];

function MyAppComponent(props: Props) {
  return (
    // ... other components rendering ...
    <TopNavMenu
       appName="my-app"
       config={navConfig}
    />
  );
}

Show a full menu with SearchBar

Search Bar is another convenience component that displays FilterBar, QueryBarInput and EuiSuperDatePicker in a standard layout.

This is the full configuration required to render all 3 components.

function MyAppComponent(props: Props) {
  const query: Query = {
    query: 'response:200',
    language: 'kuery',
  };
  const filters: Filter[] = [];
  const indexPatterns: IndexPattern[] = [];
  const queryHandler = ({ query, dateRange }) => {};
  const filterHandler = (filters) => {};
  const refreshHandler = ({ isPaused, refreshInterval }) => {};
  return (
    // ... other components rendering ...
    <TopNavMenu
       appName='my-app'
       config={navConfig}
       screenTitle='My App Page'
       store={localStorage}
       indexPatterns={indexPatterns}
       filters={filters}
       query={query}
       onQuerySubmit={queryHandler}
       onFiltersUpdated={filterHandler}
       dateRangeFrom='now-7d'
       dateRangeTo='now'
       isRefreshPaused={false}
       refreshInterval={30}
       onRefreshChange={refreshHandler}
    />
  );
}

Migrating the Angular directive

The kbn-top-nav directive is still defined, however, its API has slightly changed. While it will be deprecated during the lifetime of 7.x, you may still use it, if you take the following steps.

Key attribute is deprecated

Change the key attribute of the navigation configuration to id. It's a reserved React keyword and hence it's not supported by the TopNavMenu component. While the both attributes will work with the directive, it's better to update to id right away.

// Old
const navConfig = [{
  key: 'new-item',
  label: 'new',
  run: () => {},
}];

// New
const navConfig = [{
  id: 'new-item',
  label: 'new',
  run: () => {},
}];

Add a label

While the TopNavMenuItem component still currently falls back to using key\id if label is not provided, you should always specify a label. This is because label is an i18n string and id is not.

Transcluded views are deprecated

Either move them elsewhere or use the SearchBar options.

// Old
<kbn-top-nav config="topNavConfig">
   <search-bar
      ... search bar options...  
   />
   <my-custom-directive/>
</kbn-top-nav>

// New 
<kbn-top-nav 
    config="topNavConfig"
    ... search bar options...  
/>
<my-custom-directive/>

Template menu items are deprecated.

Render your views from your app's code and then use the menu's run function to toggle visibility.

// Old
const newTemplateForm = require(....);
const navConfig = [{
  key: 'new-item',
  label: 'new',
  template: newTemplateForm,
}];

// New
const navConfig = [{
  id: 'new-item',
  label: 'new',
  run: () => toggleMyForm,
}];

via #40262

Expose elasticsearch error wrapper

When elasticsearch client requests elasticsearch API it may throw 2 kinds of exceptions: 1. Boom error when a user is unauthorized to access a resource. 2. native error in other cases.

We added ElasticsearchErrorHelpers.isNotAuthorizedError(error) method to distinguish between them. Use this method if you need access to authorization error properties. In the future, we are going to get rid of Boom error object as a part of the public contract and your code will be ready for those changes.

try {
  await client.asScoped(request).callAsCurrentUser(...);
} catch (err) {
  if (ElasticsearchErrorHelpers.isNotAuthorizedError(err)) {
    const authHeader = err.output.headers['WWW-Authenticate'];
    //...
  }
}

via #40242

cancellation of interpreter execution

The interpreter now accepts an AbortController signal inside handlers for aborting execution. Since handlers is passed down to each function in the interpreter, functions themselves can handle additional cleanup using handlers.abortSignal if it is available.

via #40238

ui/public cleanup

Relocated modules

In preparation for Kibana's upcoming new platform, we are in the process of migrating away from the ui/public directory. Over time, the contents of this directory will be either deprecated or housed inside a parent plugin. If your plugin imports from any of the following ui/public modules, you will need to update your import statements as indicated below, so that you are pulling these modules from their new locations.

ui/index_patterns #42994

// deprecated
import * from 'ui/index_patterns/**/*';

// new location (stateful code)
import { setup as data } from '../../core_plugins/data/public';
const {
  FieldList,
  flattenHitWrapper,
  formatHitProvider,
  IndexPatternSelect,
} = data.indexPatterns;

// new location (static code)
import {
  CONTAINS_SPACES,
  getFromSavedObject,
  getRoutes,
  isFilterable,
  IndexPatternsProvider,
  validateIndexPattern,
  ILLEGAL_CHARACTERS,
  INDEX_PATTERN_ILLEGAL_CHARACTERS,
  INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE,
  IndexPatternAlreadyExists,
  IndexPatternMissingIndices,
  NoDefaultIndexPattern,
  NoDefinedIndexPatterns,
} from '../../core_plugins/data/public';

// new location (types)
import {
  Field,
  FieldType,
  IndexPattern,
  IndexPatterns,
  StaticIndexPattern,
} from '../../core_plugins/data/public';

ui/vis #43730

// deprecated
import {
  VisProvider,
  Vis,
  VisParams,
  VisState,
} from 'ui/vis';
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisTypesRegistry, VisTypesRegistryProvider } from 'ui/registry/vis_types';
import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message';
import { Status } from 'ui/vis/update_status';
import { VisualizationController, VisType } from 'ui/vis/vis_types/vis_type';
import { VisFiltersProvider, createFilter } from 'ui/vis/vis_filters';

// new location (stateful code)
import { setup as visualizations } from '../core_plugins/visualizations/public/legacy';
const { VisProvider, VisTypesRegistryProvider } = visualizations.types;
const { VisFactoryProvider } = visualizations.types.__LEGACY;
const { VisFiltersProvider, createFilter } = visualizations.filters;

// new location (static code)
import { DefaultEditorSize, defaultFeedbackMessage, visFactory } from '../core_plugins/visualizations/public';

// new location (types)
import {
  Vis,
  VisParams,
  VisProvider,
  VisState,
  VisType,
  VisTypesRegistry,
  VisualizationController,
  Status,
} from '../core_plugins/visualizations/public';

Angular Cleanup

Removed / moved Angular items

In the process of removing Angular from the Kibana core, the following items have been deleted or relocated. If you have used any of those in your plugin and want to keep using them, please copy over the source from the previous Kibana version directly into your plugin code, or import them from their new location indicated below. Please note that these will be deprecated over the lifetime of 7.x.

Directives bind decorator moved into directives folder #41638

// Old
import 'ui/bind';

// New
import 'ui/directives/bind';
$scope.$bind(local, attr);

debounce service moved into directives folder #41638

// Old
import { DebounceProvider } from 'ui/debounce';

// New
import { DebounceProvider } from 'ui/directives/debounce';

listen rootScope function moved into directives folder #41638

// Old
import 'ui/directives/listen';

// New
import 'ui/listen';
$scope.$listen(obj, 'event', handler);

renderDirective directive moved into directives folder #41638

// Old
import 'ui/render_directive';

// New
import 'ui/directives/render_directive';
 <render-directive ... >

watchMulti rootScope function moved into directives folder #41638

// Old
import 'ui/directives/watch_multi';

// New
import 'ui/watch_multi';
$scope.$watchMulti(eventNames, eventHandler); 

via #39907

Task manager now uses saved objects

Starting up kibana will convert .kibana_task_manager to an alias and indices will follow the .kibana_task_manager_1 syntax. A migration will execute to prefix the ids with task: as it converts to saved objects.

via #39829

i18n .i18nrc file as the source of truth and enhance tooling

Currently there is a disconnect disconnect between i18n tooling (i18nrc.json) and top-level/translations convention used by the kibana i18n service. This change attempts to fix it, tidying up the code a little, and enhancing the i18n tooling as a result of this change.

Use .i18nrc.json as the source of truth

Currently we assume that internal and external plugins to follow this convention of putting translations following this pattern **/*/translations/${locale}.json.

We also have to keep the .i18nrc in sync with the location of each of these translation files.

This change makes the i18n service look into top level .i18nrc.json files and parses the translations instead of traversing the source code directories to look for translation files.

This change discards the translation paths assumptions and allows developers to explicitly specify translation paths inside .i18nrc file. Previously the translations paths in the .i18nrc was only used for the scripts/i18n_check.js tooling.

This change also allows for a better third party plugin development experience as developers can fully control their own translation paths using the .i18nrc.json file.

Enhance i18n tooling (namespaces prefix checking, better logging and error handling for parsing .i18nrc files)

Introducing top level .i18nrc files for plugins allows adding a namespaces prefix check. This PR adds a prefix check that all x-pack plugin namespaces (label ids) start with xpack.*

Introducing top level .i18nrc files for plugins allows for a better experience in merging configs. This PR adds a more verbose logging while merging configs and better error handling.

via #39774

Minor changes to Index Patterns API

During our efforts to rewrite the index patterns service in TypeScript, we made a few minor changes to the API:

  • IndexPatterns.cache has been deprecated in favor of the new IndexPatterns.clearCache method.
  • IndexPatterns.delete has been deprecated. Instead, use the IndexPattern.destroy method on the specific index pattern you which to remove.
import { IndexPatterns, IndexPattern } from 'ui/index_patterns';
const indexPatterns = new IndexPatterns(config, savedObjectsClient);
const indexPattern = new IndexPattern('some-id', config, savedObjectsClient, ...etc);

// old
indexPatterns.cache.clear('some-id');
indexPatterns.delete(indexPattern);

// new
indexPatterns.clearCache('some-id'); // clears pattern matching specific ID from cache
indexPatterns.clearCache(); // clears everything
indexPattern.destroy(); // destroys pattern it is called on

via #39247

Use embeddable v2

Any plugins written on the old embeddable infrastructure will need to be updated to use the new embeddable infrastructure. The Embeddables API allows you to add your own custom UI components to a dashboard, without having to go through the visualization plugin system. This infrastructure is highly unstable at the moment. Expect this API to change frequently over the ensuing months.

via #39126

kibana-react

Tools for building React applications in Kibana.

Context

You can create React context that holds Core or plugin services that your plugin depends on.

import { createKibanaReactContext } from 'kibana-react';

class MyPlugin {
  start(core, plugins) {
    const context = createKibanaReactContext({ ...core, ...plugins });
  }
}

You may also want to be explicit about services you depend on.

import { createKibanaReactContext } from 'kibana-react';

class MyPlugin {
  start({ notifications, overlays }, { embeddable }) {
    const context = createKibanaReactContext({ notifications, overlays, embeddable });
  }
}

Wrap your React application in the created context.

<context.Provider>
  <KibanaApplication />
</context.Provider>

Or use already pre-created <KibanaContextProvider> component.

import { KibanaContextProvider } from 'kibana-react';

<KibanaContextProvider services={{ ...core, ...plugins }}>
  <KibanaApplication />
</KibanaContextProvider>

<KibanaContextProvider services={{ notifications, overlays, embeddable }}>
  <KibanaApplication />
</KibanaContextProvider>

Accessing context

Using useKibana hook.

import { useKibana } from 'kibana-react';

const Demo = () => {
  const kibana = useKibana();
  return (
    <div>
      {kibana.services.uiSettings.get('theme:darkMode') ? 'dark' : 'light'}
    </div>
  );
};

Using withKibana() higher order component.

import { withKibana } from 'kibana-react';

const Demo = ({ kibana }) => {
  return (
    <div>
      {kibana.services.uiSettings.get('theme:darkMode') ? 'dark' : 'light'}
    </div>
  );
};

export default withKibana(Demo);

Using <UseKibana> render prop.

import { UseKibana } from 'kibana-react';

const Demo = () => {
  return (
    <UseKibana>{kibana => 
      <div>
        {kibana.services.uiSettings.get('theme:darkMode') ? 'dark' : 'light'}
      </div>
    }</UseKibana>
  );
};

uiSettings service

Wrappers around Core's uiSettings service.

useUiSetting hook

useUiSetting synchronously returns the latest setting from CoreStart['uiSettings'] service.

import { useUiSetting } from 'kibana-react';

const Demo = () => {
  const darkMode = useUiSetting<boolean>('theme:darkMode');
  return (
    <div>
      {darkMode ? 'dark' : 'light'}
    </div>
  );
};
Reference
useUiSetting<T>(key: string, defaultValue: T): T;

useUiSetting$ hook

useUiSetting$ synchronously returns the latest setting from CoreStart['uiSettings'] service and subscribes to changes, re-rendering your component with latest values.

import { useUiSetting$ } from 'kibana-react';

const Demo = () => {
  const [darkMode] = useUiSetting$<boolean>('theme:darkMode');
  return (
    <div>
      {darkMode ? 'dark' : 'light'}
    </div>
  );
};
Reference
useUiSetting$<T>(key: string, defaultValue: T): [T, (newValue: T) => void];

overlays service

Wrapper around Core's overlays service, allows you to display React modals and flyouts directly without having to use react-dom library to mount to DOM nodes.

import { createKibanaReactContext } from 'kibana-react';

class MyPlugin {
  start(core) {
    const { value: { overlays } } = createKibanaReactContext(core);

    overlays.openModal(
      <div>
        Hello world!
      </div>
    );
  }
}
  • overlays.openModal opens modal window.
  • overlays.openFlyout opens right side panel.

You can access overlays service through React context.

const Demo = () => {
  const { overlays } = useKibana();
  useEffect(() => {
    overlays.openModal(
      <div>
        Oooops! {errorMessage}
      </div>
    );
  }, [errorMessage]);
};

notifications service

Wrapper around Core's notifications service, allows you to render React elements directly without having to use react-dom library to mount to DOM nodes.

import { createKibanaReactContext } from 'kibana-react';

class MyPlugin {
  start(core) {
    const { value: { notifications } } = createKibanaReactContext(core);

    notifications.toasts.show({
      title: <div>Hello</div>,
      body: <div>world!</div>
    });
  }
}
  • notifications.toasts.show() show generic toast message.
  • notifications.toasts.success() show positive toast message.
  • notifications.toasts.warning() show warning toast message.
  • notifications.toasts.danger() show error toast message.

You can access notifications service through React context.

const Demo = () => {
  const { notifications } = useKibana();
  useEffect(() => {
    notifications.toasts.danger({
      title: 'Oooops!',
      body: errorMessage,
    });
  }, [errorMessage]);
};

via #43272