Kibana源碼剖析 —— savedSearch從讀取到跳轉


持久化對象

Kibana中可以查詢到很多保存的對象,他們都存儲在es中一個叫做.kibana的索引中。

  • 搜索 存儲在type為search中;
  • 圖表 存儲在type為visualization中;
  • 儀表板 存儲在type為dashboard中;

每個plugins下的tab頁都有一個對應的savedObject對象,比如

  • 檢索頁對應的是savedSearch對象(discover/saved_searches/_saved_search.js)
  • 圖表頁對應的是savedVisualization對象(visualize/saved_visualizations/saved_visualizations.js)
  • 儀表板對應的是savedDashboard對象(dashboard/services/saved_dashboard.js)

這些JS都有一個特點,就是會在加載的時候注冊到一個saved_object_registry的對象中去

  require('plugins/settings/saved_object_registry').register({
    service: 'savedSearches',
    title: 'searches'
  });

通過這個注冊對象,可以快速的拿到對應的服務。

savedSearch

以savedSearch為例,說明如何在settings頁面獲取到該對象

首先代碼的入口在settings/objects/index.js,它加載了settings/objects/_object.js

//第二步,由於臟檢查觸發了getData,因此會去執行services的查詢
var getData = function (filter) {
          //獲取保存對象services,這里拿到存儲幾個tab頁對應的services的服務數組,然后遍歷。
          var services = registry.all().map(function (obj) {
            var service = $injector.get(obj.service);//獲取對應的服務
            return service.find(filter).then(function (data) {//執行service對應的find()方法
              return {
                service: service,
                serviceName: obj.service,
                title: obj.title,
                type: service.type,
                data: data.hits,
                total: data.total
              };
            });
          });

          $q.all(services).then(function (data) {
            $scope.services = _.sortBy(data, 'title');
            var tab = $scope.services[0];
            if ($state.tab) $scope.currentTab = tab = _.find($scope.services, {title: $state.tab});

            $scope.$watch('state.tab', function (tab) {
              if (!tab) $scope.changeTab($scope.services[0]);
            });
          });
        };
//第一步,...... 頁面剛加載時,由於頁面綁定了advancedFilter,此時的值為undefined,因此會觸發臟檢查,從而觸發getData()方法。
$scope.$watch('advancedFilter', function (filter) {
          getData(filter);
        });

由上面的代碼可以看到,在頁面初始化時,會挨個service(檢索、圖表、儀表板)的服務執行find。

那么看看find()方法的內容就行了,以searchServices為例:

this.find = function (searchString, size) {
      var self = this;
      size = (size == null) ? 100 : size;
      var body;//封裝請求體
      if (searchString) {
        body = {
          query: {
            simple_query_string: {
              query: searchString + '*',
              fields: ['title^3', 'description'],
              default_operator: 'AND'
            }
          }
        };
      } else {
        body = { query: {match_all: {}}};
      }
//執行查詢
      return es.search({
        index: configFile.kibana_index,
        type: 'search',
        body: body,
        size: size
      })
      .then(function (resp) {
//返回的數據進行改造,主要是增加source的id和url,id就是保存對象的名稱;url則主要用於后期的頁面跳轉
        return {
          total: resp.hits.total,
          hits: resp.hits.hits.map(function (hit) {
            var source = hit._source;
            source.id = hit._id;
            source.url = self.urlFor(hit._id);
            return source;
          })
        };
      });
    };

這樣基本上完成了對象的查詢,然后看看頁面是如何定義的把!

 <!-- 對象列表 遍歷services數組創建對應的service列表-->
      <div ng-repeat="service in services" ng-class="{ active: state.tab === service.title }" class="tab-pane">
        <ul class="list-unstyled">
          <li class="item" ng-repeat="item in service.data | orderBy:'title'">
            <div class="actions pull-right">
              <button
                ng-click="edit(service, item)"
                class="btn btn-default"
                aria-label="Edit">
                <span class="sr-only" translate="edit">Edit</span>
                <i aria-hidden="true" class="fa fa-pencil"></i>
              </button><!-- 該按鈕對應了頁面上的編輯按鈕 -->

              <button
                ng-click="open(item)"
                class="btn btn-info"
                aria-label="Hide">
                <span class="sr-only" translate="hide">Hide</span>
                <i aria-hidden="true" class="fa fa-eye"></i>
              </button><!-- 該按鈕對應了頁面上的跳轉按鈕,小眼睛的那個 -->
            </div>

            <div class="pull-left">
              <input
                ng-click="toggleItem(item)"
                ng-checked="selectedItems.indexOf(item) >= 0"
                type="checkbox" ><!-- 該按鈕對應了頁面上的單選框 -->
            </div>

            <div class="item-title">
              <a ng-click="edit(service, item)">{{ item.title }}</a>
            </div><!-- 該部分的內容為頁面上的對象名稱,點擊可以直接進行修改 -->
          </li>
          <li ng-if="!service.data.length" class="empty">
            <span translate="no_found_1">No </span>"{{service.title}}" <span translate="no_found_2">found.</span></li>
        </ul>
      </div>
    </div>

當點擊跳轉按鈕時,會觸發open(item)方法,item即每個對象保存的內容。就拿searchSource來說,除了之前保存的內容,還多了id和url.

$scope.open = function (item) {
    kbnUrl.change(item.url.substr(1));
};

通過kbnUrl實現頁面的跳轉。

頁面的跳轉

繼上篇,存儲的對象會通過kbnUrl服務改變url地址:

$scope.open = function (item) {
    kbnUrl.change(item.url.substr(1));
};

kbnUrl在components/url/url.js中聲明:

self.change = function (url, paramObj) {
    self._changeLocation('url', url, paramObj);
};

_changeLocation()

其中,url會改變為:"/discover/id名稱"

    self._changeLocation = function (type, url, paramObj, replace) {
     //改變地址前,記錄歷史信息,用於回退
      var prev = {
        path: $location.path(),
        search: $location.search()
      };

      url = self.eval(url, paramObj);
      $location[type](url);//改變url地址,等待臟值檢查,進行刷新
      if (replace) $location.replace();

      var next = {
        path: $location.path(),
        search: $location.search()
      };

      ...
    };

當觸發臟值檢查后,會跳轉到http://localhost:5601/#/discover/id名稱

頁面的初始化

初始化前的准備,ip和savedSearch

在頁面進入到discover的時候,會進行兩個操作:

  • ip:獲取索引列表
  • savedSearch:通過id查詢保存在.kibana索引中的信息

這兩個初始化的操作是通過路由的resolve參數綁定的。resolve有個特性,就是如果傳入的是一個Promise對象,就會等到這個Promise執行結束,再加載controller。

因此,只要加載了discover對應的controller,就說明上面的兩個對象已經准備好了。並且都可以使用了...

使用的方法都是下面的格式:

$route.current.locals.savedSearch;
$route.current.locals.ip;

discover.js的路由配置:

require('routes')
  .when('/discover/:id?', {
    template: require('text!plugins/discover/index.html'),
    reloadOnSearch: false,
    resolve: {
      ip: function (Promise, courier, config, $location) {
        return courier.indexPatterns.getIds()
        .then(function (list) {
          var stateRison = $location.search()._a;
          var state;
          try { state = rison.decode(stateRison); } catch (e) {}
          state = state || {};

          var specified = !!state.index;
          var exists = _.contains(list, state.index);
          var id = exists ? state.index : config.get('defaultIndex');

          return Promise.props({
            list: list,
            loaded: courier.indexPatterns.get(id),
            stateVal: state.index,
            stateValFound: specified && exists
          });
        });
      },
      savedSearch: function (courier, savedSearches, $route) {
        return savedSearches.get($route.current.params.id)
        .catch(courier.redirectWhenMissing({
          'search': '/discover',
          'index-pattern': '/settings/objects/savedSearches/' + $route.current.params.id
        }));
      }
    }
  });

觸發查詢

由於時間空間綁定了一個變量,在discover頁,對着變量進行了$watch監視,因此當第一次創建該值時,就會觸發$watch

$scope.$watch('state.interval', function (interval, oldInterval) {
          if (interval !== oldInterval && interval === 'auto') {
            $scope.showInterval = false;
          }
          $scope.fetch();//fetch就是觸發查詢的方法
        });
//初始化創建Interval
    var $state = $scope.state = new AppState(getStateDefaults());
    function getStateDefaults() {
      return {
        ...
        interval: 'auto',
        ...
      };
    }

此時就會觸發$watch

IP

這個方法比較簡單,主要是為了獲取當前的索引列表,然后返回:

ip: function (Promise, courier, config, $location) {
        return courier.indexPatterns.getIds()
        .then(function (list) {
          var stateRison = $location.search()._a;
          var state;
          try { state = rison.decode(stateRison); } catch (e) {}
          state = state || {};

          var specified = !!state.index;
          var exists = _.contains(list, state.index);
          var id = exists ? state.index : config.get('defaultIndex');

          return Promise.props({
            list: list,
            loaded: courier.indexPatterns.get(id),
            stateVal: state.index,
            stateValFound: specified && exists
          });
        });
      },

其中courier.indexPatterns.getIds()的會返回所有的索引列表,而且這個indexPatterns.getIds()的方法僅會執行一次:

在_get_ids.js中,執行下面的查詢,然后進行緩存

es.search({
        index: configFile.kibana_index,//索引名稱為".kibana"
        type: 'index-pattern',
        fields: [],
        body: {
          query: { match_all: {} },
          size: 2147483647
        }
      })

savedSearch

這個方法比較復雜,它首先去saved_searches工廠中,通過id獲取savedSearch對象。但是其實並沒有對savedSearch進行緩存,而是直接創建新的savedSearch:

this.get = function (id) {
    return (new SavedSearch(id)).init();
};

SavedSearch繼承於savedObject

_(SavedSearch).inherits(courier.SavedObject);

function SavedSearch(id) {
      courier.SavedObject.call(this, {
        type: SavedSearch.type,
        mapping: SavedSearch.mapping,
        searchSource: SavedSearch.searchSource,

        id: id,
        defaults: {
          title: 'New Saved Search',
          description: '',
          columns: [],
          hits: 0,
          sort: [],
          version: 1
        }
      });
    }

然后調用savedObject中對應的init()方法

self.init = _.once(function () {
        ...//創建docSource對應的信息
        docSource
        .index(configFile.kibana_index)
        .type(type)
        .id(self.id);//指定查詢的文檔

        //檢查是否定義過"search"類型,如果沒有定義過則需要在es中的.kibana中創建相關的類型。這是因為默認的es中並沒有任何kibana初始化的信息,如果第一次登陸kibana,es中的.kibana索引是沒有任何內容的
        
        return mappingSetup.isDefined(type)
        .then(function (defined) {
          if (defined) return true;

          mapping.kibanaSavedObjectMeta = {
            properties: {
              // setup the searchSource mapping, even if it is not used but this type yet
              searchSourceJSON: {
                type: 'string'
              }
            }
          };
          return mappingSetup.setup(type, mapping);
        })
        .then(function () {
        ...
		//執行查詢
          return docSource.fetch()
          .then(self.applyESResp);
        })
        .then(function () {
          return customInit.call(self);
        })
        .then(function () {
          return self;
        });
      });


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM