搜索页

前文已经说到,kibana.js 中依次加载了各主要功能模块的入口。比如搜索页是 src/core_plugins/kibana/public/discover/index.js。通过这个文件路径就可以猜到,有关搜索页的功能,代码应该都在 src/core_plugins/kibana/public/discover/ 里了。这个目录下的文件有:

  • _hit_sort_fn.js
  • components/
  • controllers/
  • directives/
  • index.html
  • index.js
  • partials/
  • saved_searches/
  • styles/

这也是一个比较标准的 angular 模块的目录结构了。一眼就能知道,controller、directive 等等分别应该进哪里去看。当然首先第一步还是看 index.js:

  1. import 'plugins/kibana/discover/saved_searches/saved_searches';
  2. import 'plugins/kibana/discover/directives/no_results';
  3. import 'plugins/kibana/discover/directives/timechart';
  4. import 'ui/collapsible_sidebar';
  5. import 'plugins/kibana/discover/components/field_chooser/field_chooser';
  6. import 'plugins/kibana/discover/controllers/discover';
  7. import 'plugins/kibana/discover/styles/main.less';
  8. import 'ui/doc_table/components/table_row';
  9. import savedObjectRegistry from 'ui/saved_objects/saved_object_registry';
  10. savedObjectRegistry.register(require('plugins/kibana/discover/saved_searches/saved_search_register'));

已存搜索、事件数趋势图、事件列表、字段列表,各自载入了。下面可以看一下这几个功能点的实现。

plugins/kibana/discover/saved_searches/saved_searches.js

  • 定义 savedSearches 这个 angular service,用来操作 kbnIndex 索引里 search 这个类型下的数据;
  • 加载了 saved_searches/_saved_searches.js 提供的 savedSearch 这个 angular factory,这里定义了一个搜索 (search) 在 kbnIndex 里的数据结构,包括 title, description, hits, column, sort, version 等字段(这部分内容,可以直接通过读取 Elasticsearch 中的索引内容看到,比阅读代码更直接,本章最后即专门介绍 kbnIndex 中的数据结构),看的是不是有点眼熟?没错,这个 savedSearch 就是继承了上一节我们介绍的那个 courier 的 savedObject:
  1. module.factory('SavedSearch', function (courier) {
  2. _.class(SavedSearch).inherits(courier.SavedObject);
  3. function SavedSearch(id) {
  4. ...
  • 还加载并注册了 plugins/kibana/management/saved_object_registry,表示可以在 management 里修改这里的 savedSearches 对象。

plugins/kibana/discover/directives/timechart.js

  • 加载 ui/vislib
  • 提供 discoverTimechart 这个 angular directive,监听 “data” 并调用 vislib.Chart 对象绘图。

vislib 是整个 Kibana 可视化的实现部分,下一节会更详细的讲述。

plugins/kibana/discover/components/field_chooser/field_chooser.js

  • 提供 discFieldChooser 这个 angular directive,其中监听字段和事件的变化,并调用计算常用字段排行,
  1. $scope.$watchMulti([
  2. '[]fieldCounts',
  3. '[]columns',
  4. '[]hits'
  5. ], function (cur, prev) {
  6. var newHits = cur[2] !== prev[2];
  7. var fields = $scope.fields;
  8. var columns = $scope.columns || [];
  9. var fieldCounts = $scope.fieldCounts;
  10. if (!fields || newHits) {
  11. $scope.fields = fields = getFields();
  12. }
  13. _.chain(fields)
  14. .each(function (field) {
  15. field.displayOrder = _.indexOf(columns, field.name) + 1;
  16. field.display = !!field.displayOrder;
  17. field.rowCount = fieldCounts[field.name];
  18. })
  19. .sortBy(function (field) {
  20. return (field.count || 0) * -1;
  21. })
  22. .groupBy(function (field) {
  23. if (field.display) return 'selected';
  24. return field.count > 0 ? 'popular' : 'unpopular';
  25. })
  26. .tap(function (groups) {
  27. groups.selected = _.sortBy(groups.selected || [], 'displayOrder');
  28. groups.popular = groups.popular || [];
  29. groups.unpopular = groups.unpopular || [];
  30. var extras = groups.popular.splice(config.get('fields:popularLimit'));
  31. groups.unpopular = extras.concat(groups.unpopular);
  32. })
  33. .each(function (group, name) {
  34. $scope[name + 'Fields'] = _.sortBy(group, name === 'selected' ? 'display' : 'name');
  35. })
  36. .commit();
  37. $scope.fieldTypes = _.union([undefined], _.pluck(fields, 'type'));
  38. });

监听 “data” 并调用 $scope.details() 方法,

提供 $scope.vizLocation() 方法。方法中,根据字段的类型不同,分别可能使用 date_histogram/geohash_grid/terms 聚合函数,创建可视化模型,然后带着当前页这些设定——前面说过,各 app 之间通过 globalState 共享状态,也就是 URL 中的 ?_a=(...)。各 app 会通过 rison.decode($location.search()._a)rison.encode($location.search()._a) 设置和读取——跳转到 “/visualize/create” 页面,相当于是这三个常用聚合的快速可视化操作。

默认的 create 页的 rison 如下:

  1. return '#/visualize/create?' + $.param(_.assign($location.search(), {
  2. indexPattern: $scope.state.index,
  3. type: type,
  4. _a: rison.encode({
  5. filters: $scope.state.filters || [],
  6. query: $scope.state.query || undefined,
  7. vis: {
  8. type: type,
  9. aggs: [
  10. agg,
  11. {schema: 'metric', type: 'count', 'id': '2'}
  12. ]
  13. }
  14. })
  15. });

之前章节的 url 示例中,读者如果注意的话,会发现 id 是从 2 开始的,原因即在此。

  • 加载 plugins/kibana/discover/components/field_chooser/lib/field_calculator.js ,提供 fieldCalculator.getFieldValueCounts() 方法,在 $scope.details() 中读取被点击的字段值的情况。
  • 加载 plugins/kibana/discover/components/field_chooser/discover_field.js,提供 discoverField 这个 angular directive,用于弹出浮层展示临时的 visualize(调用上一条提供的 $scope.details() 方法),同时给被点击的字段加常用度;加载 plugins/kibana/discover/components/field_chooser/lib/detail_views/string.html 网页,用于浮层效果。网页中对 indexed 或 scripted 类型的字段,可以调用前面提到的 vizLocation() 方法。
  1. import detailsHtml from 'plugins/kibana/discover/components/field_chooser/lib/detail_views/string.html';
  2. $scope.toggleDetails = function (field, recompute) {
  3. if (_.isUndefined(field.details) || recompute) {
  4. // This is inherited from fieldChooser
  5. $scope.details(field, recompute);
  6. detailScope.$destroy();
  7. detailScope = $scope.$new();
  8. detailScope.warnings = getWarnings(field);
  9. detailsElem = $(detailsHtml);
  10. $compile(detailsElem)(detailScope);
  11. $elem.append(detailsElem).addClass('active');
  12. } else {
  13. delete field.details;
  14. detailsElem.remove();
  15. $elem.removeClass('active');
  16. }
  17. };
  • 加载并渲染 plugins/kibana/discover/components/field_chooser/field_chooser.html 网页。网页中使用了上一条提供的 discover-field。

plugins/kibana/discover/controllers/discover.js

加载了诸多 js,主要做了:

  • 为 “/discover/:id?” 提供 route 并加载 plugins/discover/index.html 网页。
  • 提供 discoverApp 这个 angular controller。
  • 加载 ui/vis.js 并在 setupVisualization 函数中绘制 histogram 图。
  1. var visStateAggs = [
  2. {
  3. type: 'count',
  4. schema: 'metric'
  5. },
  6. {
  7. type: 'date_histogram',
  8. schema: 'segment',
  9. params: {
  10. field: $scope.opts.timefield,
  11. interval: $state.interval
  12. }
  13. }
  14. ];
  15. $scope.vis = new Vis($scope.indexPattern, {
  16. title: savedSearch.title,
  17. type: 'histogram',
  18. params: {
  19. addLegend: false,
  20. addTimeMarker: true
  21. },
  22. listeners: {
  23. click: function (e) {
  24. timefilter.time.from = moment(e.point.x);
  25. timefilter.time.to = moment(e.point.x + e.data.ordered.interval);
  26. timefilter.time.mode = 'absolute';
  27. },
  28. brush: brushEvent
  29. },
  30. aggs: visStateAggs
  31. });
  32. $scope.searchSource.aggs(function () {
  33. $scope.vis.requesting();
  34. return $scope.vis.aggs.toDsl();
  35. });