Skip to content

Commit

Permalink
Merge pull request #6105 from trevan/doc-detail-views-registry
Browse files Browse the repository at this point in the history
Add registry for doc table details
  • Loading branch information
spalger committed Feb 5, 2016
2 parents d68f018 + ec22f09 commit 0b122eb
Show file tree
Hide file tree
Showing 17 changed files with 571 additions and 287 deletions.
13 changes: 13 additions & 0 deletions src/plugins/kbn_doc_views/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = function (kibana) {

return new kibana.Plugin({

uiExports: {
docViews: [
'plugins/kbn_doc_views/kbn_doc_views'
]
}

});

};
4 changes: 4 additions & 0 deletions src/plugins/kbn_doc_views/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "kbn_doc_views",
"version": "1.0.0"
}
167 changes: 167 additions & 0 deletions src/plugins/kbn_doc_views/public/__tests__/doc_views.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import angular from 'angular';
import _ from 'lodash';
import sinon from 'auto-release-sinon';
import expect from 'expect.js';
import ngMock from 'ngMock';
import $ from 'jquery';
import 'ui/render_directive';
import 'plugins/kbn_doc_views/views/table';
import docViewsRegistry from 'ui/registry/doc_views';
const hit = {
'_index': 'logstash-2014.09.09',
'_type': 'apache',
'_id': '61',
'_score': 1,
'_source': {
'extension': 'html',
'bytes': 100,
'area': [{lat: 7, lon: 7}],
'noMapping': 'hasNoMapping',
'objectArray': [{foo: true}, {bar: false}],
'_underscore': 1
}
};

// Load the kibana app dependencies.
let $parentScope;
let $scope;
let indexPattern;
let flattened;
let docViews;

const init = function ($elem, props) {
ngMock.inject(function ($rootScope, $compile) {
$parentScope = $rootScope;
_.assign($parentScope, props);
$compile($elem)($parentScope);
$elem.scope().$digest();
$scope = $elem.isolateScope();
});
};

const destroy = function () {
$scope.$destroy();
$parentScope.$destroy();
};

describe('docViews', function () {
let $elem;
let initView;

beforeEach(ngMock.module('kibana'));
beforeEach(function () {
const aggs = 'index-pattern="indexPattern" hit="hit" filter="filter"';
$elem = angular.element(`<render-directive ${aggs} definition="view.directive"></render-directive>`);
ngMock.inject(function (Private) {
indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
flattened = indexPattern.flattenHit(hit);
docViews = Private(docViewsRegistry);
});
initView = function initView(view) {
$elem.append(view.directive.template);
init($elem, {
indexPattern: indexPattern,
hit: hit,
view: view,
filter: sinon.spy()
});
};
});

afterEach(function () {
destroy();
});

describe('Table', function () {
beforeEach(function () {
initView(docViews.byName.Table);
});
it('should have a row for each field', function () {
const rows = $elem.find('tr');
expect($elem.find('tr').length).to.be(_.keys(flattened).length);
});

it('should have the field name in the first column', function () {
_.each(_.keys(flattened), function (field) {
expect($elem.find('td[title="' + field + '"]').length).to.be(1);
});
});

it('should have the a value for each field', function () {
_.each(_.keys(flattened), function (field) {
const cellValue = $elem.find('td[title="' + field + '"]').siblings().find('.doc-viewer-value').text();

// This sucks, but testing the filter chain is too hairy ATM
expect(cellValue.length).to.be.greaterThan(0);
});
});


describe('filtering', function () {
it('should apply a filter when clicking filterable fields', function () {
const cell = $elem.find('td[title="bytes"]').next();

cell.find('.fa-search-plus').first().click();
expect($scope.filter.calledOnce).to.be(true);
cell.find('.fa-search-minus').first().click();
expect($scope.filter.calledTwice).to.be(true);
});

it('should NOT apply a filter when clicking non-filterable fields', function () {
const cell = $elem.find('td[title="area"]').next();

cell.find('.fa-search-plus').first().click();
expect($scope.filter.calledOnce).to.be(false);
cell.find('.fa-search-minus').first().click();
expect($scope.filter.calledTwice).to.be(false);
});
});

describe('warnings', function () {
it('displays a warning about field name starting with underscore', function () {
const cells = $elem.find('td[title="_underscore"]').siblings();
expect(cells.find('.doc-viewer-underscore').length).to.be(1);
expect(cells.find('.doc-viewer-no-mapping').length).to.be(0);
expect(cells.find('.doc-viewer-object-array').length).to.be(0);
});

it('displays a warning about missing mappings', function () {
const cells = $elem.find('td[title="noMapping"]').siblings();
expect(cells.find('.doc-viewer-underscore').length).to.be(0);
expect(cells.find('.doc-viewer-no-mapping').length).to.be(1);
expect(cells.find('.doc-viewer-object-array').length).to.be(0);
});

it('displays a warning about objects in arrays', function () {
const cells = $elem.find('td[title="objectArray"]').siblings();
expect(cells.find('.doc-viewer-underscore').length).to.be(0);
expect(cells.find('.doc-viewer-no-mapping').length).to.be(0);
expect(cells.find('.doc-viewer-object-array').length).to.be(1);
});
});

});

describe('JSON', function () {
beforeEach(function () {
initView(docViews.byName.JSON);
});
it('has pretty JSON', function () {
expect($scope.hitJson).to.equal(angular.toJson(hit, true));
});

it('should have a global ACE object', function () {
expect(window.ace).to.be.a(Object);
});

it('should have one ACE div', function () {
expect($elem.find('div[id="json-ace"]').length).to.be(1);
});

it('should contain the same code as hitJson', function () {
const editor = window.ace.edit($elem.find('div[id="json-ace"]')[0]);
const code = editor.getSession().getValue();
expect(code).to.equal($scope.hitJson);
});
});
});
2 changes: 2 additions & 0 deletions src/plugins/kbn_doc_views/public/kbn_doc_views.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import 'plugins/kbn_doc_views/views/table';
import 'plugins/kbn_doc_views/views/json';
16 changes: 16 additions & 0 deletions src/plugins/kbn_doc_views/public/views/json.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div
id="json-ace"
ng-model="hitJson"
readonly
ui-ace="{
useWrapMode: true,
onLoad: aceLoaded,
advanced: {
highlightActiveLine: false
},
rendererOptions: {
showPrintMargin: false,
maxLines: 4294967296
},
mode: 'json'
}"></div>
26 changes: 26 additions & 0 deletions src/plugins/kbn_doc_views/public/views/json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import _ from 'lodash';
import angular from 'angular';
import 'ace';
import docViewsRegistry from 'ui/registry/doc_views';

import jsonHtml from './json.html';

docViewsRegistry.register(function () {
return {
title: 'JSON',
order: 20,
directive: {
template: jsonHtml,
scope: {
hit: '='
},
controller: function ($scope) {
$scope.hitJson = angular.toJson($scope.hit, true);

$scope.aceLoaded = (editor) => {
editor.$blockScrolling = Infinity;
};
}
}
};
});
49 changes: 49 additions & 0 deletions src/plugins/kbn_doc_views/public/views/table.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<table class="table table-condensed">
<tbody>
<tr ng-repeat="field in fields">
<td field-name="field"
field-type="mapping[field].type"
width="1%"
class="doc-viewer-field">
</td>
<td width="1%" class="doc-viewer-buttons" ng-if="filter">
<span ng-if="mapping[field].filterable">
<i ng-click="filter(mapping[field], flattened[field], '+')"
tooltip="Filter for value"
tooltip-append-to-body="1"
class="fa fa-search-plus"></i>
<i ng-click="filter(mapping[field], flattened[field],'-')"
tooltip="Filter out value"
tooltip-append-to-body="1"
class="fa fa-search-minus"></i>
</span>
<span ng-if="!mapping[field].filterable" tooltip="Unindexed fields can not be searched">
<i class="fa fa-search-plus text-muted"></i>
<i class="fa fa-search-minus text-muted"></i>
</span>
<span ng-if="columns">
<i ng-click="toggleColumn(field)"
tooltip="Toggle column in table"
tooltip-append-to-body="1"
class="fa fa-columns"></i>
</span>
</td>

<td>
<i ng-if="!mapping[field] && field[0] === '_'"
tooltip-placement="top"
tooltip="Field names beginning with _ are not supported"
class="fa fa-warning text-color-warning ng-scope doc-viewer-underscore"></i>
<i ng-if="!mapping[field] && field[0] !== '_' && !showArrayInObjectsWarning(doc, field)"
tooltip-placement="top"
tooltip="No cached mapping for this field. Refresh field list from the Settings > Indices page"
class="fa fa-warning text-color-warning ng-scope doc-viewer-no-mapping"></i>
<i ng-if="showArrayInObjectsWarning(doc, field)"
tooltip-placement="top"
tooltip="Objects in arrays are not well supported."
class="fa fa-warning text-color-warning ng-scope doc-viewer-object-array"></i>
<div class="doc-viewer-value" ng-bind-html="typeof(formatted[field]) === 'undefined' ? hit[field] : formatted[field] | trustAsHtml"></div>
</td>
</tr>
</tbody>
</table>
35 changes: 35 additions & 0 deletions src/plugins/kbn_doc_views/public/views/table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import _ from 'lodash';
import docViewsRegistry from 'ui/registry/doc_views';

import tableHtml from './table.html';

docViewsRegistry.register(function () {
return {
title: 'Table',
order: 10,
directive: {
template: tableHtml,
scope: {
hit: '=',
indexPattern: '=',
filter: '=',
columns: '='
},
controller: function ($scope) {
$scope.mapping = $scope.indexPattern.fields.byName;
$scope.flattened = $scope.indexPattern.flattenHit($scope.hit);
$scope.formatted = $scope.indexPattern.formatHit($scope.hit);
$scope.fields = _.keys($scope.flattened).sort();

$scope.toggleColumn = function (fieldName) {
_.toggleInOut($scope.columns, fieldName);
};

$scope.showArrayInObjectsWarning = function (row, field) {
var value = $scope.flattened[field];
return _.isArray(value) && typeof value[0] === 'object';
};
}
}
};
});
3 changes: 2 additions & 1 deletion src/plugins/kibana/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ module.exports = function (kibana) {
'spyModes',
'fieldFormats',
'navbarExtensions',
'settingsSections'
'settingsSections',
'docViews'
],

injectVars: function (server, options) {
Expand Down
Loading

0 comments on commit 0b122eb

Please sign in to comment.