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;
});
});
Kibana源码剖析 —— savedSearch从读取到跳转的更多相关文章
- Apache Spark源码剖析
Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著 ISBN 978-7-121-25420- ...
- SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现
SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...
- Android源码剖析之Framework层升级版(窗口、系统启动)
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 看本篇文章之前,建议先查看: Android源码剖析之Framework层基础版 前面讲了frame ...
- 玩转Android之Picasso使用详详详详详详解,从入门到源码剖析!!!!
Picasso是Squareup公司出的一款图片加载框架,能够解决我们在Android开发中加载图片时遇到的诸多问题,比如OOM,图片错位等,问题主要集中在加载图片列表时,因为单张图片加载谁都会写.如 ...
- 《Apache Spark源码剖析》
Spark Contributor,Databricks工程师连城,华为大数据平台开发部部长陈亮,网易杭州研究院副院长汪源,TalkingData首席数据科学家张夏天联袂力荐1.本书全面.系统地介绍了 ...
- STL源码剖析 迭代器(iterator)概念与编程技法(三)
1 STL迭代器原理 1.1 迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...
- (升级版)Spark从入门到精通(Scala编程、案例实战、高级特性、Spark内核源码剖析、Hadoop高端)
本课程主要讲解目前大数据领域最热门.最火爆.最有前景的技术——Spark.在本课程中,会从浅入深,基于大量案例实战,深度剖析和讲解Spark,并且会包含完全从企业真实复杂业务需求中抽取出的案例实战.课 ...
- 菜鸟nginx源码剖析 框架篇(一) 从main函数看nginx启动流程(转)
俗话说的好,牵牛要牵牛鼻子 驾车顶牛,处理复杂的东西,只要抓住重点,才能理清脉络,不至于深陷其中,不能自拔.对复杂的nginx而言,main函数就是“牛之鼻”,只要能理清main函数,就一定能理解其中 ...
- ArrayList源码剖析
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
随机推荐
- Web Essentials之样式表StyleSheets
返回Web Essentials功能目录 本篇目录 智能感知 视觉提示 验证 Web标准 转换器 Web Essentials中大多数的CSS功能也适用于LESS. 智能感知 生成供应商特定的属性 如 ...
- EF6(CodeFirst)+MySql开发遇到的坑
最近一不小心偷个懒就已经过了好几个月了,真是惭愧惭愧,出来混终究是要还的,我还是把”脱坑指南“写完吧,-_-~~.点我打开上篇博客 0x001.架构名”dbo”の殇 坑之首也,当提架构名,在mssql ...
- EF6(CodeFirst)+MySql开发脱坑指南
废话 话说当年,在一个春光明媚的晌午,邂逅了迷人的丁香姑娘,从此拜倒在了她的石榴裙下,至今不能自拔,这位丁香姑娘就是ORM思想. 所谓ORM思想,我的理解就是根据一定的规则,把程序中的对象和数据库中的 ...
- 【C语言学习】《C Primer Plus》第12章 存储类、链接和内存管理
学习总结 1.作用域可分为代码块作用域.函数原型作用域或者文件作用域. 代码块作用域例子: { for(int i=0;i<10;i++){ //C99允许 … //i的作用域 } ... ...
- 犀利的background-clip:text,实现K歌字幕效果
今天学到了一个新的CSS3属性,更准确的说是属性值,那就是background-clip:text.利用此属性值可以制作出很神奇的效果.可惜只有chrome支持,不过今天可以先来玩玩这个属性. 先来介 ...
- 消息队列-rabbitMQ
消息队列两个用处:服务间解耦,缓解压力(削峰平谷),以前用过ZMQ.狼厂内部的NMQ,现在接触了java开源的kafka和RabbitMQ.目前先不求甚解,有个大概的认识. RabbitMQ的安装和入 ...
- 将不确定变成确定~Uri文本文件不用浏览器自动打开,而是下载到本地
回到目录 这个标题有点长,简单来说就是,对于一个文件下载来说,是否可以提示用户,让它去保存,而不是将它在浏览器中打开,在浏览器中打开有个致命问题,那就是,如果你的页面编码和文件的编码不一致时,打开的就 ...
- Android开发学习之路-使用Handler和Message更新UI
在Android中,在非主线程中更新UI控件是不安全的,app在运行时会直接Crash,所以当我们需要在非主线程中更新UI控件,那么就需要用到Handler和Message来实现 Demo中,使用到一 ...
- 更新日志 - BugHD iOS 客户端上线
中秋.十一长假归来,"满血复活"的我们做了 fir.im 和 BugHD 的优化更新:) BugHD 新增功能 1.iOS 客户端上线 BugHD iOS 客户端上线了,你可以随时 ...
- Mysql 事件(定时任务)
mysql 创建任务(事件) 1.检查数据库事件是否开启,如果 event_scheduler 等于 NO表示开启 SELECT @@event_scheduler; SHOW VARIABLES L ...