• 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;
            });
          });
    
  • 相关阅读:
    [Qt]《开发指南》3.1源码分析
    [c++] 头文件
    [Qt] 信号和槽
    [Qt] 编译问题
    [Qt] 项处理组件
    MYSQL 之 JDBC(十三):处理事务
    MYSQL 之 JDBC(十二): 处理Blob
    MYSQL 之 JDBC(十一): JDBC获取插入记录的主键值
    MYSQL 之 JDBC(十): JDBC的元数据
    MYSQL 之 JDBC(九):增删改查(七)DAO的补充和重构
  • 原文地址:https://www.cnblogs.com/xing901022/p/5158425.html
Copyright © 2020-2023  润新知