Help us understand the problem. What is going on with this article?

Kibanaプラットフォームでアプリケーションをつくってみよう

More than 3 years have passed since last update.

はじめに

Kibana 4のプラグインをつくってみよう」という記事を書いてから1年が経過しました。
この記事を書いた時点ではKibanaのプラグインシステムは公式にサポートされたものではありませんでしたが、先日Kibana 4.2がリリースされ、ドキュメントにプラグインについて記述されていました。

これでようやく公式にプラグインがつくれるぜ!と思ったわけですが、よく調べてみるとなにやら違和感があります。
どうやらこれは、Kibanaにちょっとした機能を追加するようなプラグインではなくて、Kibanaプラットフォーム上に新たなアプリケーションをつくるための仕組みのようです。
バックエンドにNode.jsとElasticsearch、フロントエンドにAngularJSをつかったアプリケーションが、簡単につくれるようになっています。

思っていたものとはちょっと違ったのですが、せっかくなのでプラグインのつくり方を解説したいと思います。

ちなみに、現在公開されているKibanaプラグインには以下のようなものがあります。

Timelionは昨日@johtaniさんが「Timelionの紹介 - Elasticsearch Advent Calendar 2015 1日目」で紹介されていますね。

プラグインの実装準備

それではさっそくプラグインをつくっていきましょう。
なお、Node.js、Kibana 4.2以降、Elasticsearch 2.0以降はすでインストールされているものとします。

プラグインのひな形をつくる

まずはプラグインのひな形をつくりましょう。
今回は Kibana Plugin Yeoman Generator を利用します。

次のコマンドでYeomanとKibana Plugin Yeoman Generatorをインストールします。

npm install -g yo
# 本記事執筆時点では、npmに上がっているgenerator-kibana-pluginが古かったので、githubからインストールします
npm install -g https://github.com/elastic/generator-kibana-plugin.git

次のコマンドでソースコードのひな形を生成します。

mkdir my-kibana-plugin
cd my-kibana-plugin
yo kibana-plugin

プラグイン名と説明を入力すると、以下のようなファイルが生成されます。
ここではmy-kibana-pluginという名前にしています。

my-kibana-plugin
├── gulpfile.js
├── index.js
├── package.json
├── public
│   ├── app.js
│   ├── less
│   │   └── main.less
│   └── templates
│       └── index.html
└── server
    └── routes
        └── example.js

クライアントサイドのコードがpublicの下に、サーバーサイドのコードがserverの下にあります。

プラグインのビルド

生成されたコードをビルドしてパッケージをつくります。

npm install
npm run package

Kibanaのインストールディレクトリの下で次のコマンドを実行すると、プラグインをインストールすることができます。-iオプションでプラグイン名、-uオプションでビルドしたパッケージのパスを指定してください。

./bin/kibana plugin -i my-kibana-plugin -u file:///path/to/my-kibana-plugin/target/my-kibana-plugin-0.1.0.tar.gz

Kibanaを起動します。

./bin/kibana

ブラウザからKibanaの画面を開くと、ナビゲーションバーの一番右に見慣れぬアイコンが表示されているので、これをクリックします。

kibana-plugin-header.png

プラグインのアイコンをクリックして以下のような画面が表示されれば成功です。

kibana-plugin-first-screen.png

開発モード

プラグインの開発時に、ソースコードを少し修正するたびにビルドして、Kibanaを再起動するのはとても面倒です。
そういうときはKibanaの開発モードを利用しましょう。

Kibanaの開発モードは、公式サイトからダウンロードできるものでは利用することができず、ソースコードからKibanaをビルドする必要があります。
CONTRIBUTING.mdを参考にしてKibanaをセットアップしてください。なお、ビルドのスピード向上のため、kibana.dev.ymlを設定しておくことをおすすめします。

Kibanaを開発モードで利用するには、以下のように--devオプションを指定します。

./bin/kibana --dev

つづいて、Kibanaのインストールディレクトリと同じ階層にプラグインのディレクトリを置きます。

├── kibana
└── my-kibana-plugin

プラグインのディレクトリに移動して、ビルドをおこないます。

cd my-kibana-plugin
npm start

これで、プラグインのソースコードを変更すると、自動的にkibana/installedPlugins/にプラグインが配備され、さらにKibanaの再起動がおこなわれます。

プラグインの実装

最近、スコットランド出身の同僚が「ぼくはゲームで日本語おぼえたよ。英語もゲームやってればすぐおぼえるでしょ。」というので、英語のゲームにチャレンジしていたのですが、英単語を調べるのにすごく時間がかかってしまいまともにゲームが進められません。
というわけで、ゲーム内の英語テキストと日本語化MODのテキストをElasticsearchに突っ込んでおいて、英語のテキストを入力したら対応する日本語のテキストを表示するだけのシンプルなアプリをつくってみました。

https://github.com/zoetrope/elastic-translator

これを題材にして、Kibanaプラグインのつくり方を解説していきます。

index.js

まずはindex.jsを見てみましょう。

index.js
module.exports = function (kibana) {
  return new kibana.Plugin({

    name: 'elastic-translator',
    require: ['kibana', 'elasticsearch'],
    uiExports: {
      app: {
        title: 'Elastic Translator',
        description: 'Sample for Kibana plugin',
        main: 'plugins/elastic-translator/app',
        injectVars: function (server, options) {
          var config = server.config();
          return {
            kbnIndex: config.get('kibana.index'),
            esApiVersion: config.get('elasticsearch.apiVersion'),
            esShardTimeout: config.get('elasticsearch.shardTimeout')
          };
        }
      }
    },
    init: function (server, options) {
      var searchApi = require('./server/api/search');
      searchApi(server);
    }
  });
};

このコードでは、プラグインの各種設定を記述します。
Kibana.Pluginのコンストラクタに渡しているuiExports.app.mainには、クライアントサイドのコードのパスを指定します。
initメソッドではサーバーサイドのコードの初期化をおこないます。

サーバーサイドコード

つづいて、サーバーサイドのコードは以下のようになります。

server/api/search.js
module.exports = function (server) {

  var client = server.plugins.elasticsearch.client;

  server.route({
    path: '/elastic-translator/api/search',
    method: 'GET',
    handler: function (req, reply) {
      client.search({
        index: 'elastic-translator',
        q: 'default_text_en:"' + req.query.keywords + '"'
      }, function (error, response) {
        if (!error) {
          reply(response.hits.hits);
        } else {
          server.log(['error', 'elastic-translator'], error);
          reply(error);
        }
      });
    }
  });
};

API

Kibanaでは、Webアプリケーションフレームワークとして hapi.js をつかっています。

上記のコードの中でserver.routehapi.jsのAPIになります。
ここでは、http://localhost:5601/elastic-translator/api/searchGETリクエストを投げたときのAPIを定義しています。

詳しくは以下のドキュメントを参考にしてください。

Elasticsearch

server.plugins.elasticsearch.clientというオブジェクトをつかうと、Kibanaで利用しているElasticsearchにアクセスすることができます。

上記のコードではclient.searchメソッドを利用して、elastic-translatorというインデックスのdefault_text_enフィールドにkeywordsで指定した文字列が含まれるドキュメントを検索して返しています。

Elasticsearch Clientの詳細については、以下のドキュメントを参考にしてください。

ログを出力する

ログを出力するには、以下のようにserver.logメソッドにログレベル、プラグイン名、ログメッセージを渡します。

server.log(['info', 'my-kibana-plugin'], 'log message');

Kibanaのログに以下のように出力されます。

server    log   [21:46:31.321] [info][my-kibana-plugin] log message

クライアントサイドコード

最後にクライアントサイドのコードを見てみましょう。

public/app.js
require('plugins/elastic-translator/less/main.less');

require('ui/chrome').setNavBackground('#222222').setTabs([
  {id: 'search', title: 'Search'},
  {id: 'setting', title: 'Setting'}
]);

var app = require('ui/modules').get('app/elastic-translator', []);

require('ui/routes')
  .when('/search', {
    template: require('plugins/elastic-translator/templates/search.html')
  })
  .when('/setting', {
    template: require('plugins/elastic-translator/templates/setting.html')
  })
  .otherwise({
    redirectTo: '/search'
  });

app.controller('searchController', function ($scope, $http) {
  $scope.search = function (keywords) {
    $http.get('/elastic-translator/api/search', {params: {keywords: keywords}})
      .then(function (response) {
        $scope.result = response;
      });
  };
});

ナビゲーションバーの設定

以下のコードでは、ナビゲーションバーの設定をおこなっています。
ナビゲーションバーの背景色の指定、アイコンの指定、タブの定義やデフォルトタブの指定などをおこなうことができます。
ここでは、SearchSettingの2つのタブを用意しています。

require('ui/chrome').setNavBackground('#222222').setTabs([
  {id: 'search', title: 'Search'},
  {id: 'setting', title: 'Setting'}
]);

ルーティング

クライアントサイドのルーティングには ngRoute を使っています。
以下のコードでは、searchsettingにアクセスしたときのルーティングを指定しています。

require('ui/routes')
  .when('/search', {
    template: require('plugins/elastic-translator/templates/search.html')
  })
  .when('/setting', {
    template: require('plugins/elastic-translator/templates/setting.html')
  })
  .otherwise({
    redirectTo: '/search'
  });

APIの呼び出し

最後に、先ほどサーバーサイドのコードで定義したAPIを呼び出します。

まず、以下のようなテンプレートを用意します。
検索キーワードを入力するためのinputタグと、検索結果を羅列しているだけのシンプルなものです。
詳細についてはAngularJSのドキュメントを参照してください。

public/templates/search.html
<div class="container" ng-controller="searchController">
  <form role="form" class="search-form" ng-submit="search(keywords)" name="searchKeywords">
    <div class="input-group">
      <input ng-model="keywords" placeholder="Keywords ..." type="text" class="form-control">
      <span class="input-group-btn">
        <button type="submit" >
          <span class="fa fa-play"></span></button>
      </span>
    </div>
  </form>

  <div class="row">
    <div class="col-12-sm">
      <div ng-repeat="val in result.data">
        <div class="panel panel-default">
          <div class="panel-heading">
            {{val._source.default_text_en}}
          </div>
          <div class="panel-body">
            {{val._source.default_text_ja}}
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

以下のコードでは、AngularJSの controller を定義して、その中で $http をつかってサーバーにリクエストを投げています。

app.controller('searchController', function ($scope, $http) {
  $scope.search = function (keywords) {
    $http.get('/elastic-translator/api/search', {params: {keywords: keywords}})
      .then(function (response) {
        $scope.result = response;
      });
  };
});

まとめ

後半はちょっと駆け足気味になってしまいましたが、いかがだったでしょうか。
個人的な感想としては、Elasticsearchを使ったWebアプリケーションがさくっとお手軽につくれるのがいいなと思いました。

また、今回は紹介しきれませんでしたが、アプリケーション内でKibanaが提供しているコンポーネントを利用することもできるようです。
グラフのコンポーネントを使いまわすことができれば、いろいろと面白いアプリケーションがつくれそうで期待が膨らみますね!

zoetro
YAMLエンジニア
http://zoetrope.hatenablog.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした