mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
Merge branch 'master' into state_state_state
This commit is contained in:
commit
6f1e23fe74
23 changed files with 630 additions and 299 deletions
13
K3_FAQ.md
Normal file
13
K3_FAQ.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
**Kibana 3 Migration FAQ:**
|
||||
|
||||
**Q:** Where is feature X that I loved from Kibana 3?
|
||||
**A:** It might be coming! We’ve published our immediate roadmap as tickets Check out the beta milestones on Github to see if the feature you’re missing is coming soon.
|
||||
|
||||
**Q:** Is the dashboard schema compatible?
|
||||
**A:** Unfortunately they are not compatible, in order to achieve the features we wanted it simply was not possible to keep the same schema. Aggregations work fundamentally different from facets, the new dashboard isn’t tied to rows and columns and the relationships between searches, visualizations and the dashboard are complex enough that we simply had to design something more flexible.
|
||||
|
||||
**Q:** How do I do multi-query?
|
||||
**A:** The ‘filters’ aggregations will allow you to input multiple queries and compare them visually. You can even use Elasticsearch JSON in there!
|
||||
|
||||
**Q:** What happened to templated/scripted dashboards?
|
||||
**A:** Check out the URL, the state of each app is stored there, including any filters, queries or columns. This should be a lot easier than constructing scripted dashboards. The encoding of the URL is RISON.
|
|
@ -43,6 +43,7 @@ define(function (require) {
|
|||
stop: readGridsterChangeHandler
|
||||
},
|
||||
draggable: {
|
||||
handle: '.panel-heading, .panel-title',
|
||||
stop: readGridsterChangeHandler
|
||||
}
|
||||
}).data('gridster');
|
||||
|
|
|
@ -326,9 +326,11 @@ define(function (require) {
|
|||
}
|
||||
|
||||
$scope.rows.forEach(function (hit) {
|
||||
// when we are resorting on each segment we need to rebuild the
|
||||
// counts each time
|
||||
if (sortFn && hit._formatted) return;
|
||||
// skip this work if we have already done it and we are NOT sorting.
|
||||
// ---
|
||||
// when we are sorting results, we need to redo the counts each time because the
|
||||
// "top 500" may change with each response
|
||||
if (hit._formatted && !sortFn) return;
|
||||
|
||||
// Flatten the fields
|
||||
var indexPattern = $scope.searchSource.get('index');
|
||||
|
@ -633,6 +635,7 @@ define(function (require) {
|
|||
type: 'histogram',
|
||||
vislibParams: {
|
||||
addLegend: false,
|
||||
addEvents: true,
|
||||
addBrushing: true,
|
||||
},
|
||||
listeners: {
|
||||
|
|
|
@ -1,71 +1,13 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var html = require('text!apps/discover/partials/table.html');
|
||||
var detailsHtml = require('text!apps/discover/partials/row_details.html');
|
||||
var moment = require('moment');
|
||||
|
||||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
|
||||
require('directives/truncated');
|
||||
require('directives/infinite_scroll');
|
||||
require('apps/discover/directives/table_header');
|
||||
require('apps/discover/directives/table_row');
|
||||
|
||||
var module = require('modules').get('app/discover');
|
||||
|
||||
module.directive('kbnTableHeader', function () {
|
||||
var headerHtml = require('text!apps/discover/partials/table_header.html');
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
columns: '=',
|
||||
sorting: '=',
|
||||
mapping: '=',
|
||||
timefield: '=?'
|
||||
},
|
||||
template: headerHtml,
|
||||
controller: function ($scope) {
|
||||
$scope.headerClass = function (column) {
|
||||
if (!$scope.mapping) return;
|
||||
if ($scope.mapping[column] && !$scope.mapping[column].indexed) return;
|
||||
|
||||
var sorting = $scope.sorting;
|
||||
var defaultClass = ['fa', 'fa-sort', 'table-header-sortchange'];
|
||||
|
||||
if (!sorting) return defaultClass;
|
||||
|
||||
if (column === sorting[0]) {
|
||||
return ['fa', sorting[1] === 'asc' ? 'fa-sort-up' : 'fa-sort-down'];
|
||||
} else {
|
||||
return defaultClass;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.moveLeft = function (column) {
|
||||
var index = _.indexOf($scope.columns, column);
|
||||
if (index === 0) return;
|
||||
|
||||
_.move($scope.columns, index, --index);
|
||||
};
|
||||
|
||||
$scope.moveRight = function (column) {
|
||||
var index = _.indexOf($scope.columns, column);
|
||||
if (index === $scope.columns.length - 1) return;
|
||||
|
||||
_.move($scope.columns, index, ++index);
|
||||
};
|
||||
|
||||
$scope.sort = function (column) {
|
||||
if ($scope.mapping[column] && !$scope.mapping[column].indexed) return;
|
||||
var sorting = $scope.sorting || [];
|
||||
$scope.sorting = [column, sorting[1] === 'asc' ? 'desc' : 'asc'];
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* kbnTable directive
|
||||
*
|
||||
|
@ -89,7 +31,7 @@ define(function (require) {
|
|||
mapping: '=',
|
||||
timefield: '=?'
|
||||
},
|
||||
link: function ($scope, element) {
|
||||
link: function ($scope, $el) {
|
||||
$scope.limit = 50;
|
||||
$scope.addRows = function () {
|
||||
if ($scope.limit < config.get('discover:sampleSize')) {
|
||||
|
@ -100,164 +42,4 @@ define(function (require) {
|
|||
};
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* kbnTableRow directive
|
||||
*
|
||||
* Display a row in the table
|
||||
* ```
|
||||
* <tr ng-repeat="row in rows" kbn-table-row="row"></tr>
|
||||
* ```
|
||||
*/
|
||||
module.directive('kbnTableRow', function ($compile, config) {
|
||||
// base class for all dom nodes
|
||||
var DOMNode = window.Node;
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
columns: '=',
|
||||
filtering: '=',
|
||||
mapping: '=',
|
||||
timefield: '=?',
|
||||
row: '=kbnTableRow'
|
||||
},
|
||||
link: function ($scope, element, attrs) {
|
||||
element.after('<tr>');
|
||||
|
||||
var init = function () {
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
};
|
||||
|
||||
// track a list of id's that are currently open, so that
|
||||
// render can easily render in the same current state
|
||||
var opened = [];
|
||||
|
||||
// whenever we compile, we should create a child scope that we can then detroy
|
||||
var $child;
|
||||
|
||||
// toggle display of the rows details, a full list of the fields from each row
|
||||
$scope.toggleRow = function () {
|
||||
var row = $scope.row;
|
||||
var id = row._id;
|
||||
|
||||
$scope.open = !$scope.open;
|
||||
|
||||
var $tr = element;
|
||||
var $detailsTr = $tr.next();
|
||||
|
||||
///
|
||||
// add/remove $details children
|
||||
///
|
||||
|
||||
$detailsTr.toggle($scope.open);
|
||||
|
||||
if (!$scope.open) {
|
||||
// close the child scope if it exists
|
||||
$child.$destroy();
|
||||
// no need to go any further
|
||||
return;
|
||||
} else {
|
||||
$child = $scope.$new();
|
||||
}
|
||||
|
||||
// The fields to loop over
|
||||
row._fields = row._fields || _.keys(row._source).concat(config.get('metaFields')).sort();
|
||||
row._mode = 'table';
|
||||
|
||||
// empty the details and rebuild it
|
||||
$detailsTr
|
||||
.empty()
|
||||
.append(
|
||||
$('<td>').attr('colspan', $scope.columns.length + 2).append(detailsHtml)
|
||||
);
|
||||
|
||||
var showFilters = function (mapping) {
|
||||
var validTypes = ['string', 'number', 'date', 'ip'];
|
||||
if (!mapping.indexed) return false;
|
||||
return _.contains(validTypes, mapping.type);
|
||||
};
|
||||
|
||||
$child.row = row;
|
||||
$child.showFilters = showFilters;
|
||||
|
||||
$compile($detailsTr)($child);
|
||||
};
|
||||
|
||||
$scope.filter = function (row, field, operation) {
|
||||
$scope.filtering(field, row._source[field] || row[field], operation);
|
||||
};
|
||||
|
||||
$scope.$watchCollection('columns', function (columns) {
|
||||
element.empty();
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
});
|
||||
|
||||
$scope.$watch('timefield', function (timefield) {
|
||||
element.empty();
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
});
|
||||
|
||||
// create a tr element that lists the value for each *column*
|
||||
function createSummaryRow(row, id) {
|
||||
|
||||
var expandTd = $('<td>')
|
||||
.append(
|
||||
$('<i class="fa"></span>')
|
||||
.attr('ng-class', '{"fa-caret-right": !open, "fa-caret-down": open}')
|
||||
)
|
||||
.attr('ng-click', 'toggleRow()');
|
||||
$compile(expandTd)($scope);
|
||||
element.append(expandTd);
|
||||
|
||||
var td = $(document.createElement('td'));
|
||||
if ($scope.timefield) {
|
||||
td.addClass('discover-table-timefield');
|
||||
td.attr('width', '1%');
|
||||
_displayField(td, row, $scope.timefield);
|
||||
element.append(td);
|
||||
}
|
||||
|
||||
_.each($scope.columns, function (column) {
|
||||
td = $(document.createElement('td'));
|
||||
_displayField(td, row, column);
|
||||
element.append(td);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill an element with the value of a field
|
||||
*/
|
||||
function _displayField(el, row, field) {
|
||||
return el.html(
|
||||
$('<div>').addClass('truncate-by-height')
|
||||
.text(_getValForField(row, field))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the value of a field from a row, serialize it to a string
|
||||
* and truncate it if necessary
|
||||
*
|
||||
* @param {object} row - the row to pull the value from
|
||||
* @param {string} field - the name of the field (dot-seperated paths are accepted)
|
||||
* @return {[type]} a string, which should be inserted as text, or an element
|
||||
*/
|
||||
function _getValForField(row, field) {
|
||||
var val;
|
||||
|
||||
// discover formats all of the values and puts them in _formatted for display
|
||||
val = row._formatted[field] || row[field];
|
||||
|
||||
// undefined and null should just be an empty string
|
||||
val = (val == null) ? '' : val;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
});
|
||||
|
|
56
src/kibana/apps/discover/directives/table_header.js
Normal file
56
src/kibana/apps/discover/directives/table_header.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var module = require('modules').get('app/discover');
|
||||
|
||||
module.directive('kbnTableHeader', function () {
|
||||
var headerHtml = require('text!apps/discover/partials/table_header.html');
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
columns: '=',
|
||||
sorting: '=',
|
||||
mapping: '=',
|
||||
timefield: '=?'
|
||||
},
|
||||
template: headerHtml,
|
||||
controller: function ($scope) {
|
||||
$scope.headerClass = function (column) {
|
||||
if (!$scope.mapping) return;
|
||||
if ($scope.mapping[column] && !$scope.mapping[column].indexed) return;
|
||||
|
||||
var sorting = $scope.sorting;
|
||||
var defaultClass = ['fa', 'fa-sort', 'table-header-sortchange'];
|
||||
|
||||
if (!sorting) return defaultClass;
|
||||
|
||||
if (column === sorting[0]) {
|
||||
return ['fa', sorting[1] === 'asc' ? 'fa-sort-up' : 'fa-sort-down'];
|
||||
} else {
|
||||
return defaultClass;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.moveLeft = function (column) {
|
||||
var index = _.indexOf($scope.columns, column);
|
||||
if (index === 0) return;
|
||||
|
||||
_.move($scope.columns, index, --index);
|
||||
};
|
||||
|
||||
$scope.moveRight = function (column) {
|
||||
var index = _.indexOf($scope.columns, column);
|
||||
if (index === $scope.columns.length - 1) return;
|
||||
|
||||
_.move($scope.columns, index, ++index);
|
||||
};
|
||||
|
||||
$scope.sort = function (column) {
|
||||
if ($scope.mapping[column] && !$scope.mapping[column].indexed) return;
|
||||
var sorting = $scope.sorting || [];
|
||||
$scope.sorting = [column, sorting[1] === 'asc' ? 'desc' : 'asc'];
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
223
src/kibana/apps/discover/directives/table_row.js
Normal file
223
src/kibana/apps/discover/directives/table_row.js
Normal file
|
@ -0,0 +1,223 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
var htmlEscape = require('utils/html_escape');
|
||||
var module = require('modules').get('app/discover');
|
||||
|
||||
/**
|
||||
* kbnTableRow directive
|
||||
*
|
||||
* Display a row in the table
|
||||
* ```
|
||||
* <tr ng-repeat="row in rows" kbn-table-row="row"></tr>
|
||||
* ```
|
||||
*/
|
||||
module.directive('kbnTableRow', function ($compile, config) {
|
||||
var openRowHtml = require('text!apps/discover/partials/table_row/open.html');
|
||||
var detailsHtml = require('text!apps/discover/partials/table_row/details.html');
|
||||
var cellTemplate = _.template(require('text!apps/discover/partials/table_row/cell.html'));
|
||||
var truncateByHeightTemplate = _.template(require('text!partials/truncate_by_height.html'));
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
columns: '=',
|
||||
filtering: '=',
|
||||
mapping: '=',
|
||||
timefield: '=?',
|
||||
row: '=kbnTableRow'
|
||||
},
|
||||
link: function ($scope, $el, attrs) {
|
||||
$el.after('<tr>');
|
||||
$el.empty();
|
||||
|
||||
var init = function () {
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
};
|
||||
|
||||
// when we compile the details, we use this $scope
|
||||
var $detailsScope;
|
||||
|
||||
// when we compile the toggle button in the summary, we use this $scope
|
||||
var $toggleScope;
|
||||
|
||||
// toggle display of the rows details, a full list of the fields from each row
|
||||
$scope.toggleRow = function () {
|
||||
var row = $scope.row;
|
||||
|
||||
$scope.open = !$scope.open;
|
||||
|
||||
var $tr = $el;
|
||||
var $detailsTr = $tr.next();
|
||||
|
||||
///
|
||||
// add/remove $details children
|
||||
///
|
||||
|
||||
$detailsTr.toggle($scope.open);
|
||||
|
||||
if (!$scope.open) {
|
||||
// close the child scope if it exists
|
||||
$detailsScope.$destroy();
|
||||
// no need to go any further
|
||||
return;
|
||||
} else {
|
||||
$detailsScope = $scope.$new();
|
||||
}
|
||||
|
||||
// The fields to loop over
|
||||
row._fields = row._fields || _.keys(row._source).concat(config.get('metaFields')).sort();
|
||||
row._mode = 'table';
|
||||
|
||||
// empty the details and rebuild it
|
||||
$detailsTr.html(detailsHtml);
|
||||
|
||||
$detailsScope.row = row;
|
||||
$detailsScope.showFilters = function (mapping) {
|
||||
var validTypes = ['string', 'number', 'date', 'ip'];
|
||||
if (!mapping.indexed) return false;
|
||||
return _.contains(validTypes, mapping.type);
|
||||
};
|
||||
|
||||
$compile($detailsTr)($detailsScope);
|
||||
};
|
||||
|
||||
$scope.filter = function (row, field, operation) {
|
||||
$scope.filtering(field, row._source[field] || row[field], operation);
|
||||
};
|
||||
|
||||
$scope.$watchCollection('columns', function () {
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
});
|
||||
|
||||
$scope.$watch('timefield', function () {
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
});
|
||||
|
||||
// create a tr element that lists the value for each *column*
|
||||
function createSummaryRow(row) {
|
||||
// We just create a string here because its faster.
|
||||
var newHtmls = [
|
||||
openRowHtml
|
||||
];
|
||||
|
||||
if ($scope.timefield) {
|
||||
newHtmls.push(cellTemplate({
|
||||
timefield: true,
|
||||
formatted: _displayField(row, $scope.timefield)
|
||||
}));
|
||||
}
|
||||
|
||||
$scope.columns.forEach(function (column) {
|
||||
newHtmls.push(cellTemplate({
|
||||
timefield: false,
|
||||
formatted: _displayField(row, column, true)
|
||||
}));
|
||||
});
|
||||
|
||||
var $cells = $el.children();
|
||||
newHtmls.forEach(function (html, i) {
|
||||
var $cell = $cells.eq(i);
|
||||
if ($cell.data('discover:html') === html) return;
|
||||
|
||||
var reuse = _.find($cells.slice(i + 1), function (cell) {
|
||||
return $.data(cell, 'discover:html') === html;
|
||||
});
|
||||
|
||||
var $target = reuse ? $(reuse).detach() : $(html);
|
||||
$target.data('discover:html', html);
|
||||
var $before = $cells.eq(i - 1);
|
||||
if ($before.size()) {
|
||||
$before.after($target);
|
||||
} else {
|
||||
$el.append($target);
|
||||
}
|
||||
|
||||
// rebuild cells since we modified the children
|
||||
$cells = $el.children();
|
||||
|
||||
if (i === 0 && !reuse) {
|
||||
$toggleScope = $scope.$new();
|
||||
$compile($target)($toggleScope);
|
||||
}
|
||||
});
|
||||
|
||||
// trim off cells that were not used rest of the cells
|
||||
$cells.filter(':gt(' + (newHtmls.length - 1) + ')').remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill an element with the value of a field
|
||||
*/
|
||||
function _displayField(row, field, breakWords) {
|
||||
var text = _getValForField(row, field);
|
||||
var minLineLength = 20;
|
||||
|
||||
|
||||
if (breakWords) {
|
||||
text = htmlEscape(text);
|
||||
var lineSize = 0;
|
||||
var newText = '';
|
||||
for (var i = 0, len = text.length; i < len; i++) {
|
||||
var chr = text.charAt(i);
|
||||
newText += chr;
|
||||
|
||||
switch (chr) {
|
||||
case ' ':
|
||||
case '&':
|
||||
case ';':
|
||||
case ':':
|
||||
case ',':
|
||||
// natural line break, reset line size
|
||||
lineSize = 0;
|
||||
break;
|
||||
default:
|
||||
lineSize++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (lineSize > minLineLength) {
|
||||
// continuous text is longer then we want,
|
||||
// so break it up with a <wbr>
|
||||
lineSize = 0;
|
||||
newText += '<wbr>';
|
||||
}
|
||||
}
|
||||
|
||||
if (text.length > minLineLength) {
|
||||
return truncateByHeightTemplate({
|
||||
body: newText
|
||||
});
|
||||
}
|
||||
|
||||
text = newText;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the value of a field from a row, serialize it to a string
|
||||
* and truncate it if necessary
|
||||
*
|
||||
* @param {object} row - the row to pull the value from
|
||||
* @param {string} field - the name of the field (dot-seperated paths are accepted)
|
||||
* @return {[type]} a string, which should be inserted as text, or an element
|
||||
*/
|
||||
function _getValForField(row, field) {
|
||||
var val;
|
||||
|
||||
// discover formats all of the values and puts them in _formatted for display
|
||||
val = row._formatted[field] || row[field];
|
||||
|
||||
// undefined and null should just be an empty string
|
||||
val = (val == null) ? '' : val;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -1,38 +0,0 @@
|
|||
<div class="discover-table-details">
|
||||
<ul class="nav nav-tabs discover-table-details-toggle">
|
||||
<li ng-class="{active: row._mode == 'table'}"><a ng-click="row._mode='table'">Table</a></li>
|
||||
<li ng-class="{active: row._mode == 'json'}"><a ng-click="row._mode='json'">JSON</a></li>
|
||||
</ul>
|
||||
|
||||
<table class="table table-condensed" ng-show="row._mode == 'table'" bindonce>
|
||||
<tbody>
|
||||
<tr ng-repeat="field in row._fields" bindonce>
|
||||
<td field-name="field"
|
||||
field-type="mapping[field].type"
|
||||
width="1%"
|
||||
class="discover-table-details-field">
|
||||
</td>
|
||||
<td width="1%" class="discover-table-details-buttons">
|
||||
<span bo-show="showFilters(mapping[field])">
|
||||
<i ng-click="filter(row, field, '+')" class="fa fa-search-plus"></i>
|
||||
<i ng-click="filter(row, field, '-')" class="fa fa-search-minus"></i>
|
||||
</span>
|
||||
<span bo-show="!showFilters(mapping[field])" 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>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<i bo-show="!mapping[field]"
|
||||
tooltip-placement="top"
|
||||
tooltip="No cached mapping for this field. Refresh your mapping from the Settings > Indices page"
|
||||
class="fa fa-warning text-color-warning ng-scope"></i>
|
||||
<span class="discover-table-details-value">{{row._formatted[field] || row[field]}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<pre ng-show="row._mode == 'json'">{{row._source | json}}</pre>
|
||||
</div>
|
3
src/kibana/apps/discover/partials/table_row/cell.html
Normal file
3
src/kibana/apps/discover/partials/table_row/cell.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<td <%= timefield ? 'class="discover-table-timefield" width="1%"' : '' %>>
|
||||
<%= formatted %>
|
||||
</td>
|
40
src/kibana/apps/discover/partials/table_row/details.html
Normal file
40
src/kibana/apps/discover/partials/table_row/details.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
<td colspan="{{ columns.length + 2 }}">
|
||||
<div class="discover-table-details">
|
||||
<ul class="nav nav-tabs discover-table-details-toggle">
|
||||
<li ng-class="{active: row._mode == 'table'}"><a ng-click="row._mode='table'">Table</a></li>
|
||||
<li ng-class="{active: row._mode == 'json'}"><a ng-click="row._mode='json'">JSON</a></li>
|
||||
</ul>
|
||||
|
||||
<table class="table table-condensed" ng-show="row._mode == 'table'" bindonce>
|
||||
<tbody>
|
||||
<tr ng-repeat="field in row._fields" bindonce>
|
||||
<td field-name="field"
|
||||
field-type="mapping[field].type"
|
||||
width="1%"
|
||||
class="discover-table-details-field">
|
||||
</td>
|
||||
<td width="1%" class="discover-table-details-buttons">
|
||||
<span bo-show="showFilters(mapping[field])">
|
||||
<i ng-click="filter(row, field, '+')" class="fa fa-search-plus"></i>
|
||||
<i ng-click="filter(row, field, '-')" class="fa fa-search-minus"></i>
|
||||
</span>
|
||||
<span bo-show="!showFilters(mapping[field])" 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>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<i bo-show="!mapping[field]"
|
||||
tooltip-placement="top"
|
||||
tooltip="No cached mapping for this field. Refresh your mapping from the Settings > Indices page"
|
||||
class="fa fa-warning text-color-warning ng-scope"></i>
|
||||
<span class="discover-table-details-value">{{row._formatted[field] || row[field]}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<pre ng-show="row._mode == 'json'">{{row._source | json}}</pre>
|
||||
</div>
|
||||
</td>
|
6
src/kibana/apps/discover/partials/table_row/open.html
Normal file
6
src/kibana/apps/discover/partials/table_row/open.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
<td ng-click="toggleRow()">
|
||||
<i
|
||||
class="fa discover-table-open-icon"
|
||||
ng-class="{ 'fa-caret-down': open, 'fa-caret-right': !open }">
|
||||
</i>
|
||||
</td>
|
|
@ -64,9 +64,16 @@
|
|||
padding-left: 0px !important;
|
||||
padding-right: 0px !important;
|
||||
|
||||
.discover-table-timefield {
|
||||
&-timefield {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&-open-icon {
|
||||
// when switching between open and closed, the toggle changes size
|
||||
// slightly which is a problem because it forces the entire table to
|
||||
// re-render which is SLOW
|
||||
width: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.discover-table-footer {
|
||||
|
|
|
@ -3,10 +3,5 @@
|
|||
<label for="visTitle">Title</label>
|
||||
<input class="form-control" input-focus type="text" name="visTitle" ng-model="conf.savedVis.title" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="visDescription">Description</label>
|
||||
<textarea class="form-control" name="visDescription" ng-model="conf.savedVis.description" placeholder=""></textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
</form>
|
|
@ -25,7 +25,7 @@ define(function (require) {
|
|||
* @return {Promise} - resolved with the current AppSource
|
||||
*/
|
||||
function getAppSource() {
|
||||
return prom || loadDefaultPattern();
|
||||
return Promise.resolve(appSource || prom || loadDefaultPattern());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -11,8 +11,6 @@ define(function (require) {
|
|||
shareYAxis: true,
|
||||
addTooltip: true,
|
||||
addLegend: true,
|
||||
addEvents: true,
|
||||
addBrushing: true
|
||||
},
|
||||
schemas: new Schemas([
|
||||
{
|
||||
|
|
|
@ -117,6 +117,7 @@ define(function (require) {
|
|||
ColumnChart.prototype.addBarEvents = function (svg, bars, brush) {
|
||||
var events = this.events;
|
||||
var dispatch = this.events._attr.dispatch;
|
||||
var addBrush = this._attr.addBrushing;
|
||||
var xScale = this.handler.xAxis.xScale;
|
||||
var startXInv;
|
||||
|
||||
|
@ -131,19 +132,21 @@ define(function (require) {
|
|||
d3.event.stopPropagation();
|
||||
})
|
||||
.on('mousedown.bar', function () {
|
||||
var bar = d3.select(this);
|
||||
var startX = d3.mouse(svg.node());
|
||||
startXInv = xScale.invert(startX[0]);
|
||||
if (addBrush) {
|
||||
var bar = d3.select(this);
|
||||
var startX = d3.mouse(svg.node());
|
||||
startXInv = xScale.invert(startX[0]);
|
||||
|
||||
// Reset the brush value
|
||||
brush.extent([startXInv, startXInv]);
|
||||
// Reset the brush value
|
||||
brush.extent([startXInv, startXInv]);
|
||||
|
||||
// Magic!
|
||||
// Need to call brush on svg to see brush when brushing
|
||||
// while on top of bars.
|
||||
// Need to call brush on bar to allow the click event to be registered
|
||||
svg.call(brush);
|
||||
bar.call(brush);
|
||||
// Magic!
|
||||
// Need to call brush on svg to see brush when brushing
|
||||
// while on top of bars.
|
||||
// Need to call brush on bar to allow the click event to be registered
|
||||
svg.call(brush);
|
||||
bar.call(brush);
|
||||
}
|
||||
})
|
||||
.on('click.bar', function (d, i) {
|
||||
dispatch.click(events.eventResponse(d, i));
|
||||
|
|
|
@ -168,12 +168,13 @@ define(function (require) {
|
|||
var clipPathBuffer = 5;
|
||||
var startX = 0;
|
||||
var startY = 0 - clipPathBuffer;
|
||||
var id = 'chart-area' + _.uniqueId();
|
||||
|
||||
// Creating clipPath
|
||||
return svg
|
||||
.attr('clip-path', 'url(#chart-area)')
|
||||
.attr('clip-path', 'url(#' + id + ')')
|
||||
.append('clipPath')
|
||||
.attr('id', 'chart-area')
|
||||
.attr('id', id)
|
||||
.append('rect')
|
||||
.attr('x', startX)
|
||||
.attr('y', startY)
|
||||
|
|
|
@ -71,7 +71,7 @@ visualize-spy {
|
|||
}
|
||||
|
||||
.visualize-spy-container {
|
||||
.flex(1, 1, auto);
|
||||
.flex(1, 0, auto);
|
||||
|
||||
.display(flex);
|
||||
.flex-direction(column);
|
||||
|
|
3
src/kibana/partials/truncate_by_height.html
Normal file
3
src/kibana/partials/truncate_by_height.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="truncate-by-height">
|
||||
<%= body %>
|
||||
</div>
|
17
src/kibana/utils/html_escape.js
Normal file
17
src/kibana/utils/html_escape.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'\'': ''',
|
||||
'"': '"',
|
||||
};
|
||||
|
||||
var regex = new RegExp('[' + _.keys(map).join('') + ']', 'g');
|
||||
return function htmlEscape(text) {
|
||||
return text.replace(regex, function (c) {
|
||||
return map[c];
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,15 +1,19 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var longString = Array(200).join('_');
|
||||
|
||||
return function (id, mapping) {
|
||||
var columns = _.keys(mapping);
|
||||
return {
|
||||
_formatted: _.zipObject(_.map(columns, function (c) { return [c, c + '_formatted_' + id + longString]; })),
|
||||
_source: _.zipObject(_.map(columns, function (c) { return [c, c + '_original_' + id + longString]; })),
|
||||
_id: id,
|
||||
_index: 'test',
|
||||
sort: [id]
|
||||
};
|
||||
var fake = {
|
||||
_formatted: _.mapValues(mapping, function (f, c) { return c + '_formatted_' + id + longString; }),
|
||||
_source: _.mapValues(mapping, function (f, c) { return c + '_original_' + id + longString; }),
|
||||
_id: id,
|
||||
_index: 'test',
|
||||
sort: [id]
|
||||
};
|
||||
|
||||
fake._formatted._source = '_source_formatted_' + id + longString;
|
||||
|
||||
return fake;
|
||||
};
|
||||
});
|
|
@ -103,6 +103,7 @@
|
|||
'specs/utils/versionmath',
|
||||
'specs/utils/routes/index',
|
||||
'specs/utils/sequencer',
|
||||
'specs/utils/html_escape',
|
||||
'specs/courier/search_source/_get_normalized_sort',
|
||||
'specs/factories/base_object',
|
||||
'specs/state_management/state',
|
||||
|
|
|
@ -195,7 +195,6 @@ define(function (require) {
|
|||
expect($scope.columns[0]).to.be('bytes');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('kbnTable', function () {
|
||||
|
@ -259,12 +258,9 @@ define(function (require) {
|
|||
expect(tr.length).to.be(100);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('kbnTableRow', function () {
|
||||
|
||||
var $elem = angular.element(
|
||||
'<tr kbn-table-row="row" ' +
|
||||
'columns="columns" ' +
|
||||
|
@ -359,11 +355,209 @@ define(function (require) {
|
|||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('row diffing', function () {
|
||||
var $row;
|
||||
var $scope;
|
||||
var $root;
|
||||
var $before;
|
||||
|
||||
beforeEach(module('kibana', 'apps/discover'));
|
||||
beforeEach(inject(function ($rootScope, $compile) {
|
||||
$root = $rootScope;
|
||||
$root.row = getFakeRow(0, mapping);
|
||||
$root.columns = ['_source'];
|
||||
$root.sorting = [];
|
||||
$root.filtering = sinon.spy();
|
||||
$root.maxLength = 50;
|
||||
$root.mapping = mapping;
|
||||
$root.timefield = 'timestamp';
|
||||
|
||||
$row = $('<tr>')
|
||||
.attr({
|
||||
'kbn-table-row': 'row',
|
||||
'columns': 'columns',
|
||||
'sorting': 'sortin',
|
||||
'filtering': 'filtering',
|
||||
'mapping': 'mapping',
|
||||
'timefield': 'timefield',
|
||||
});
|
||||
|
||||
$scope = $root.$new();
|
||||
$compile($row)($scope);
|
||||
$root.$apply();
|
||||
|
||||
$before = $row.find('td');
|
||||
expect($before).to.have.length(3);
|
||||
expect($before.eq(0).text().trim()).to.be('');
|
||||
expect($before.eq(1).text().trim()).to.match(/^timestamp_formatted/);
|
||||
expect($before.eq(2).text().trim()).to.match(/^_source_formatted/);
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
$row.remove();
|
||||
});
|
||||
|
||||
it('handles a new column', function () {
|
||||
$root.columns.push('bytes');
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(4);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
expect($after[2]).to.be($before[2]);
|
||||
expect($after.eq(3).text().trim()).to.match(/^bytes_formatted/);
|
||||
});
|
||||
|
||||
it('handles two new columns at once', function () {
|
||||
$root.columns.push('bytes');
|
||||
$root.columns.push('request');
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(5);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
expect($after[2]).to.be($before[2]);
|
||||
expect($after.eq(3).text().trim()).to.match(/^bytes_formatted/);
|
||||
expect($after.eq(4).text().trim()).to.match(/^request_formatted/);
|
||||
});
|
||||
|
||||
it('handles three new columns in odd places', function () {
|
||||
$root.columns = [
|
||||
'timestamp',
|
||||
'bytes',
|
||||
'_source',
|
||||
'request'
|
||||
];
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(6);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
expect($after.eq(2).text().trim()).to.match(/^timestamp_formatted/);
|
||||
expect($after.eq(3).text().trim()).to.match(/^bytes_formatted/);
|
||||
expect($after[4]).to.be($before[2]);
|
||||
expect($after.eq(5).text().trim()).to.match(/^request_formatted/);
|
||||
});
|
||||
|
||||
|
||||
it('handles a removed column', function () {
|
||||
_.pull($root.columns, '_source');
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(2);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
});
|
||||
|
||||
it('handles two removed columns', function () {
|
||||
// first add a column
|
||||
$root.columns.push('timestamp');
|
||||
$root.$apply();
|
||||
|
||||
var $mid = $row.find('td');
|
||||
expect($mid).to.have.length(4);
|
||||
|
||||
$root.columns.pop();
|
||||
$root.columns.pop();
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(2);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
});
|
||||
|
||||
it('handles three removed random columns', function () {
|
||||
// first add two column
|
||||
$root.columns.push('timestamp', 'bytes');
|
||||
$root.$apply();
|
||||
|
||||
var $mid = $row.find('td');
|
||||
expect($mid).to.have.length(5);
|
||||
|
||||
$root.columns[0] = false; // _source
|
||||
$root.columns[2] = false; // bytes
|
||||
$root.columns = $root.columns.filter(Boolean);
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(3);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
expect($after.eq(2).text().trim()).to.match(/^timestamp_formatted/);
|
||||
});
|
||||
|
||||
it('handles two columns with the same content', function () {
|
||||
$root.row._formatted.bytes = $root.row._formatted._source;
|
||||
$root.columns.push('bytes');
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(4);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
expect($after[2]).to.be($before[2]);
|
||||
expect($after.eq(3).text().trim()).to.match(/^_source_formatted/);
|
||||
});
|
||||
|
||||
it('handles two columns swapping position', function () {
|
||||
$root.columns.push('bytes');
|
||||
$root.$apply();
|
||||
|
||||
var $mid = $row.find('td');
|
||||
expect($mid).to.have.length(4);
|
||||
|
||||
$root.columns.reverse();
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(4);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
expect($after[2]).to.be($mid[3]);
|
||||
expect($after[3]).to.be($mid[2]);
|
||||
});
|
||||
|
||||
it('handles four columns all reversing position', function () {
|
||||
$root.columns.push('bytes', 'response', 'timestamp');
|
||||
$root.$apply();
|
||||
|
||||
var $mid = $row.find('td');
|
||||
expect($mid).to.have.length(6);
|
||||
|
||||
$root.columns.reverse();
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(6);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
expect($after[2]).to.be($mid[5]);
|
||||
expect($after[3]).to.be($mid[4]);
|
||||
expect($after[4]).to.be($mid[3]);
|
||||
expect($after[5]).to.be($mid[2]);
|
||||
});
|
||||
|
||||
it('handles multiple columns with the same name', function () {
|
||||
$root.columns.push('bytes', 'bytes', 'bytes');
|
||||
$root.$apply();
|
||||
|
||||
var $after = $row.find('td');
|
||||
expect($after).to.have.length(6);
|
||||
expect($after[0]).to.be($before[0]);
|
||||
expect($after[1]).to.be($before[1]);
|
||||
expect($after[2]).to.be($before[2]);
|
||||
expect($after.eq(3).text().trim()).to.match(/^bytes_formatted/);
|
||||
expect($after.eq(4).text().trim()).to.match(/^bytes_formatted/);
|
||||
expect($after.eq(5).text().trim()).to.match(/^bytes_formatted/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
19
test/unit/specs/utils/html_escape.js
Normal file
19
test/unit/specs/utils/html_escape.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
define(function (require) {
|
||||
describe('HTML Escape Util', function () {
|
||||
var htmlEscape = require('utils/html_escape');
|
||||
|
||||
it('removes tags by replacing their angle-brackets', function () {
|
||||
expect(htmlEscape('<h1>header</h1>')).to.eql('<h1>header</h1>');
|
||||
});
|
||||
|
||||
it('removes attributes from tags using " and '', function () {
|
||||
expect(htmlEscape('<h1 onclick="alert(\'hi\');">header</h1>'))
|
||||
.to.eql('<h1 onclick="alert('hi');">header</h1>');
|
||||
});
|
||||
|
||||
it('escapes existing html entities by escaping their leading &', function () {
|
||||
expect(htmlEscape('<h1>header</h1>'))
|
||||
.to.eql('&lt;h1&gt;header&lt;/h1&gt;');
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue