持久化对象
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; }) }; }); };
这样基本上完成了对象的查询,然后看看页面是如何定义的把!
- No "{ {service.title}}" found.
当点击跳转按钮时,会触发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; }); });