Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add registry for doc table details #6105

Merged
merged 7 commits into from
Feb 5, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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