Localization for plugins

edit

To introduce localization for your plugin, use our i18n tool to create IDs and default messages. You can then extract these IDs with respective default messages into localization JSON files for Kibana to use when running your plugin.

Adding localization to your plugin

edit

You must add a translations directory at the root of your plugin. This directory will contain the translation files that Kibana uses.

.
├── translations
│   ├── en.json
│   └── zh-CN.json
└── .i18nrc.json

Using Kibana i18n tooling

edit

To simplify the localization process, Kibana provides tools for the following functions:

  • Verify all translations have translatable strings and extract default messages from templates
  • Verify translation files and integrate them into Kibana

To use Kibana i18n tooling, create a .i18nrc.json file with the following configs:

  • paths. The directory from which the i18n translation IDs are extracted.
  • exclude. The list of files to exclude while parsing paths.
  • translations. The list of translations where JSON localizations are found.
{
  "paths": {
    "myPlugin": "src/ui",
  },
  "exclude": [
  ],
  "translations": [
    "translations/zh-CN.json"
  ]
}

An example Kibana .i18nrc.json is {kib-repo}blob/6.8/.i18nrc.json[here].

Full documentation about i18n tooling is {kib-repo}blob/6.8/src/dev/i18n/README.md[here].

Extracting default messages

edit

To extract the default messages from your plugin, run the following command:

node scripts/i18n_extract --output-dir ./translations --include-config ../kibana-extra/myPlugin/.i18nrc.json

This outputs a en.json file inside the translations directory. To localize other languages, clone the file and translate each string.

Checking i18n messages

edit

Checking i18n does the following:

  • Checks all existing labels for violations.
  • Takes translations from .i18nrc.json and compares them to the messages extracted and validated at the step above and:

    • Checks for unused translations. If you remove a label that has a corresponding translation, you must also remove the label from the translations file.
    • Checks for incompatible translations. If you add or remove a new parameter from an existing string, you must also remove the label from the translations file.

To check your i18n translations, run the following command:

node scripts/i18n_check --fix --include-config ../kibana-extra/myPlugin/.i18nrc.json

Implementing i18n in the UI

edit

Kibana relies on several UI frameworks (ReactJS and AngularJS) and requires localization in different environments (browser and NodeJS). The internationalization engine is framework agnostic and consumable in all parts of Kibana (ReactJS, AngularJS and NodeJS).

To simplify internationalization in UI frameworks, additional abstractions are built around the I18n engine: react-intl for React and custom components for AngularJS. React-intl is built around intl-messageformat, so both React and AngularJS frameworks use the same engine and the same message syntax.

i18n for vanilla JavaScript
edit
import { i18n } from '@kbn/i18n';

export const HELLO_WORLD = i18n.translate('hello.wonderful.world', {
  defaultMessage: 'Greetings, planet Earth!',
});

<<<<<<< HEAD Full details are {repo}tree/6.7/packages/kbn-i18n#vanilla-js[here].

Full details are here. >>>>>>> d26cbef389…​ [DOCS] Adds kibana-pull attribute for release docs (#69554)

i18n for React
edit

To localize strings in React, use either FormattedMessage or i18n.translate.

import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';

export const Component = () => {
  return (
    <div>
      {i18n.translate('xpack.someText', { defaultMessage: 'Some text' })}
      <FormattedMessage id="xpack.someOtherText" defaultMessage="Some other text">
      </FormattedMessage>
    </div>
  );
};

<<<<<<< HEAD Full details are {repo}tree/6.7/packages/kbn-i18n#react[here].

Full details are here. >>>>>>> d26cbef389…​ [DOCS] Adds kibana-pull attribute for release docs (#69554)

i18n for Angular
edit

AngularJS wrapper has 4 entities: translation provider, service, directive and filter. Both the directive and the filter use the translation service with i18n engine under the hood.

The translation directive has the following syntax:

<ANY
  i18n-id="{string}"
  i18n-default-message="{string}"
  [i18n-values="{object}"]
  [i18n-description="{string}"]
></ANY>

<<<<<<< HEAD Full details are {repo}tree/6.7/packages/kbn-i18n#angularjs[here].

Full details are here. >>>>>>> d26cbef389…​ [DOCS] Adds kibana-pull attribute for release docs (#69554)

Resources

edit

To learn more about i18n tooling, see {kib-repo}blob/6.8/src/dev/i18n/README.md[i18n dev tooling].

To learn more about implementing i18n in the UI, follow the links below:

  • {kib-repo}blob/6.8/packages/kbn-i18n/README.md[i18n plugin]
  • {kib-repo}blob/6.8/packages/kbn-i18n/GUIDELINE.md[i18n guidelines]

== Developing Visualizations

Kibana Visualizations are the easiest way to add additional functionality to Kibana. This part of documentation is split into two parts. The first part tells you all you need to know on how to embed existing Kibana Visualizations in your plugin. The second step explains how to create your own custom visualization.

These pages document internal APIs and are not guaranteed to be supported across future versions of Kibana. However, these docs will be kept up-to-date to reflect the current implementation of Visualization plugins in Kibana.

=== Embedding Visualizations

To embed visualization use the VisualizeLoader.

==== VisualizeLoader

The VisualizeLoader class is the easiest way to embed a visualization into your plugin. It will take care of loading the data and rendering the visualization.

To get an instance of the loader, do the following:

import { getVisualizeLoader } from 'ui/visualize/loader';

getVisualizeLoader().then((loader) => {
  // You now have access to the loader
});

The loader exposes the following methods:

  • getVisualizationList(): which returns promise which gets resolved with a list of saved visualizations
  • embedVisualizationWithId(container, savedId, params): which embeds visualization by id
  • embedVisualizationWithSavedObject(container, savedObject, params): which embeds visualization from saved object

Depending on which embed method you are using, you either pass in the id of the saved object for the visualization, or a savedObject, that you can retrieve via the savedVisualizations Angular service by its id. The savedObject give you access to the filter and query logic and allows you to attach listeners to the visualizations. For a more complex use-case you usually want to use that method.

container should be a DOM element (jQuery wrapped or regular DOM element) into which the visualization should be embedded params is a parameter object specifying several parameters, that influence rendering.

You will find a detailed description of all the parameters in the inline docs in the {repo}blob/6.8/src/ui/public/visualize/loader/types.ts[loader source code].

Both methods return an EmbeddedVisualizeHandler, that gives you some access to the visualization. The embedVisualizationWithSavedObject method will return the handler immediately from the method call, whereas the embedVisualizationWithId will return a promise, that resolves with the handler, as soon as the id could be found. It will reject, if the id is invalid.

The returned EmbeddedVisualizeHandler itself has the following methods and properties:

  • destroy(): destroys the embedded visualization. You MUST call that method when navigating away or destroying the DOM node you have embedded into.
  • getElement(): a reference to the jQuery wrapped DOM element, that renders the visualization
  • whenFirstRenderComplete(): will return a promise, that resolves as soon as the visualization has finished rendering for the first time
  • addRenderCompleteListener(listener): will register a listener to be called whenever a rendering of this visualization finished (not just the first one)
  • removeRenderCompleteListener(listener): removes an event listener from the handler again

You can find the detailed EmbeddedVisualizeHandler documentation in its {repo}blob/6.8/src/ui/public/visualize/loader/embedded_visualize_handler.ts[source code].

<<<<<<< HEAD

=== Developing Visualizations

This is a short description of functions and interfaces provided. For more information you should check the kibana source code and the existing visualizations provided with it.

=== Visualization Factory

Use the VisualizationFactory to create a new visualization. The creation-methods create a new visualization tied to the underlying rendering technology. You should also register the visualization with VisTypesRegistryProvider.

import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';

const MyNewVisType = (Private) => {
  const VisFactory = Private(VisFactoryProvider);

  return  VisFactory.createBaseVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    ...
  });
}

VisTypesRegistryProvider.register(MyNewVisType);

The list of common parameters:

  • name: unique visualization name, only lowercase letters and underscore
  • title: title of your visualization as displayed in kibana
  • icon: <string> the EUI icon type to use for this visualization
  • image: instead of an icon you can provide a SVG image (imported)
  • legacyIcon: (DEPRECATED) <string> provide a class name (e.g. for a font awesome icon)
  • description: description of your visualization as shown in kibana
  • hidden: <bool> if set to true, will hide the type from showing up in the visualization wizard
  • visConfig: object holding visualization parameters
  • visConfig.defaults: object holding default visualization configuration
  • visualization: A constructor function for a Visualization.
  • requestHandler: <string> one of the available request handlers or a <function> for a custom request handler
  • responseHandler: <string> one of the available response handlers or a <function> for a custom response handler
  • editor: <string> one of the available editors or Editor class for custom one
  • editorConfig: object holding editor parameters
  • options.showTimePicker: <bool> show or hide time picker (defaults to true)
  • options.showQueryBar: <bool> show or hide query bar (defaults to true)
  • options.showFilterBar: <bool> show or hide filter bar (defaults to true)
  • options.showIndexSelection: <bool> show or hide index selection (defaults to true)
  • stage: <string> Set this to "experimental" to mark your visualization as experimental. Experimental visualizations can also be disabled from the advanced settings. (defaults to "production")
  • feedbackMessage: <string> You can provide a message (which can contain HTML), that will be appended to the experimental notification in visualize, if your visualization is experimental or in lab mode.

Each of the factories have some of the custom parameters, which will be described below.

==== Base Visualization Type The base visualization type does not make any assumptions about the rendering technology you are going to use and works with pure JavaScript. It is the visualization type we recommend to use.

You need to provide a type with a constructor function, a render method which will be called every time options or data change, and a destroy method which will be called to cleanup.

The render function receives the data object and status object which tells what actually changed. Render function needs to return a promise, which should be resolved once the visualization is done rendering.

The status object provides information about changes since the previous render call. Due to performance reasons you need to opt-in for each status change, that you want to be informed about by Kibana. This is done by using the requiresUpdateStatus key in your visualization registration object. You pass it an array, that contains all the status updates you want to receive. By default none of it will be calculated.

The following snippet shows explain all available status updates. You should only activate those changes, that you actually use in your render method.

import { Status } from 'ui/vis/update_status';

// ...
return  VisFactory.createBaseVisualization({
  // ...
  requiresUpdateStatus: [
    // Check for changes in the aggregation configuration for the visualization
    Status.AGGS,
    // Check for changes in the actual data returned from Elasticsearch
    Status.DATA,
    // Check for changes in the parameters (configuration) for the visualization
    Status.PARAMS,
    // Check if the visualization has changes its size
    Status.RESIZE,
    // Check if the time range for the visualization has been changed
    Status.TIME,
    // Check if the UI state of the visualization has been changed
    Status.UI_STATE
  ]
});

If you activate any of these status updates, the status object passed as second parameter to the render method will contain a key for that status (e.g. status[Status.DATA]), that is either true if a change has been detected or false otherwise.

Main Flow
  • Your visualizations constructor will get called with vis object and the DOM-element to which it should render. At this point you should prepare everything for rendering, but not render yet
  • <visualize> component monitors appState, uiState and vis for changes
  • on changes the <visualize>-directive will call your requestHandler. Implementing a request handler is optional, as you might use one of the provided ones.
  • response from requestHandler will get passed to responseHandler. It should convert raw data to something that can be consumed by visualization. Implementing responseHandler is optional, as you might use of of the provided ones.
  • On new data from the responseHandler or on when the size of the surrounding DOM-element has changed, your visualization render-method gets called. It needs to return a promise which resolves once the visualization is done rendering.
  • the visualization should call vis.updateState() any time something has changed that requires to re-render or fetch new data.
import { VisFactoryProvider } from 'ui/vis/vis_factory';
import { VisTypesRegistryProvider } from 'ui/registry/vis_types';

class MyVisualization {
   constructor(el, vis) {
      this.el = el;
      this.vis = vis;
   }
   async render(visData, status) {
      ...
      return 'done rendering';
   }
   destroy() {
      console.log('destroying');
   }
}

const MyNewVisType = (Private) => {
  const VisFactory = Private(VisFactoryProvider);

  return VisFactory.createBaseVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    visualization: MyVisualization
  });
}

VisTypesRegistryProvider.register(MyNewVisType);

==== React Visualization Type React visualization type assumes you are using React as your rendering technology. Just pass in a React component to visConfig.component.

The visualization will receive vis, appState, updateStatus and visData as props. It also has a renderComplete property, which needs to be called once the rendering has completed.

import { ReactComponent } from './my_react_component';

const MyNewVisType = (Private) => {
  const VisFactory = Private(VisFactoryProvider);

  return VisFactory.createReactVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    visConfig: {
       component: ReactComponent
    }
  });
}

=== Visualization Editors By default, visualizations will use the default editor. This is the sidebar editor you see in many of the Kibana visualizations. You can also write your own editor.

==== default editor controller The default editor controller receives an optionsTemplate or optionsTabs parameter. These can be either an AngularJS template or React component.

{
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    editor: 'default',
    editorConfig: {
       optionsTemplate: '<my-custom-options-directive></my-custom-options-directive>' // or
       optionsTemplate: MyReactComponent // or if multiple tabs are required:
       optionsTabs: [
           { title: 'tab 1', template: '<div>....</div> },
           { title: 'tab 2', template: '<my-custom-options-directive></my-custom-options-directive>' },
           { title: 'tab 3', template: MyReactComponent }
       ]
    }
  }

==== custom editor controller You can create a custom editor controller. To do so pass an Editor object (the same format as VisController class). You can make your controller take extra configuration which is passed to the editorConfig property.

import { VisFactoryProvider } from 'ui/vis/vis_factory';

class MyEditorController {
    constructor(el, vis) {
      this.el = el;
      this.vis = vis;
      this.config = vis.type.editorConfig;
   }
   async render(visData) {
      console.log(this.config.my);
      ...
      return 'done rendering';
   }
   destroy() {
      console.log('destroying');
   }
}

const MyNewVisType = (Private) => {
  const VisFactory = Private(VisFactoryProvider);

  return VisFactory.createAngularVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    editor: MyEditorController,
    editorConfig: { my: 'custom config' }
  });
}

VisTypesRegistryProvider.register(MyNewVisType);

=== Visualization Request Handlers Request handler gets called when one of the following keys on AppState change: vis, query, filters or uiState and when timepicker is updated. On top of that it will also get called on force refresh.

By default visualizations will use the courier request handler. They can also choose to use any of the other provided request handlers. It is also possible to define your own request handler (which you can then register to be used by other visualizations).

==== courier request handler courier is the default request handler which works with the default side bar editor.

==== none request handler Using none as your request handles means your visualization does not require any data to be requested.

==== custom request handler You can define your custom request handler by providing a function with the following signature: function (vis, { uiState, appState, timeRange }) { ... }

The timeRange will be an object with a from and to key, that can contain datemath expressions, like now-7d. You can use the datemath library to parse them.

This function must return a promise, which should get resolved with new data that will be passed to responseHandler.

It’s up to function to decide when it wants to issue a new request or return previous data (if none of the objects relevant to the request handler changed).

import { VisFactoryProvider } from 'ui/vis/vis_factory';

const myRequestHandler = async (vis, { appState, uiState, timeRange }) => {
  const data = ... parse ...
  return data;
};

const MyNewVisType = (Private) => {
  const VisFactory = Private(VisFactoryProvider);

  return VisFactory.createAngularVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    requestHandler: myRequestHandler
  });
}

VisTypesRegistryProvider.register(MyNewVisType);

=== Visualization Response Handlers The response handler is a function that receives the data from a request handler, as well as an instance of Vis object. Its job is to convert the data to a format visualization can use. By default default request handler is used which produces a table representation of the data. The data object will then be passed to visualization. This response matches the visData property of the <visualization> directive.

==== default response handler The default response handler converts pure elasticsearch responses into a tabular format. It is the recommended responseHandler. The response object contains a table property, which is an array of all the tables in the response. Each of the table objects has two properties:

  • columns: array of column objects, where each column object has a title property and an aggConfig property
  • rows: array of rows, where each row is an array of non formatted cell values

Here is an example of a response with 1 table, 3 columns and 2 rows:

{
  tables: [{
    columns: [{
      title: 'column1',
      aggConfig: ...
    },{
      title: 'column2',
      aggConfig: ...
    },{
      title: 'column3',
      aggConfig: ...
    }],
    rows: [
      [ '404', 1262, 12.5 ]
      [ '200', 343546, 60.1 ]
    ]
  }];
}

==== none response handler None response handler is an identity function, which will return the same data it receives.

==== custom response handler You can define your custom response handler by providing a function with the following definition: function (vis, response) { …​ }.

Function should return the transformed data object that visualization can consume.

import { VisFactoryProvider } from 'ui/vis/vis_factory';

const myResponseHandler = (vis, response) => {
   // transform the response (based on vis object?)
   const response = ... transform data ...;
   return response;
};

const MyNewVisType(Private) => {
  const VisFactory = Private(VisFactoryProvider);

  return VisFactory.createAngularVisualization({
    name: 'my_new_vis',
    title: 'My New Vis',
    icon: 'my_icon',
    description: 'Cool new chart',
    responseHandler: myResponseHandler
  });
}

VisTypesRegistryProvider.register(MyNewVisType);

=== Vis object The vis object holds the visualization state and is the window into kibana:

  • vis.params: holds the visualization parameters
  • vis.indexPattern: selected index pattern object
  • vis.getState(): gets current visualization state
  • vis.updateState(): updates current state with values from vis.params
  • vis.resetState(): resets vis.params to the values in the current state
  • vis.forceReload(): forces whole cycle (request handler gets called)
  • vis.getUiState(): gets UI state of visualization
  • vis.uiStateVal(name, val): updates a property in UI state
  • vis.isEditorMode(): returns true if in editor mode
  • vis.API.timeFilter: allows you to access time picker
  • vis.API.queryFilter: gives you access to queryFilter
  • vis.API.events.click: default click handler
  • vis.API.events.brush: default brush handler

The visualization gets all its parameters in vis.params, which are default values merged with the current state. If the visualization needs to update the current state, it should update the vis.params and call vis.updateState() which will inform <visualize> about the change, which will call request and response handler and then your visualization’s render method.

For the parameters that should not be saved with the visualization you should use the UI state. These hold viewer-specific state, such as popup open/closed, custom colors applied to the series etc.

You can access filter bar and time picker through the objects defined on vis.API

==== timeFilter

Update the timefilter time values and call update() method on it to update time picker

   timefilter.time.from = moment(ranges.xaxis.from);
   timefilter.time.to = moment(ranges.xaxis.to);
   timefilter.time.mode = 'absolute';
   timefilter.update();

=== AggConfig object

The AggConfig object represents an aggregation search to Elasticsearch, plus some additional functionality to manage data-values that belong to this aggregation. This is primarily used internally in Kibana, but you may find you have a need for it when writing your own visualization. Here we provide short description of some of the methods on it, however the best reference would be to actually check the source code.

  • fieldFormatter(<type>) : returns a function which will format your value according to field formatters defined on the field. The type can be either text or html.
  • makeLabel() : gets the label for the aggregation
  • isFilterable() : return true if aggregation is filterable (you can then call createFilter)
  • createFilter(bucketKey) : creates a filter for specific bucket key
  • getValue(bucket) : gets value for a specific bucket
  • getField() : gets the field used for this aggregation
  • getFieldDisplayName() : gets field display name
  • getAggParams() : gets the arguments to the aggregation
  • The breaking changes documentation, where we try to capture any changes to the APIs as they occur across minors.
  • Meta issue which is tracking the move of the plugin to the new Kibana platform
  • Our Elastic Stack workspace on Slack.
  • The source code, which will continue to be the most accurate source of information. >>>>>>> d26cbef389…​ [DOCS] Adds kibana-pull attribute for release docs (#69554)