mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Merge branch 'master' into fix/desaturate-map-tiles
This commit is contained in:
commit
a8d6e0c651
43 changed files with 1341 additions and 353 deletions
56
src/kibana/components/agg_types/controls/_percent_list.js
Normal file
56
src/kibana/components/agg_types/controls/_percent_list.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var INVALID = {}; // invalid flag
|
||||
var FLOATABLE = /^[\d\.e\-\+]+$/i;
|
||||
|
||||
require('modules')
|
||||
.get('kibana')
|
||||
.directive('percentList', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function ($scope, $el, attrs, ngModelCntr) {
|
||||
function parse(viewValue) {
|
||||
if (!_.isString(viewValue)) return INVALID;
|
||||
|
||||
var nums = _(viewValue.split(','))
|
||||
.invoke('trim')
|
||||
.filter(Boolean)
|
||||
.map(function (num) {
|
||||
// prevent '100 boats' from passing
|
||||
return FLOATABLE.test(num) ? parseFloat(num) : NaN;
|
||||
});
|
||||
|
||||
var ration = nums.none(_.isNaN);
|
||||
var ord = ration && nums.isOrdinal();
|
||||
var range = ord && nums.min() >= 0 && nums.max() <= 100;
|
||||
|
||||
return range ? nums.value() : INVALID;
|
||||
}
|
||||
|
||||
function makeString(list) {
|
||||
if (!_.isArray(list)) return INVALID;
|
||||
return list.join(', ');
|
||||
}
|
||||
|
||||
function converter(/* fns... */) {
|
||||
var fns = _.toArray(arguments);
|
||||
return function (input) {
|
||||
var value = input;
|
||||
var valid = fns.every(function (fn) {
|
||||
return (value = fn(value)) !== INVALID;
|
||||
});
|
||||
|
||||
ngModelCntr.$setValidity('listInput', valid);
|
||||
return valid ? value : void 0;
|
||||
};
|
||||
}
|
||||
|
||||
ngModelCntr.$parsers.push(converter(parse));
|
||||
ngModelCntr.$formatters.push(converter(makeString));
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
<th width="1%"></th>
|
||||
<th ng-if="timefield">
|
||||
<span ng-click="sort(timefield)" tooltip="Sort by time">Time <i ng-class="headerClass(timefield)"></i></span>
|
||||
<th ng-if="indexPattern.timeFieldName">
|
||||
<span ng-click="sort(indexPattern.timeFieldName)" tooltip="Sort by time">Time <i ng-class="headerClass(indexPattern.timeFieldName)"></i></span>
|
||||
</th>
|
||||
<th ng-repeat="name in columns">
|
||||
<span ng-click="sort(name)" class="table-header-name" tooltip="Sort by {{name | shortDots}}">
|
|
@ -5,17 +5,17 @@ define(function (require) {
|
|||
require('filters/short_dots');
|
||||
|
||||
module.directive('kbnTableHeader', function () {
|
||||
var headerHtml = require('text!plugins/discover/partials/table_header.html');
|
||||
var headerHtml = require('text!components/doc_table/components/table_header.html');
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
columns: '=',
|
||||
sorting: '=',
|
||||
indexPattern: '=',
|
||||
timefield: '=?'
|
||||
},
|
||||
template: headerHtml,
|
||||
controller: function ($scope) {
|
||||
|
||||
var sortableField = function (field) {
|
||||
return $scope.indexPattern.fields.byName[field].sortable;
|
||||
};
|
|
@ -21,12 +21,12 @@ define(function (require) {
|
|||
* <tr ng-repeat="row in rows" kbn-table-row="row"></tr>
|
||||
* ```
|
||||
*/
|
||||
module.directive('kbnTableRow', function ($compile, config, highlightFilter, shortDotsFilter) {
|
||||
var openRowHtml = require('text!plugins/discover/partials/table_row/open.html');
|
||||
var detailsHtml = require('text!plugins/discover/partials/table_row/details.html');
|
||||
var cellTemplate = _.template(require('text!plugins/discover/partials/table_row/cell.html'));
|
||||
module.directive('kbnTableRow', function ($compile, config, highlightFilter, shortDotsFilter, courier) {
|
||||
var openRowHtml = require('text!components/doc_table/components/table_row/open.html');
|
||||
var detailsHtml = require('text!components/doc_table/components/table_row/details.html');
|
||||
var cellTemplate = _.template(require('text!components/doc_table/components/table_row/cell.html'));
|
||||
var truncateByHeightTemplate = _.template(require('text!partials/truncate_by_height.html'));
|
||||
var sourceTemplate = _.template(noWhiteSpace(require('text!plugins/discover/partials/table_row/_source.html')));
|
||||
var sourceTemplate = _.template(noWhiteSpace(require('text!components/doc_table/components/table_row/_source.html')));
|
||||
|
||||
return {
|
||||
restrict: 'A',
|
||||
|
@ -34,7 +34,6 @@ define(function (require) {
|
|||
columns: '=',
|
||||
filter: '=',
|
||||
indexPattern: '=',
|
||||
timefield: '=?',
|
||||
row: '=kbnTableRow'
|
||||
},
|
||||
link: function ($scope, $el, attrs) {
|
||||
|
@ -42,6 +41,7 @@ define(function (require) {
|
|||
$el.empty();
|
||||
|
||||
var init = function () {
|
||||
_formatRow($scope.row);
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
};
|
||||
|
||||
|
@ -84,7 +84,7 @@ define(function (require) {
|
|||
createSummaryRow($scope.row, $scope.row._id);
|
||||
});
|
||||
|
||||
$scope.$watchMulti(['timefield', 'row.highlight'], function () {
|
||||
$scope.$watchMulti(['indexPattern.timeFieldName', 'row.highlight'], function () {
|
||||
createSummaryRow($scope.row, $scope.row._id);
|
||||
});
|
||||
|
||||
|
@ -95,10 +95,10 @@ define(function (require) {
|
|||
openRowHtml
|
||||
];
|
||||
|
||||
if ($scope.timefield) {
|
||||
if ($scope.indexPattern.timeFieldName) {
|
||||
newHtmls.push(cellTemplate({
|
||||
timefield: true,
|
||||
formatted: _displayField(row, $scope.timefield)
|
||||
formatted: _displayField(row, $scope.indexPattern.timeFieldName)
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -187,8 +187,8 @@ define(function (require) {
|
|||
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];
|
||||
// discover formats all of the values and puts them in $$_formatted for display
|
||||
val = (row.$$_formatted || _formatRow(row))[field];
|
||||
|
||||
// undefined and null should just be an empty string
|
||||
val = (val == null) ? '' : val;
|
||||
|
@ -196,6 +196,26 @@ define(function (require) {
|
|||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Format a field with the index pattern on scope.
|
||||
*/
|
||||
function _formatField(value, name) {
|
||||
var defaultFormat = courier.indexPatterns.fieldFormats.defaultByType.string;
|
||||
var field = $scope.indexPattern.fields.byName[name];
|
||||
var formatter = (field && field.format) ? field.format : defaultFormat;
|
||||
|
||||
return formatter.convert(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create the $$_formatted key on a row
|
||||
*/
|
||||
function _formatRow(row) {
|
||||
row.$$_flattened = row.$$_flattened || $scope.indexPattern.flattenHit(row);
|
||||
row.$$_formatted = row.$$_formatted || _.mapValues(row.$$_flattened, _formatField);
|
||||
return row.$$_formatted;
|
||||
}
|
||||
|
||||
init();
|
||||
}
|
||||
};
|
36
src/kibana/components/doc_table/doc_table.html
Normal file
36
src/kibana/components/doc_table/doc_table.html
Normal file
|
@ -0,0 +1,36 @@
|
|||
<paginate ng-if="!infiniteScroll" list="hits" per-page="50" top-controls="true">
|
||||
<table class="kbn-table table" ng-if="indexPattern">
|
||||
<thead
|
||||
kbn-table-header
|
||||
columns="persist.columns"
|
||||
index-pattern="indexPattern"
|
||||
sorting="persist.sorting">
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="row in page|limitTo:limit track by row._index+row._id"
|
||||
kbn-table-row="row"
|
||||
columns="persist.columns"
|
||||
sorting="persist.sorting"
|
||||
index-pattern="indexPattern"
|
||||
class="discover-table-row"></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</paginate>
|
||||
|
||||
<table ng-if="infiniteScroll" class="kbn-table table" ng-if="indexPattern">
|
||||
<thead
|
||||
kbn-table-header
|
||||
columns="persist.columns"
|
||||
index-pattern="indexPattern"
|
||||
sorting="persist.sorting">
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="row in hits|limitTo:limit track by row._index+row._id"
|
||||
kbn-table-row="row"
|
||||
columns="persist.columns"
|
||||
sorting="persist.sorting"
|
||||
index-pattern="indexPattern"
|
||||
class="discover-table-row"></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<kbn-infinite-scroll ng-if="infiniteScroll" more="addRows"></kbn-infinite-scroll>
|
94
src/kibana/components/doc_table/doc_table.js
Normal file
94
src/kibana/components/doc_table/doc_table.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var html = require('text!components/doc_table/doc_table.html');
|
||||
var getSort = require('components/doc_table/lib/get_sort');
|
||||
|
||||
require('css!components/doc_table/doc_table.css');
|
||||
require('directives/truncated');
|
||||
require('components/doc_table/components/table_header');
|
||||
require('components/doc_table/components/table_row');
|
||||
|
||||
require('modules').get('kibana')
|
||||
.directive('docTable', function (config, Notifier) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: html,
|
||||
scope: {
|
||||
searchSource: '=',
|
||||
sorting: '=',
|
||||
columns: '=',
|
||||
infiniteScroll: '=?',
|
||||
filter: '=?',
|
||||
},
|
||||
link: function ($scope) {
|
||||
var notify = new Notifier();
|
||||
|
||||
$scope.persist = {
|
||||
sorting: $scope.sorting,
|
||||
columns: $scope.columns
|
||||
};
|
||||
|
||||
var prereq = (function () {
|
||||
var fns = [];
|
||||
|
||||
return function register(fn) {
|
||||
fns.push(fn);
|
||||
|
||||
return function () {
|
||||
fn.apply(this, arguments);
|
||||
|
||||
if (fns.length) {
|
||||
_.pull(fns, fn);
|
||||
if (!fns.length) {
|
||||
$scope.$root.$broadcast('ready:vis');
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}());
|
||||
|
||||
$scope.addRows = function () {
|
||||
$scope.limit += 50;
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', function () {
|
||||
if ($scope.searchSource) $scope.searchSource.destroy();
|
||||
});
|
||||
|
||||
$scope.$watch('searchSource', prereq(function (searchSource) {
|
||||
if (!$scope.searchSource) return;
|
||||
|
||||
$scope.indexPattern = $scope.searchSource.get('index');
|
||||
|
||||
$scope.searchSource.size(config.get('discover:sampleSize'));
|
||||
$scope.searchSource.sort(getSort($scope.sorting, $scope.indexPattern));
|
||||
|
||||
// Set the watcher after initialization
|
||||
$scope.$watch('persist.sorting', function (newSort, oldSort) {
|
||||
// Don't react if sort values didn't really change
|
||||
if (newSort === oldSort) return;
|
||||
$scope.searchSource.sort(getSort(newSort, $scope.indexPattern));
|
||||
$scope.searchSource.fetchQueued();
|
||||
});
|
||||
|
||||
// TODO: we need to have some way to clean up result requests
|
||||
$scope.searchSource.onResults().then(function onResults(resp) {
|
||||
// Reset infinite scroll limit
|
||||
$scope.limit = 50;
|
||||
|
||||
// Abort if something changed
|
||||
if ($scope.searchSource !== $scope.searchSource) return;
|
||||
|
||||
$scope.hits = resp.hits.hits;
|
||||
|
||||
return $scope.searchSource.onResults().then(onResults);
|
||||
}).catch(notify.fatal);
|
||||
|
||||
$scope.searchSource.onError(notify.error).catch(notify.fatal);
|
||||
}));
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
9
src/kibana/components/doc_table/doc_table.less
Normal file
9
src/kibana/components/doc_table/doc_table.less
Normal file
|
@ -0,0 +1,9 @@
|
|||
@import (reference) "../../styles/_bootstrap.less";
|
||||
@import (reference) "../../styles/theme/_theme.less";
|
||||
@import (reference) "lesshat.less";
|
||||
|
||||
doc-table {
|
||||
overflow: auto;
|
||||
margin: 5px;
|
||||
.flex(1, 1, 100%);
|
||||
}
|
22
src/kibana/components/doc_table/lib/get_sort.js
Normal file
22
src/kibana/components/doc_table/lib/get_sort.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Take a sorting array and make it into an object
|
||||
* @param {array} 2 item array [fieldToSort, directionToSort]
|
||||
* @param {object} indexPattern used for determining default sort
|
||||
* @returns {object} a sort object suitable for returning to elasticsearch
|
||||
*/
|
||||
return function (sort, indexPattern) {
|
||||
var sortObj = {};
|
||||
if (_.isArray(sort) && sort.length === 2) {
|
||||
// At some point we need to refact the sorting logic, this array sucks.
|
||||
sortObj[sort[0]] = sort[1];
|
||||
} else if (indexPattern.timeFieldName) {
|
||||
sortObj[indexPattern.timeFieldName] = 'desc';
|
||||
} else {
|
||||
sortObj._score = 'desc';
|
||||
}
|
||||
return sortObj;
|
||||
};
|
||||
});
|
|
@ -209,10 +209,8 @@ define(function (require) {
|
|||
* than the required number of data points
|
||||
* @param {String} message - the message to provide with the error
|
||||
*/
|
||||
errors.NotEnoughData = function NotEnoughData() {
|
||||
KbnError.call(this,
|
||||
'There are not enough data points to render this chart',
|
||||
errors.NotEnoughData);
|
||||
errors.NotEnoughData = function NotEnoughData(message) {
|
||||
KbnError.call(this, message, errors.NotEnoughData);
|
||||
};
|
||||
inherits(errors.NotEnoughData, KbnError);
|
||||
|
||||
|
|
|
@ -265,13 +265,15 @@ define(function (require) {
|
|||
|
||||
AreaChart.prototype.checkIfEnoughData = function () {
|
||||
var series = this.chartData.series;
|
||||
var message = 'Area charts require more than one data point. Try adding ' +
|
||||
'an X-Axis Aggregation';
|
||||
|
||||
var notEnoughData = series.some(function (obj) {
|
||||
return obj.values.length < 2;
|
||||
});
|
||||
|
||||
if (notEnoughData) {
|
||||
throw new errors.NotEnoughData();
|
||||
throw new errors.NotEnoughData(message);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@ define(function (require) {
|
|||
color: self.darkerColor(defaultColor),
|
||||
weight: 1.0,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.7
|
||||
fillOpacity: 0.75
|
||||
};
|
||||
}
|
||||
}).addTo(map);
|
||||
|
@ -247,7 +247,7 @@ define(function (require) {
|
|||
color: self.darkerColor(color),
|
||||
weight: 1.0,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.7
|
||||
fillOpacity: 0.75
|
||||
};
|
||||
}
|
||||
}).addTo(map);
|
||||
|
@ -303,14 +303,29 @@ define(function (require) {
|
|||
var div = L.DomUtil.create('div', 'tilemap-legend');
|
||||
var colors = self._attr.colors;
|
||||
var labels = [];
|
||||
for (var i = 0; i < colors.length; i++) {
|
||||
var vals = self._attr.cScale.invertExtent(colors[i]);
|
||||
var strokecol = self.darkerColor(colors[i]);
|
||||
var i = 0;
|
||||
var vals;
|
||||
var strokecol;
|
||||
|
||||
if (data.properties.min === data.properties.max) {
|
||||
// 1 val for legend
|
||||
vals = self._attr.cScale.invertExtent(colors[i]);
|
||||
strokecol = self.darkerColor(colors[i]);
|
||||
labels.push(
|
||||
'<i style="background:' + colors[i] + ';border-color:' + strokecol + '"></i> ' +
|
||||
vals[0].toFixed(1) + ' – ' + vals[1].toFixed(1));
|
||||
vals[0].toFixed(0));
|
||||
} else {
|
||||
// 3 to 5 vals for legend
|
||||
for (i = 0; i < colors.length; i++) {
|
||||
vals = self._attr.cScale.invertExtent(colors[i]);
|
||||
strokecol = self.darkerColor(colors[i]);
|
||||
labels.push(
|
||||
'<i style="background:' + colors[i] + ';border-color:' + strokecol + '"></i> ' +
|
||||
vals[0].toFixed(0) + ' – ' + vals[1].toFixed(0));
|
||||
}
|
||||
}
|
||||
div.innerHTML = labels.join('<br>');
|
||||
|
||||
return div;
|
||||
};
|
||||
legend.addTo(map);
|
||||
|
@ -407,7 +422,7 @@ define(function (require) {
|
|||
};
|
||||
|
||||
/**
|
||||
* radiusScale returns a circle radius from
|
||||
* radiusScale returns a a number for scaled circle markers
|
||||
* approx. square root of count
|
||||
* which is multiplied by a factor based on the geohash precision
|
||||
* for relative sizing of markers
|
||||
|
@ -425,40 +440,40 @@ define(function (require) {
|
|||
var maxr;
|
||||
switch (precision) {
|
||||
case 1:
|
||||
maxr = 200;
|
||||
maxr = 150;
|
||||
break;
|
||||
case 2:
|
||||
maxr = 30;
|
||||
maxr = 28;
|
||||
break;
|
||||
case 3:
|
||||
maxr = 9;
|
||||
maxr = 8;
|
||||
break;
|
||||
case 4:
|
||||
maxr = 3;
|
||||
maxr = 2;
|
||||
break;
|
||||
case 5:
|
||||
maxr = 1.44;
|
||||
maxr = 1.4;
|
||||
break;
|
||||
case 6:
|
||||
maxr = 1.12;
|
||||
break;
|
||||
case 7:
|
||||
maxr = 0.6;
|
||||
break;
|
||||
case 7:
|
||||
maxr = 0.4;
|
||||
break;
|
||||
case 8:
|
||||
maxr = 0.3;
|
||||
maxr = 0.2;
|
||||
break;
|
||||
case 9:
|
||||
maxr = 0.22;
|
||||
maxr = 0.12;
|
||||
break;
|
||||
default:
|
||||
maxr = 9;
|
||||
maxr = 8;
|
||||
}
|
||||
return Math.pow(count, exp) / Math.pow(max, exp) * maxr;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns a number to scale circle markers
|
||||
* returns a number to scale shaded circle markers
|
||||
* based on the geohash precision
|
||||
*
|
||||
* @method quantRadiusScale
|
||||
|
@ -469,34 +484,34 @@ define(function (require) {
|
|||
var maxr;
|
||||
switch (precision) {
|
||||
case 1:
|
||||
maxr = 100;
|
||||
maxr = 150;
|
||||
break;
|
||||
case 2:
|
||||
maxr = 12;
|
||||
maxr = 18;
|
||||
break;
|
||||
case 3:
|
||||
maxr = 3;
|
||||
maxr = 4.5;
|
||||
break;
|
||||
case 4:
|
||||
maxr = 0.6;
|
||||
maxr = 0.7;
|
||||
break;
|
||||
case 5:
|
||||
maxr = 0.3;
|
||||
maxr = 0.26;
|
||||
break;
|
||||
case 6:
|
||||
maxr = 0.22;
|
||||
maxr = 0.20;
|
||||
break;
|
||||
case 7:
|
||||
maxr = 0.18;
|
||||
break;
|
||||
case 8:
|
||||
maxr = 0.16;
|
||||
break;
|
||||
case 8:
|
||||
maxr = 0.13;
|
||||
break;
|
||||
case 9:
|
||||
maxr = 0.14;
|
||||
maxr = 0.11;
|
||||
break;
|
||||
default:
|
||||
maxr = 3;
|
||||
maxr = 4.5;
|
||||
}
|
||||
return maxr;
|
||||
};
|
||||
|
@ -513,14 +528,20 @@ define(function (require) {
|
|||
*/
|
||||
TileMap.prototype.quantizeColorScale = function (count, min, max) {
|
||||
var self = this;
|
||||
var greens = ['#c7e9b4', '#7fcdbb', '#41b6c4', '#2c7fb8', '#253494'];
|
||||
var reds = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026'];
|
||||
var blues = ['#9ecae1', '#6baed6', '#4292c6', '#2171b5', '#084594'];
|
||||
var colors = self._attr.colors = reds;
|
||||
var reds5 = ['#fed976', '#feb24c', '#fd8d3c', '#f03b20', '#bd0026'];
|
||||
var reds3 = ['#fecc5c', '#fd8d3c', '#e31a1c'];
|
||||
var reds1 = ['#ff6128'];
|
||||
var colors = self._attr.colors = reds5;
|
||||
|
||||
if (max - min < 3) {
|
||||
colors = self._attr.colors = reds1;
|
||||
} else if (max - min < 25) {
|
||||
colors = self._attr.colors = reds3;
|
||||
}
|
||||
|
||||
var cScale = self._attr.cScale = d3.scale.quantize()
|
||||
.domain([min, max])
|
||||
.range(colors);
|
||||
|
||||
if (max === min) {
|
||||
return colors[0];
|
||||
} else {
|
||||
|
@ -553,4 +574,4 @@ define(function (require) {
|
|||
return TileMap;
|
||||
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,12 +9,18 @@ define(function (require) {
|
|||
restrict: 'E',
|
||||
scope: true,
|
||||
link: {
|
||||
pre: function ($scope, $el) {
|
||||
if ($el.find('paginate-controls').size() === 0) {
|
||||
$el.append($compile('<paginate-controls>')($scope));
|
||||
pre: function ($scope, $el, attrs) {
|
||||
if (_.isUndefined(attrs.bottomControls)) attrs.bottomControls = true;
|
||||
if ($el.find('paginate-controls.paginate-bottom').size() === 0 && attrs.bottomControls) {
|
||||
$el.append($compile('<paginate-controls class="paginate-bottom">')($scope));
|
||||
}
|
||||
},
|
||||
post: function ($scope, $el, attrs) {
|
||||
if (_.isUndefined(attrs.topControls)) attrs.topControls = false;
|
||||
if ($el.find('paginate-controls.paginate-top').size() === 0 && attrs.topControls) {
|
||||
$el.prepend($compile('<paginate-controls class="paginate-top">')($scope));
|
||||
}
|
||||
|
||||
var paginate = $scope.paginate;
|
||||
|
||||
// add some getters to the controller powered by attributes
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
return function loadPanelFunction(Private) { // Inject services here
|
||||
return function (panel, $scope) { // Function parameters here
|
||||
var panelTypes = {
|
||||
visualization: Private(require('plugins/dashboard/components/panel/lib/visualization')),
|
||||
search: Private(require('plugins/dashboard/components/panel/lib/search'))
|
||||
};
|
||||
|
||||
try {
|
||||
return panelTypes[panel.type](panel, $scope);
|
||||
} catch (e) {
|
||||
throw new Error('Loader not found for unknown panel type: ' + panel.type);
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
});
|
14
src/kibana/plugins/dashboard/components/panel/lib/search.js
Normal file
14
src/kibana/plugins/dashboard/components/panel/lib/search.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
define(function (require) {
|
||||
return function searchLoader(savedSearches, Private) { // Inject services here
|
||||
return function (panel, $scope) { // Function parameters here
|
||||
return savedSearches.get(panel.id)
|
||||
.then(function (savedSearch) {
|
||||
return {
|
||||
savedObj: savedSearch,
|
||||
panel: panel,
|
||||
edit: '#discover'
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
define(function (require) {
|
||||
return function visualizationLoader(savedVisualizations, Private) { // Inject services here
|
||||
var brushEvent = Private(require('utils/brush_event'));
|
||||
var filterBarClickHandler = Private(require('components/filter_bar/filter_bar_click_handler'));
|
||||
|
||||
return function (panel, $scope) { // Function parameters here
|
||||
return savedVisualizations.get(panel.id)
|
||||
.then(function (savedVis) {
|
||||
// $scope.state comes via $scope inheritence from the dashboard app. Don't love this.
|
||||
savedVis.vis.listeners.click = filterBarClickHandler($scope.state);
|
||||
savedVis.vis.listeners.brush = brushEvent;
|
||||
|
||||
return {
|
||||
savedObj: savedVis,
|
||||
panel: panel,
|
||||
edit: '#visualize/edit'
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
});
|
28
src/kibana/plugins/dashboard/components/panel/panel.html
Normal file
28
src/kibana/plugins/dashboard/components/panel/panel.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
<div class="panel panel-default" ng-switch on="panel.type" ng-if="savedObj || error">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">{{savedObj.title}}</span>
|
||||
<div class="btn-group">
|
||||
<a ng-show="!appEmbedded" ng-href="{{edit}}/{{panel.id | uriescape}}"><i class="fa fa-pencil"></i></a>
|
||||
<a ng-show="!appEmbedded" ng-click="remove()"><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div ng-if="error" class="load-error">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<span ng-bind="error"></span>
|
||||
</div>
|
||||
|
||||
<visualize ng-switch-when="visualization"
|
||||
vis="savedObj.vis"
|
||||
search-source="savedObj.searchSource"
|
||||
class="panel-content">
|
||||
</visualize>
|
||||
|
||||
<doc-table ng-switch-when="search"
|
||||
search-source="savedObj.searchSource"
|
||||
sorting="savedObj.sort"
|
||||
columns="savedObj.columns"
|
||||
class="panel-content">
|
||||
</doc-table>
|
||||
</div>
|
46
src/kibana/plugins/dashboard/components/panel/panel.js
Normal file
46
src/kibana/plugins/dashboard/components/panel/panel.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
define(function (require) {
|
||||
var moment = require('moment');
|
||||
var $ = require('jquery');
|
||||
require('modules')
|
||||
.get('app/dashboard')
|
||||
.directive('dashboardPanel', function (savedVisualizations, savedSearches, Notifier, Private, $compile) {
|
||||
var _ = require('lodash');
|
||||
var filterBarClickHandler = Private(require('components/filter_bar/filter_bar_click_handler'));
|
||||
var loadPanel = Private(require('plugins/dashboard/components/panel/lib/load_panel'));
|
||||
var notify = new Notifier();
|
||||
|
||||
require('components/visualize/visualize');
|
||||
require('components/doc_table/doc_table');
|
||||
|
||||
var brushEvent = Private(require('utils/brush_event'));
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: require('text!plugins/dashboard/components/panel/panel.html'),
|
||||
requires: '^dashboardGrid',
|
||||
link: function ($scope, $el) {
|
||||
// using $scope inheritance, panels are available in AppState
|
||||
var $state = $scope.state;
|
||||
|
||||
// receives $scope.panel from the dashboard grid directive, seems like should be isolate?
|
||||
$scope.$watch('id', function (id) {
|
||||
if (!$scope.panel.id || !$scope.panel.type) return;
|
||||
|
||||
loadPanel($scope.panel, $scope).then(function (panelConfig) {
|
||||
// These could be done in loadPanel, putting them here to make them more explicit
|
||||
$scope.savedObj = panelConfig.savedObj;
|
||||
$scope.edit = panelConfig.edit;
|
||||
$scope.$on('$destroy', panelConfig.savedObj.destroy);
|
||||
}).catch(function (e) {
|
||||
$scope.error = e.message;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$scope.remove = function () {
|
||||
_.pull($state.panels, $scope.panel);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -138,7 +138,16 @@ define(function (require) {
|
|||
});
|
||||
|
||||
// ignore panels that don't have vis id's
|
||||
if (!panel.visId) throw new Error('missing visId on panel');
|
||||
if (!panel.id) {
|
||||
// In the interest of backwards compat
|
||||
if (panel.visId) {
|
||||
panel.id = panel.visId;
|
||||
panel.type = 'visualization';
|
||||
delete panel.visId;
|
||||
} else {
|
||||
throw new Error('missing object id on panel');
|
||||
}
|
||||
}
|
||||
|
||||
panel.$scope = $scope.$new();
|
||||
panel.$scope.panel = panel;
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
define(function (require) {
|
||||
var moment = require('moment');
|
||||
require('modules')
|
||||
.get('app/dashboard')
|
||||
.directive('dashboardPanel', function (savedVisualizations, Notifier, Private) {
|
||||
var _ = require('lodash');
|
||||
var filterBarClickHandler = Private(require('components/filter_bar/filter_bar_click_handler'));
|
||||
|
||||
var notify = new Notifier();
|
||||
|
||||
require('components/visualize/visualize');
|
||||
var brushEvent = Private(require('utils/brush_event'));
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
template: require('text!plugins/dashboard/partials/panel.html'),
|
||||
requires: '^dashboardGrid',
|
||||
link: function ($scope, $el) {
|
||||
// using $scope inheritance, panels are available in AppState
|
||||
var $state = $scope.state;
|
||||
|
||||
// receives panel object from the dashboard grid directive
|
||||
$scope.$watch('visId', function (visId) {
|
||||
delete $scope.vis;
|
||||
if (!$scope.panel.visId) return;
|
||||
|
||||
savedVisualizations.get($scope.panel.visId)
|
||||
.then(function (savedVis) {
|
||||
$scope.savedVis = savedVis;
|
||||
$scope.$on('$destroy', savedVis.destroy);
|
||||
savedVis.vis.listeners.click = filterBarClickHandler($state);
|
||||
savedVis.vis.listeners.brush = brushEvent;
|
||||
})
|
||||
.catch(function (e) {
|
||||
$scope.error = e.message;
|
||||
});
|
||||
});
|
||||
|
||||
$scope.remove = function () {
|
||||
_.pull($state.panels, $scope.panel);
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -13,7 +13,7 @@ define(function (require) {
|
|||
|
||||
|
||||
require('plugins/dashboard/directives/grid');
|
||||
require('plugins/dashboard/directives/panel');
|
||||
require('plugins/dashboard/components/panel/panel');
|
||||
require('plugins/dashboard/services/saved_dashboards');
|
||||
require('css!plugins/dashboard/styles/main.css');
|
||||
|
||||
|
@ -47,7 +47,7 @@ define(function (require) {
|
|||
}
|
||||
});
|
||||
|
||||
app.directive('dashboardApp', function (Notifier, courier, savedVisualizations, AppState, timefilter, kbnUrl) {
|
||||
app.directive('dashboardApp', function (Notifier, courier, AppState, timefilter, kbnUrl) {
|
||||
return {
|
||||
controller: function ($scope, $route, $routeParams, $location, configFile, Private) {
|
||||
var notify = new Notifier({
|
||||
|
@ -169,7 +169,12 @@ define(function (require) {
|
|||
// called by the saved-object-finder when a user clicks a vis
|
||||
$scope.addVis = function (hit) {
|
||||
pendingVis++;
|
||||
$state.panels.push({ visId: hit.id });
|
||||
$state.panels.push({ id: hit.id, type: 'visualization' });
|
||||
};
|
||||
|
||||
$scope.addSearch = function (hit) {
|
||||
pendingVis++;
|
||||
$state.panels.push({ id: hit.id, type: 'search' });
|
||||
};
|
||||
|
||||
// Setup configurable values for config directive, after objects are initialized
|
||||
|
@ -177,6 +182,7 @@ define(function (require) {
|
|||
dashboard: dash,
|
||||
save: $scope.save,
|
||||
addVis: $scope.addVis,
|
||||
addSearch: $scope.addSearch,
|
||||
shareData: function () {
|
||||
return {
|
||||
link: $location.absUrl(),
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">{{savedVis.title}}</span>
|
||||
<div class="btn-group">
|
||||
<a ng-show="!appEmbedded" ng-href="#visualize/edit/{{panel.visId | uriescape}}"><i class="fa fa-pencil"></i></a>
|
||||
<a ng-show="!appEmbedded" ng-click="remove()"><i class="fa fa-times"></i></a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div ng-if="error" class="load-error">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<span ng-bind="error"></span>
|
||||
</div>
|
||||
<visualize ng-if="savedVis" vis="savedVis.vis" search-source="savedVis.searchSource"></visualize>
|
||||
</div>
|
|
@ -1 +1,14 @@
|
|||
<saved-object-finder title="Choose a visualization" type="visualizations" on-choose="opts.addVis"></saved-object-finder>
|
||||
<div ng-switch on="mode" ng-init="mode = 'visualization'" class="dashboard-panel-picker">
|
||||
<ul class="nav nav-tabs">
|
||||
<li ng-class="{active: mode == 'visualization'}"><a ng-click="mode='visualization'">Visualizations</a></li>
|
||||
<li ng-class="{active: mode == 'search'}"><a ng-click="mode='search'">Searches</a></li>
|
||||
</ul>
|
||||
|
||||
<li class="list-group-item" ng-switch-when="visualization">
|
||||
<saved-object-finder title="Choose a visualization" type="visualizations" on-choose="opts.addVis"></saved-object-finder>
|
||||
</li>
|
||||
|
||||
<li class="list-group-item" ng-switch-when="search">
|
||||
<saved-object-finder title="Choose a search" type="searches" on-choose="opts.addSearch"></saved-object-finder>
|
||||
</li>
|
||||
</div>
|
|
@ -101,7 +101,8 @@ dashboard-grid {
|
|||
}
|
||||
}
|
||||
|
||||
visualize {
|
||||
.panel-content {
|
||||
display: flex;
|
||||
.flex(1, 1, 100%);
|
||||
height: auto;
|
||||
}
|
||||
|
@ -109,6 +110,10 @@ dashboard-grid {
|
|||
}
|
||||
}
|
||||
|
||||
.dashboard-panel-picker > li.list-group-item {
|
||||
border-top: 0px;
|
||||
}
|
||||
|
||||
.dashboard-load {
|
||||
margin: 10px;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ define(function (require) {
|
|||
|
||||
require('directives/truncated');
|
||||
require('directives/infinite_scroll');
|
||||
require('plugins/discover/directives/table_header');
|
||||
require('plugins/discover/directives/table_row');
|
||||
require('components/doc_table/components/table_header');
|
||||
require('components/doc_table/components/table_row');
|
||||
|
||||
var module = require('modules').get('app/discover');
|
||||
|
||||
|
@ -22,14 +22,12 @@ define(function (require) {
|
|||
restrict: 'E',
|
||||
template: html,
|
||||
scope: {
|
||||
fields: '=',
|
||||
columns: '=',
|
||||
rows: '=',
|
||||
sorting: '=',
|
||||
filtering: '=',
|
||||
refresh: '=',
|
||||
indexPattern: '=',
|
||||
timefield: '=?'
|
||||
},
|
||||
link: function ($scope, $el) {
|
||||
$scope.limit = 50;
|
||||
|
|
|
@ -160,7 +160,6 @@
|
|||
sorting="state.sort"
|
||||
filtering="filterQuery"
|
||||
refresh="fetch"
|
||||
timefield="opts.timefield"
|
||||
index-pattern="indexPattern">
|
||||
</kbn-table>
|
||||
|
||||
|
|
|
@ -4,14 +4,12 @@
|
|||
columns="columns"
|
||||
index-pattern="indexPattern"
|
||||
sorting="sorting"
|
||||
timefield="timefield">
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="row in rows |limitTo:limit track by row._index+row._id"
|
||||
kbn-table-row="row"
|
||||
columns="columns"
|
||||
sorting="sorting"
|
||||
timefield="timefield"
|
||||
filter="filtering"
|
||||
index-pattern="indexPattern"
|
||||
class="discover-table-row"></tr>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
kbn-table,tbody[kbn-rows] {
|
||||
kbn-table, .kbn-table, tbody[kbn-rows] {
|
||||
// sub tables should not have a leading border
|
||||
.table .table {
|
||||
margin-bottom: 0px;
|
||||
|
|
|
@ -264,7 +264,7 @@ notifications {
|
|||
|
||||
//== Table
|
||||
|
||||
kbn-table {
|
||||
kbn-table, .kbn-table {
|
||||
font-size: @font-size-small;
|
||||
|
||||
th {
|
||||
|
|
|
@ -11,198 +11,8 @@ define(function (require) {
|
|||
*/
|
||||
var _ = require('lodash_src');
|
||||
_.mixin(require('lodash-deep'));
|
||||
_.mixin({
|
||||
inherits: function (Sub, Super) {
|
||||
Sub.prototype = _.create(Super.prototype, { 'constructor': Sub });
|
||||
Sub.Super = Super;
|
||||
},
|
||||
remove: function (array, index) {
|
||||
array.splice(index, 1);
|
||||
return array;
|
||||
},
|
||||
// If variable is value, then return alt. If variable is anything else, return value;
|
||||
toggle: function (variable, value, alt) {
|
||||
return variable === value ? alt : value;
|
||||
},
|
||||
toggleInOut: function (array, value) {
|
||||
if (_.contains(array, value)) {
|
||||
array = _.without(array, value);
|
||||
} else {
|
||||
array.push(value);
|
||||
}
|
||||
return array;
|
||||
},
|
||||
// NOTE: The flatten behavior here works if you don't need to keep a reference to the
|
||||
// original value
|
||||
flattenWith: function (dot, nestedObj, flattenArrays) {
|
||||
var key; // original key
|
||||
var stack = []; // track key stack
|
||||
var flatObj = {};
|
||||
(function flattenObj(obj) {
|
||||
_.keys(obj).forEach(function (key) {
|
||||
stack.push(key);
|
||||
if (!flattenArrays && _.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key];
|
||||
else if (_.isObject(obj[key])) flattenObj(obj[key]);
|
||||
else flatObj[stack.join(dot)] = obj[key];
|
||||
stack.pop();
|
||||
});
|
||||
}(nestedObj));
|
||||
return flatObj;
|
||||
},
|
||||
// assign the properties of an object's subObject to the parent object.
|
||||
// obj = { prop: { a: 1} } ===> obj = { a: 1 }
|
||||
unwrapProp: function (obj, prop) {
|
||||
var wrapped = obj[prop];
|
||||
delete obj[prop];
|
||||
_.assign(obj, wrapped);
|
||||
},
|
||||
optMemoize: function (fn) {
|
||||
var memo = _.memoize(fn);
|
||||
return function () {
|
||||
if (arguments[0] == null) {
|
||||
return fn.apply(this, arguments);
|
||||
} else {
|
||||
return memo.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
},
|
||||
isNumeric: function (v) {
|
||||
return !_.isNaN(v) && (typeof v === 'number' || (!_.isArray(v) && !_.isNaN(parseInt(v, 10))));
|
||||
},
|
||||
setValue: function (obj, name, value) {
|
||||
var path = name.split('.');
|
||||
var current = obj;
|
||||
|
||||
(function recurse() {
|
||||
var step = path.shift();
|
||||
|
||||
if (path.length === 0) {
|
||||
current[step] = value;
|
||||
}
|
||||
|
||||
if (_.isObject(current)) {
|
||||
current = current[step];
|
||||
recurse();
|
||||
}
|
||||
}());
|
||||
},
|
||||
// limit the number of arguments that are passed to the function
|
||||
limit: function (context, fn, count) {
|
||||
// syntax without context limit(fn, 1)
|
||||
if (count == null && _.isNumeric(fn)) {
|
||||
count = fn;
|
||||
fn = context;
|
||||
context = null;
|
||||
}
|
||||
|
||||
count = count || 0;
|
||||
|
||||
// shortcuts for common paths
|
||||
// !!!! PLEASE don't use more than two arg
|
||||
if (count === 0) return function () { return fn.call(context); };
|
||||
if (count === 1) return function (a) { return fn.call(context, a); };
|
||||
if (count === 2) return function (a, b) { return fn.call(context, a, b); };
|
||||
|
||||
// catch all version
|
||||
return function () {
|
||||
return fn.apply(context, [].slice.call(arguments, 0, count));
|
||||
};
|
||||
},
|
||||
// call all functions in an array
|
||||
callEach: function (arr) {
|
||||
_.invoke(arr, 'call');
|
||||
},
|
||||
onceWithCb: function (fn) {
|
||||
var callbacks = [];
|
||||
|
||||
// on initial flush, call the init function, but ensure
|
||||
// that it only happens once
|
||||
var flush = _.once(function (cntx, args) {
|
||||
args.push(function finishedOnce() {
|
||||
// override flush to simply schedule an asynchronous clear
|
||||
flush = function () {
|
||||
setTimeout(function () {
|
||||
_.callEach(callbacks.splice(0));
|
||||
}, 0);
|
||||
};
|
||||
|
||||
flush();
|
||||
});
|
||||
|
||||
fn.apply(cntx, args);
|
||||
});
|
||||
|
||||
return function runOnceWithCb() {
|
||||
var args = [].slice.call(arguments, 0);
|
||||
var cb = args[args.length - 1];
|
||||
|
||||
if (typeof cb === 'function') {
|
||||
callbacks.push(cb);
|
||||
// trim the arg list so the other callback can
|
||||
// be pushed if needed
|
||||
args = args.slice(0, -1);
|
||||
}
|
||||
|
||||
// always call flush, it might not do anything
|
||||
flush(this, args);
|
||||
};
|
||||
},
|
||||
chunk: function (arr, count) {
|
||||
var size = Math.ceil(arr.length / count);
|
||||
var chunks = new Array(count);
|
||||
for (var i = 0; i < count; i ++) {
|
||||
var start = i * size;
|
||||
chunks[i] = arr.slice(start, start + size);
|
||||
}
|
||||
return chunks;
|
||||
},
|
||||
repeat: function (string, times) {
|
||||
var out = '';
|
||||
for (var i = 0; i < times; i++) out += string;
|
||||
return out;
|
||||
},
|
||||
/**
|
||||
* move an obj either up or down in the collection by
|
||||
* injecting it either before/after the prev/next obj that
|
||||
* satisfied the qualifier
|
||||
*
|
||||
* or, just from one index to another...
|
||||
*
|
||||
* @param {array} objs - the list to move the object within
|
||||
* @param {number|any} obj - the object that should be moved, or the index that the object is currently at
|
||||
* @param {number|boolean} below - the index to move the object to, or whether it should be moved up or down
|
||||
* @param {function} qualifier - a lodash-y callback, object = _.where, string = _.pluck
|
||||
* @return {array} - the objs argument
|
||||
*/
|
||||
move: function (objs, obj, below, qualifier) {
|
||||
var origI = _.isNumber(obj) ? obj : objs.indexOf(obj);
|
||||
if (origI === -1) return objs;
|
||||
|
||||
if (_.isNumber(below)) {
|
||||
// move to a specific index
|
||||
objs.splice(below, 0, objs.splice(origI, 1)[0]);
|
||||
return objs;
|
||||
}
|
||||
|
||||
below = !!below;
|
||||
qualifier = _.createCallback(qualifier, null, 2);
|
||||
|
||||
var above = !below;
|
||||
var finder = below ? _.findIndex : _.findLastIndex;
|
||||
|
||||
// find the index of the next/previous obj that meets the qualifications
|
||||
var targetI = finder(objs, function (otherAgg, otherI) {
|
||||
if (below && otherI <= origI) return;
|
||||
if (above && otherI >= origI) return;
|
||||
return !!qualifier(otherAgg, otherI);
|
||||
});
|
||||
|
||||
if (targetI === -1) return objs;
|
||||
|
||||
// place the obj at it's new index
|
||||
objs.splice(targetI, 0, objs.splice(origI, 1)[0]);
|
||||
}
|
||||
});
|
||||
_.mixin(require('utils/_mixins_chainable'), { chain: true });
|
||||
_.mixin(require('utils/_mixins_notchainable'), { chain: false });
|
||||
|
||||
return _;
|
||||
});
|
||||
|
|
181
src/kibana/utils/_mixins_chainable.js
Normal file
181
src/kibana/utils/_mixins_chainable.js
Normal file
|
@ -0,0 +1,181 @@
|
|||
define(function (require) {
|
||||
/**
|
||||
* THESE ARE AUTOMATICALLY INCLUDED IN LODASH
|
||||
*
|
||||
* use:
|
||||
* var _ = require('lodash');
|
||||
*
|
||||
* require.js config points the 'lodash' id to
|
||||
* this module, which provides a modified version
|
||||
* of lodash.
|
||||
*/
|
||||
var _ = require('lodash_src');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Remove an element at a specific index from an array
|
||||
*
|
||||
* @param {array} arr
|
||||
* @param {number} index
|
||||
* @return {array} arr
|
||||
*/
|
||||
remove: function (arr, index) {
|
||||
arr.splice(index, 1);
|
||||
return arr;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove or add a value to an array based on it's presense in the
|
||||
* array initially.
|
||||
*
|
||||
* @param {array} arr
|
||||
* @param {any} value - the value to toggle
|
||||
* @return {array} arr
|
||||
*/
|
||||
toggleInOut: function (arr, value) {
|
||||
if (_.contains(arr, value)) {
|
||||
arr = _.without(arr, value);
|
||||
} else {
|
||||
arr.push(value);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Flatten an object into a single-level object.
|
||||
* NOTE: The flatten behavior here works if you don't need to keep a reference to the original value
|
||||
*
|
||||
* set flattenArrays to traverse into arrays and create properties like:
|
||||
* {
|
||||
* 'users.0.name': 'username1',
|
||||
* 'users.1.name': 'username2',
|
||||
* 'users.2.name': 'username3',
|
||||
* }
|
||||
*
|
||||
* @param {string} dot - the seperator for keys, '.' is generally preferred
|
||||
* @param {object} nestedObj - the object to flatten
|
||||
* @param {Boolean} flattenArrays - should arrays be travered or left alone?
|
||||
* @return {object}
|
||||
*/
|
||||
flattenWith: function (dot, nestedObj, flattenArrays) {
|
||||
var key; // original key
|
||||
var stack = []; // track key stack
|
||||
var flatObj = {};
|
||||
(function flattenObj(obj) {
|
||||
_.keys(obj).forEach(function (key) {
|
||||
stack.push(key);
|
||||
if (!flattenArrays && _.isArray(obj[key])) flatObj[stack.join(dot)] = obj[key];
|
||||
else if (_.isObject(obj[key])) flattenObj(obj[key]);
|
||||
else flatObj[stack.join(dot)] = obj[key];
|
||||
stack.pop();
|
||||
});
|
||||
}(nestedObj));
|
||||
return flatObj;
|
||||
},
|
||||
|
||||
/**
|
||||
* assign the properties of an object's subObject to the parent object.
|
||||
*
|
||||
* var obj = { prop: { a: 1} };
|
||||
* _.unwrapProp(obj, 'prop'); // { a: 1 };
|
||||
*
|
||||
* @param {[type]} obj [description]
|
||||
* @param {[type]} prop [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
unwrapProp: function (obj, prop) {
|
||||
var wrapped = obj[prop];
|
||||
delete obj[prop];
|
||||
_.assign(obj, wrapped);
|
||||
return obj;
|
||||
},
|
||||
|
||||
/**
|
||||
* Memoize a function, but only use the memoized version if
|
||||
* a first argument is passed, otherwise execute the original method
|
||||
* every time.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
*/
|
||||
optMemoize: function (fn) {
|
||||
var memo = _.memoize(fn);
|
||||
return function () {
|
||||
if (arguments[0] == null) {
|
||||
return fn.apply(this, arguments);
|
||||
} else {
|
||||
return memo.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Alias to _.deepSet
|
||||
*/
|
||||
setValue: function (obj, path, value) {
|
||||
_.deepSet(obj, path, value);
|
||||
return obj;
|
||||
},
|
||||
|
||||
/**
|
||||
* Split an array into a series of smaller arrays, containing
|
||||
* portions of the previous array.
|
||||
*
|
||||
* @param {array} arr - the array to chunk
|
||||
* @param {number} count - the number of chunks to create
|
||||
* @return {array[array]} - array of slice of arr
|
||||
*/
|
||||
chunk: function (arr, count) {
|
||||
var size = Math.ceil(arr.length / count);
|
||||
var chunks = new Array(count);
|
||||
for (var i = 0; i < count; i ++) {
|
||||
var start = i * size;
|
||||
chunks[i] = arr.slice(start, start + size);
|
||||
}
|
||||
return chunks;
|
||||
},
|
||||
|
||||
/**
|
||||
* move an obj either up or down in the collection by
|
||||
* injecting it either before/after the prev/next obj that
|
||||
* satisfied the qualifier
|
||||
*
|
||||
* or, just from one index to another...
|
||||
*
|
||||
* @param {array} objs - the list to move the object within
|
||||
* @param {number|any} obj - the object that should be moved, or the index that the object is currently at
|
||||
* @param {number|boolean} below - the index to move the object to, or whether it should be moved up or down
|
||||
* @param {function} qualifier - a lodash-y callback, object = _.where, string = _.pluck
|
||||
* @return {array} - the objs argument
|
||||
*/
|
||||
move: function (objs, obj, below, qualifier) {
|
||||
var origI = _.isNumber(obj) ? obj : objs.indexOf(obj);
|
||||
if (origI === -1) return objs;
|
||||
|
||||
if (_.isNumber(below)) {
|
||||
// move to a specific index
|
||||
objs.splice(below, 0, objs.splice(origI, 1)[0]);
|
||||
return objs;
|
||||
}
|
||||
|
||||
below = !!below;
|
||||
qualifier = _.createCallback(qualifier, null, 2);
|
||||
|
||||
var above = !below;
|
||||
var finder = below ? _.findIndex : _.findLastIndex;
|
||||
|
||||
// find the index of the next/previous obj that meets the qualifications
|
||||
var targetI = finder(objs, function (otherAgg, otherI) {
|
||||
if (below && otherI <= origI) return;
|
||||
if (above && otherI >= origI) return;
|
||||
return !!qualifier(otherAgg, otherI);
|
||||
});
|
||||
|
||||
if (targetI === -1) return objs;
|
||||
|
||||
// place the obj at it's new index
|
||||
objs.splice(targetI, 0, objs.splice(origI, 1)[0]);
|
||||
}
|
||||
};
|
||||
});
|
185
src/kibana/utils/_mixins_notchainable.js
Normal file
185
src/kibana/utils/_mixins_notchainable.js
Normal file
|
@ -0,0 +1,185 @@
|
|||
define(function (require) {
|
||||
/**
|
||||
* THESE ARE AUTOMATICALLY INCLUDED IN LODASH
|
||||
*
|
||||
* use:
|
||||
* var _ = require('lodash');
|
||||
*
|
||||
* require.js config points the 'lodash' id to
|
||||
* this module, which provides a modified version
|
||||
* of lodash.
|
||||
*/
|
||||
var _ = require('lodash_src');
|
||||
|
||||
return {
|
||||
/**
|
||||
* Setup Class-like inheritance between two constructors.
|
||||
* Exposes the Super class at SubClass.Super;
|
||||
*
|
||||
* @param {Constructor} Sub - The "Class" that should be extended
|
||||
* @param {Constructor} Super - The parent "Class"
|
||||
* @return {Constructor} - the sub argument;
|
||||
*/
|
||||
inherits: function (Sub, Super) {
|
||||
Sub.prototype = _.create(Super.prototype, { 'constructor': Sub });
|
||||
Sub.Super = Super;
|
||||
return Sub;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a string by repeating another string n-times
|
||||
*
|
||||
* @param {string} str - the string to repeat
|
||||
* @param {number} times - the number of times to repeat the string
|
||||
* @return {string}
|
||||
*/
|
||||
repeat: function (str, times) {
|
||||
var out = '';
|
||||
for (var i = 0; i < times; i++) out += str;
|
||||
return out;
|
||||
},
|
||||
|
||||
/**
|
||||
* If current is value (===), then return alt. If current is anything else, return value
|
||||
*
|
||||
* @param {any} current
|
||||
* @param {any} value
|
||||
* @param {any} alt
|
||||
* @return {any} alt|value
|
||||
*/
|
||||
toggle: function (current, value, alt) {
|
||||
return current === value ? alt : value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Inverse of _.some(), reads better in some cases,
|
||||
*
|
||||
* @param {array} arr
|
||||
* @param {Function} fn
|
||||
* @param {any} cntx
|
||||
* @return {Boolean}
|
||||
*/
|
||||
none: function (arr, fn, cntx) {
|
||||
return !_.some(arr, fn, cntx);
|
||||
},
|
||||
|
||||
/**
|
||||
* check if the values in an array are all numbers, unique, and
|
||||
* @param {[type]} arr [description]
|
||||
* @return {Boolean} [description]
|
||||
*/
|
||||
isOrdinal: function (arr) {
|
||||
if (!_.isArray(arr)) return false;
|
||||
|
||||
return _.all(arr, function (num, i, arr) {
|
||||
if (!_.isNumber(num)) return false;
|
||||
if (i === 0) return true;
|
||||
return num > arr[i - 1];
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks to see if an input value is number-like, this
|
||||
* includes strings that parse into valid numbers and objects
|
||||
* that don't have a type of number but still parse properly
|
||||
* via-some sort of valueOf magic
|
||||
*
|
||||
* @param {any} v - the value to check
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isNumeric: function (v) {
|
||||
return !_.isNaN(v) && (typeof v === 'number' || (!_.isArray(v) && !_.isNaN(parseFloat(v))));
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a method that wraps another method which expects a callback as it's last
|
||||
* argument. The wrapper method will call the wrapped function only once (the first
|
||||
* time it is called), but will always call the callbacks passed to it. This has a
|
||||
* similar effect to calling a promise-returning function that is wrapped with _.once
|
||||
* but can be used outside of angular.
|
||||
*
|
||||
* @param {Function} fn - the function that should only be executed once and accepts
|
||||
* a callback as it's last arg
|
||||
* @return {Function} - the wrapper method
|
||||
*/
|
||||
onceWithCb: function (fn) {
|
||||
var callbacks = [];
|
||||
|
||||
// on initial flush, call the init function, but ensure
|
||||
// that it only happens once
|
||||
var flush = _.once(function (cntx, args) {
|
||||
args.push(function finishedOnce() {
|
||||
// override flush to simply schedule an asynchronous clear
|
||||
flush = function () {
|
||||
setTimeout(function () {
|
||||
_.callEach(callbacks.splice(0));
|
||||
}, 0);
|
||||
};
|
||||
|
||||
flush();
|
||||
});
|
||||
|
||||
fn.apply(cntx, args);
|
||||
});
|
||||
|
||||
return function runOnceWithCb() {
|
||||
var args = [].slice.call(arguments, 0);
|
||||
var cb = args[args.length - 1];
|
||||
|
||||
if (typeof cb === 'function') {
|
||||
callbacks.push(cb);
|
||||
// trim the arg list so the other callback can
|
||||
// be pushed if needed
|
||||
args = args.slice(0, -1);
|
||||
}
|
||||
|
||||
// always call flush, it might not do anything
|
||||
flush(this, args);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a function that will ignore all but n-number of arguments.
|
||||
* This is useful for passing functions like _.parseInt to Array#map.
|
||||
* Since _.parseInt accepts a second argument, it will try to use the
|
||||
* index of the value passed as the base for that number and weird
|
||||
* errors occur.
|
||||
*
|
||||
* @param {this} [context] - this value for fn, optional
|
||||
* @param {Function} fn - the function to wrap
|
||||
* @param {number} count - the number of args to accept
|
||||
* @return {Function}
|
||||
*/
|
||||
limit: function (context, fn, count) {
|
||||
// syntax without context limit(fn, 1)
|
||||
if (count == null && _.isNumeric(fn)) {
|
||||
count = fn;
|
||||
fn = context;
|
||||
context = null;
|
||||
}
|
||||
|
||||
count = count || 0;
|
||||
|
||||
// shortcuts for common paths
|
||||
// !!!! PLEASE don't use more than two arg
|
||||
if (count === 0) return function () { return fn.call(context); };
|
||||
if (count === 1) return function (a) { return fn.call(context, a); };
|
||||
if (count === 2) return function (a, b) { return fn.call(context, a, b); };
|
||||
|
||||
// catch all version
|
||||
return function () {
|
||||
return fn.apply(context, [].slice.call(arguments, 0, count));
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Call all of the function in an array
|
||||
*
|
||||
* @param {array[functions]} arr
|
||||
* @return {undefined}
|
||||
*/
|
||||
callEach: function (arr) {
|
||||
_.invoke(arr, 'call');
|
||||
}
|
||||
};
|
||||
});
|
|
@ -4,6 +4,7 @@ define(function (require) {
|
|||
{ name: 'bytes', type: 'number', indexed: true, analyzed: true, count: 10 },
|
||||
{ name: 'ssl', type: 'boolean', indexed: true, analyzed: true, count: 20 },
|
||||
{ name: '@timestamp', type: 'date', indexed: true, analyzed: true, count: 30 },
|
||||
{ name: 'time', type: 'date', indexed: true, analyzed: true, count: 30 },
|
||||
{ name: 'utc_time', type: 'date', indexed: true, analyzed: true },
|
||||
{ name: 'phpmemory', type: 'number', indexed: true, analyzed: true },
|
||||
{ name: 'ip', type: 'ip', indexed: true, analyzed: true },
|
||||
|
|
18
test/unit/fixtures/search_response.js
Normal file
18
test/unit/fixtures/search_response.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
define(function (require) {
|
||||
var hits = require('fixtures/real_hits');
|
||||
|
||||
return {
|
||||
took: 73,
|
||||
timed_out: false,
|
||||
_shards: {
|
||||
total: 144,
|
||||
successful: 144,
|
||||
failed: 0
|
||||
},
|
||||
hits: {
|
||||
total : 49487,
|
||||
max_score : 1.0,
|
||||
hits: hits
|
||||
}
|
||||
};
|
||||
});
|
36
test/unit/fixtures/stubbed_search_source.js
Normal file
36
test/unit/fixtures/stubbed_search_source.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
define(function (require) {
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var searchResponse = require('fixtures/search_response');
|
||||
|
||||
return function stubSearchSource(Private, $q) {
|
||||
var deferedResult = $q.defer();
|
||||
|
||||
return {
|
||||
sort: sinon.spy(),
|
||||
size: sinon.spy(),
|
||||
fetch: sinon.spy(),
|
||||
destroy: sinon.spy(),
|
||||
get: function (param) {
|
||||
switch (param) {
|
||||
case 'index':
|
||||
return Private(require('fixtures/stubbed_logstash_index_pattern'));
|
||||
default:
|
||||
throw new Error('Param "' + param + '" is not implemented in the stubbed search source');
|
||||
}
|
||||
},
|
||||
crankResults: function () {
|
||||
deferedResult.resolve(searchResponse);
|
||||
deferedResult = $q.defer();
|
||||
},
|
||||
onResults: function () {
|
||||
// Up to the test to resolve this manually
|
||||
// For example:
|
||||
// someHandler.resolve(require('fixtures/search_response'))
|
||||
return deferedResult.promise;
|
||||
},
|
||||
onError: function () { return $q.defer().promise; },
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
});
|
|
@ -41,16 +41,8 @@ define(function (require) {
|
|||
// For testing column removing/adding for the header and the rows
|
||||
//
|
||||
var columnTests = function (elemType, parentElem) {
|
||||
it('should create only the toggle column by default', function (done) {
|
||||
var childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(1);
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create a time column if the timefield is defined', function (done) {
|
||||
// Should include a column for toggling and the time column by default
|
||||
$parentScope.timefield = '@timestamp';
|
||||
parentElem.scope().$digest();
|
||||
var childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(2);
|
||||
done();
|
||||
|
@ -62,22 +54,32 @@ define(function (require) {
|
|||
$parentScope.columns = ['bytes'];
|
||||
parentElem.scope().$digest();
|
||||
childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(2);
|
||||
expect($(childElems[1]).text()).to.contain('bytes');
|
||||
expect(childElems.length).to.be(3);
|
||||
expect($(childElems[2]).text()).to.contain('bytes');
|
||||
|
||||
$parentScope.columns = ['bytes', 'request_body'];
|
||||
parentElem.scope().$digest();
|
||||
childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(3);
|
||||
expect($(childElems[2]).text()).to.contain('request_body');
|
||||
expect(childElems.length).to.be(4);
|
||||
expect($(childElems[3]).text()).to.contain('request_body');
|
||||
|
||||
$parentScope.columns = ['request_body'];
|
||||
parentElem.scope().$digest();
|
||||
childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(2);
|
||||
expect($(childElems[1]).text()).to.contain('request_body');
|
||||
expect(childElems.length).to.be(3);
|
||||
expect($(childElems[2]).text()).to.contain('request_body');
|
||||
done();
|
||||
});
|
||||
|
||||
it('should create only the toggle column if there is no timeField', function (done) {
|
||||
delete parentElem.scope().indexPattern.timeFieldName;
|
||||
parentElem.scope().$digest();
|
||||
|
||||
var childElems = parentElem.find(elemType);
|
||||
expect(childElems.length).to.be(1);
|
||||
done();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -86,7 +88,7 @@ define(function (require) {
|
|||
describe('kbnTableHeader', function () {
|
||||
|
||||
var $elem = angular.element(
|
||||
'<thead kbn-table-header columns="columns" index-pattern="indexPattern" sort="sort" timefield="timefield"></thead>'
|
||||
'<thead kbn-table-header columns="columns" index-pattern="indexPattern" sort="sort"></thead>'
|
||||
);
|
||||
|
||||
beforeEach(function () {
|
||||
|
@ -206,7 +208,6 @@ define(function (require) {
|
|||
'filter="filtering"' +
|
||||
'maxLength=maxLength ' +
|
||||
'index-pattern="indexPattern"' +
|
||||
'timefield="timefield" ' +
|
||||
'></thead>'
|
||||
);
|
||||
|
||||
|
@ -227,7 +228,6 @@ define(function (require) {
|
|||
sorting: [],
|
||||
filtering: sinon.spy(),
|
||||
maxLength: 50,
|
||||
timefield: '@timestamp'
|
||||
});
|
||||
});
|
||||
afterEach(function () {
|
||||
|
@ -265,7 +265,6 @@ define(function (require) {
|
|||
'sorting="sorting"' +
|
||||
'filter="filter"' +
|
||||
'index-pattern="indexPattern"' +
|
||||
'timefield="timefield" ' +
|
||||
'></tr>'
|
||||
);
|
||||
|
||||
|
@ -335,7 +334,6 @@ define(function (require) {
|
|||
'sorting="sorting"' +
|
||||
'filtering="filtering"' +
|
||||
'index-pattern="indexPattern"' +
|
||||
'timefield="timefield" ' +
|
||||
'></tr>'
|
||||
);
|
||||
var $details;
|
||||
|
@ -378,7 +376,7 @@ define(function (require) {
|
|||
var $before;
|
||||
|
||||
beforeEach(module('kibana', 'apps/discover'));
|
||||
beforeEach(inject(function ($rootScope, $compile) {
|
||||
beforeEach(inject(function ($rootScope, $compile, Private) {
|
||||
$root = $rootScope;
|
||||
$root.row = getFakeRow(0, mapping);
|
||||
$root.columns = ['_source'];
|
||||
|
@ -386,7 +384,7 @@ define(function (require) {
|
|||
$root.filtering = sinon.spy();
|
||||
$root.maxLength = 50;
|
||||
$root.mapping = mapping;
|
||||
$root.timefield = '@timestamp';
|
||||
$root.indexPattern = Private(require('fixtures/stubbed_logstash_index_pattern'));
|
||||
|
||||
$row = $('<tr>')
|
||||
.attr({
|
||||
|
@ -395,7 +393,6 @@ define(function (require) {
|
|||
'sorting': 'sortin',
|
||||
'filtering': 'filtering',
|
||||
'index-pattern': 'indexPattern',
|
||||
'timefield': 'timefield',
|
||||
});
|
||||
|
||||
$scope = $root.$new();
|
||||
|
@ -405,7 +402,7 @@ define(function (require) {
|
|||
$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(1).text().trim()).to.match(/^time_formatted/);
|
||||
expect($before.eq(2).find('dl dt').length).to.be(_.keys($scope.row.$$_flattened).length);
|
||||
}));
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
define(function (require) {
|
||||
describe('PercentList directive', function () {
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
|
||||
require('components/agg_types/controls/_percent_list');
|
||||
|
||||
var $el;
|
||||
var $scope;
|
||||
var $compile;
|
||||
var $rootScope;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
beforeEach(inject(function ($injector) {
|
||||
$rootScope = $injector.get('$rootScope');
|
||||
$compile = $injector.get('$compile');
|
||||
|
||||
$el = $('<input>').attr('ng-model', 'val').attr('percent-list', '');
|
||||
$scope = $rootScope.$new();
|
||||
}));
|
||||
|
||||
afterEach(function () {
|
||||
$el.remove();
|
||||
$scope.$destroy();
|
||||
});
|
||||
|
||||
it('filters out empty entries', function () {
|
||||
$compile($el)($scope);
|
||||
|
||||
$el.val(',,1, ,, ,2, ,\n,');
|
||||
$el.change();
|
||||
expect($scope.val).to.eql([1, 2]);
|
||||
});
|
||||
|
||||
it('fails on invalid numbers', function () {
|
||||
$compile($el)($scope);
|
||||
|
||||
$el.val('foo,bar');
|
||||
$el.change();
|
||||
expect($scope.val).to.be(undefined);
|
||||
expect($el.hasClass('ng-invalid')).to.be(true);
|
||||
});
|
||||
|
||||
it('supports decimals', function () {
|
||||
$compile($el)($scope);
|
||||
|
||||
$el.val('1.2,000001.6,99.10');
|
||||
$el.change();
|
||||
expect($scope.val).to.eql([1.2, 1.6, 99.10]);
|
||||
});
|
||||
|
||||
it('ensures that the values are in order', function () {
|
||||
$compile($el)($scope);
|
||||
|
||||
$el.val('1, 2, 3, 10, 4, 5');
|
||||
$el.change();
|
||||
expect($scope.val).to.be(undefined);
|
||||
expect($el.hasClass('ng-invalid')).to.be(true);
|
||||
});
|
||||
|
||||
it('ensures that the values are less between 0 and 100', function () {
|
||||
$compile($el)($scope);
|
||||
|
||||
$el.val('-1, 0, 1');
|
||||
$el.change();
|
||||
expect($scope.val).to.be(undefined);
|
||||
expect($el.hasClass('ng-invalid')).to.be(true);
|
||||
|
||||
$el.val('0, 1');
|
||||
$el.change();
|
||||
expect($scope.val).to.eql([0, 1]);
|
||||
expect($el.hasClass('ng-invalid')).to.be(false);
|
||||
|
||||
$el.val('1, 101');
|
||||
$el.change();
|
||||
expect($scope.val).to.be(undefined);
|
||||
expect($el.hasClass('ng-invalid')).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
104
test/unit/specs/components/doc_table/doc_table.js
Normal file
104
test/unit/specs/components/doc_table/doc_table.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
define(function (require) {
|
||||
var angular = require('angular');
|
||||
var $ = require('jquery');
|
||||
var _ = require('lodash');
|
||||
var sinon = require('test_utils/auto_release_sinon');
|
||||
var searchResponse = require('fixtures/search_response');
|
||||
|
||||
// Load the kibana app dependencies.
|
||||
require('services/private');
|
||||
require('components/doc_table/doc_table');
|
||||
|
||||
|
||||
var $parentScope, $scope, $timeout, searchSource;
|
||||
|
||||
var init = function ($elem, props) {
|
||||
inject(function ($rootScope, $compile, _$timeout_) {
|
||||
$timeout = _$timeout_;
|
||||
$parentScope = $rootScope;
|
||||
_.assign($parentScope, props);
|
||||
|
||||
$compile($elem)($parentScope);
|
||||
|
||||
// I think the prereq requires this?
|
||||
$timeout(function () {
|
||||
$elem.scope().$digest();
|
||||
}, 0);
|
||||
|
||||
$scope = $elem.isolateScope();
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
var destroy = function () {
|
||||
$scope.$destroy();
|
||||
$parentScope.$destroy();
|
||||
};
|
||||
|
||||
describe('docTable', function () {
|
||||
var $elem;
|
||||
|
||||
beforeEach(module('kibana'));
|
||||
beforeEach(function () {
|
||||
$elem = angular.element('<doc-table search-source="searchSource" columns="columns" sorting="sorting"></doc-table>');
|
||||
inject(function (Private) {
|
||||
searchSource = Private(require('fixtures/stubbed_search_source'));
|
||||
});
|
||||
init($elem, {
|
||||
searchSource: searchSource,
|
||||
columns: [],
|
||||
sorting: ['@timestamp', 'desc']
|
||||
});
|
||||
$scope.$digest();
|
||||
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
destroy();
|
||||
});
|
||||
|
||||
it('should compile', function () {
|
||||
expect($elem.text()).to.not.be.empty();
|
||||
});
|
||||
|
||||
it('should set the indexPattern to that of the searchSource', function () {
|
||||
expect($scope.indexPattern).to.be(searchSource.get('index'));
|
||||
});
|
||||
|
||||
it('should set size and sort on the searchSource', function () {
|
||||
expect($scope.searchSource.sort.called).to.be(true);
|
||||
expect($scope.searchSource.size.called).to.be(true);
|
||||
});
|
||||
|
||||
it('should have an addRows function that increases the row cound', function () {
|
||||
expect($scope.addRows).to.be.a(Function);
|
||||
searchSource.crankResults();
|
||||
$scope.$digest();
|
||||
expect($scope.limit).to.be(50);
|
||||
$scope.addRows();
|
||||
expect($scope.limit).to.be(100);
|
||||
});
|
||||
|
||||
it('should reset the row limit when results are received', function () {
|
||||
$scope.limit = 100;
|
||||
expect($scope.limit).to.be(100);
|
||||
searchSource.crankResults();
|
||||
$scope.$digest();
|
||||
expect($scope.limit).to.be(50);
|
||||
});
|
||||
|
||||
it('should put the hits array on scope', function () {
|
||||
expect($scope.hits).to.be(undefined);
|
||||
searchSource.crankResults();
|
||||
$scope.$digest();
|
||||
expect($scope.hits).to.be.an(Array);
|
||||
});
|
||||
|
||||
it('should destroy the searchSource when the scope is destroyed', function () {
|
||||
expect(searchSource.destroy.called).to.be(false);
|
||||
$scope.$destroy();
|
||||
expect(searchSource.destroy.called).to.be(true);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
35
test/unit/specs/components/doc_table/lib/get_sort.js
Normal file
35
test/unit/specs/components/doc_table/lib/get_sort.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
define(function (require) {
|
||||
var getSort = require('components/doc_table/lib/get_sort');
|
||||
var indexPattern =
|
||||
describe('docTable', function () {
|
||||
describe('getSort function', function () {
|
||||
|
||||
var timePattern = {
|
||||
timeFieldName: 'time'
|
||||
};
|
||||
var noTimePattern = {};
|
||||
|
||||
it('should be a function', function () {
|
||||
expect(getSort).to.be.a(Function);
|
||||
});
|
||||
|
||||
it('should return an object if passed a 2 item array', function () {
|
||||
expect(getSort(['foo', 'bar'], timePattern)).to.eql({foo: 'bar'});
|
||||
expect(getSort(['foo', 'bar'], noTimePattern)).to.eql({foo: 'bar'});
|
||||
});
|
||||
|
||||
it('should sort in reverse chrono order otherwise on time based patterns', function () {
|
||||
expect(getSort([], timePattern)).to.eql({time: 'desc'});
|
||||
expect(getSort(['foo'], timePattern)).to.eql({time: 'desc'});
|
||||
expect(getSort({foo: 'bar'}, timePattern)).to.eql({time: 'desc'});
|
||||
});
|
||||
|
||||
it('should sort by score on non-time patterns', function () {
|
||||
expect(getSort([], noTimePattern)).to.eql({_score: 'desc'});
|
||||
expect(getSort(['foo'], noTimePattern)).to.eql({_score: 'desc'});
|
||||
expect(getSort({foo: 'bar'}, noTimePattern)).to.eql({_score: 'desc'});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
|
@ -11,6 +11,156 @@ define(function (require) {
|
|||
angular.module('ZeroFilledArrayUtilService', ['kibana']);
|
||||
|
||||
describe('Vislib Zero Injection Module Test Suite', function () {
|
||||
var dateHistogramRows = {
|
||||
'rows': [
|
||||
{
|
||||
'label': 'Top 5 @tags: success',
|
||||
'ordered': {
|
||||
'date': true,
|
||||
'interval': 60000,
|
||||
'min': 1418410540548,
|
||||
'max': 1418410936568
|
||||
},
|
||||
'series': [
|
||||
{
|
||||
'label': 'jpg',
|
||||
'values': [
|
||||
{ 'x': 1418410560000, 'y': 2 },
|
||||
{ 'x': 1418410620000, 'y': 4 },
|
||||
{ 'x': 1418410680000, 'y': 1 },
|
||||
{ 'x': 1418410740000, 'y': 5 },
|
||||
{ 'x': 1418410800000, 'y': 2 },
|
||||
{ 'x': 1418410860000, 'y': 3 },
|
||||
{ 'x': 1418410920000, 'y': 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'css',
|
||||
'values': [
|
||||
{ 'x': 1418410560000, 'y': 1 },
|
||||
{ 'x': 1418410620000, 'y': 3 },
|
||||
{ 'x': 1418410680000, 'y': 1 },
|
||||
{ 'x': 1418410740000, 'y': 4 },
|
||||
{ 'x': 1418410800000, 'y': 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'gif',
|
||||
'values': [
|
||||
{ 'x': 1418410500000, 'y': 1 },
|
||||
{ 'x': 1418410680000, 'y': 3 },
|
||||
{ 'x': 1418410740000, 'y': 2 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'Top 5 @tags: info',
|
||||
'ordered': {
|
||||
'date': true,
|
||||
'interval': 60000,
|
||||
'min': 1418410540548,
|
||||
'max': 1418410936568
|
||||
},
|
||||
'series': [
|
||||
{
|
||||
'label': 'jpg',
|
||||
'values': [
|
||||
{ 'x': 1418410560000, 'y': 4 },
|
||||
{ 'x': 1418410620000, 'y': 2 },
|
||||
{ 'x': 1418410680000, 'y': 1 },
|
||||
{ 'x': 1418410740000, 'y': 5 },
|
||||
{ 'x': 1418410800000, 'y': 2 },
|
||||
{ 'x': 1418410860000, 'y': 3 },
|
||||
{ 'x': 1418410920000, 'y': 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'css',
|
||||
'values': [
|
||||
{ 'x': 1418410620000, 'y': 3 },
|
||||
{ 'x': 1418410680000, 'y': 1 },
|
||||
{ 'x': 1418410740000, 'y': 4 },
|
||||
{ 'x': 1418410800000, 'y': 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'gif',
|
||||
'values': [
|
||||
{ 'x': 1418410500000, 'y': 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'Top 5 @tags: security',
|
||||
'ordered': {
|
||||
'date': true,
|
||||
'interval': 60000,
|
||||
'min': 1418410540548,
|
||||
'max': 1418410936568
|
||||
},
|
||||
'series': [
|
||||
{
|
||||
'label': 'jpg',
|
||||
'values': [
|
||||
{ 'x': 1418410560000, 'y': 1 },
|
||||
{ 'x': 1418410620000, 'y': 3 },
|
||||
{ 'x': 1418410920000, 'y': 2 }
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'gif',
|
||||
'values': [
|
||||
{ 'x': 1418410680000, 'y': 3 },
|
||||
{ 'x': 1418410740000, 'y': 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'Top 5 @tags: login',
|
||||
'ordered': {
|
||||
'date': true,
|
||||
'interval': 60000,
|
||||
'min': 1418410540548,
|
||||
'max': 1418410936568
|
||||
},
|
||||
'series': [
|
||||
{
|
||||
'label': 'jpg',
|
||||
'values': [
|
||||
{ 'x': 1418410740000, 'y': 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'css',
|
||||
'values': [
|
||||
{ 'x': 1418410560000, 'y': 1 }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'label': 'Top 5 @tags: warning',
|
||||
'ordered': {
|
||||
'date': true,
|
||||
'interval': 60000,
|
||||
'min': 1418410540548,
|
||||
'max': 1418410936568
|
||||
},
|
||||
'series': [
|
||||
{
|
||||
'label': 'jpg',
|
||||
'values': [
|
||||
{ 'x': 1418410860000, 'y': 2 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var seriesData = {
|
||||
series: [
|
||||
{
|
||||
|
@ -150,7 +300,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
it('should throw an error if property series, rows, or columns is not ' +
|
||||
'present', function () {
|
||||
'present', function () {
|
||||
|
||||
expect(function () {
|
||||
injectZeros(childrenObject);
|
||||
|
@ -158,7 +308,7 @@ define(function (require) {
|
|||
});
|
||||
|
||||
it('should not throw an error if object has property series, rows, or ' +
|
||||
'columns', function () {
|
||||
'columns', function () {
|
||||
|
||||
expect(function () {
|
||||
injectZeros(seriesObject);
|
||||
|
@ -177,7 +327,7 @@ define(function (require) {
|
|||
expect(_.isFunction(injectZeros)).to.be(true);
|
||||
});
|
||||
|
||||
it('should return an object with series[0].values"', function () {
|
||||
it('should return an object with series[0].values', function () {
|
||||
expect(_.isObject(sample1)).to.be(true);
|
||||
expect(_.isObject(sample1.series[0].values)).to.be(true);
|
||||
});
|
||||
|
@ -468,6 +618,7 @@ define(function (require) {
|
|||
zeroFillArray = Private(require('components/vislib/components/zero_injection/zero_fill_data_array'));
|
||||
createZeroArray = Private(require('components/vislib/components/zero_injection/zero_filled_array'));
|
||||
arr1 = createZeroArray(xValueArr);
|
||||
|
||||
// Takes zero array as 1st arg and data array as 2nd arg
|
||||
results = zeroFillArray(arr1, arr2);
|
||||
});
|
||||
|
@ -521,5 +672,37 @@ define(function (require) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Injected Zero values return in the correct order', function () {
|
||||
var injectZeros;
|
||||
var results;
|
||||
|
||||
beforeEach(function () {
|
||||
module('ZeroInjectionUtilService');
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
inject(function (Private) {
|
||||
injectZeros = Private(require('components/vislib/components/zero_injection/inject_zeros'));
|
||||
results = injectZeros(dateHistogramRows);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return an array of objects', function () {
|
||||
results.rows.forEach(function (row) {
|
||||
expect(_.isArray(row.series[0].values)).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return ordered x values', function () {
|
||||
var values = results.rows[0].series[0].values;
|
||||
expect(values[0].x).to.be.lessThan(values[1].x);
|
||||
expect(values[1].x).to.be.lessThan(values[2].x);
|
||||
expect(values[2].x).to.be.lessThan(values[3].x);
|
||||
expect(values[3].x).to.be.lessThan(values[4].x);
|
||||
expect(values[4].x).to.be.lessThan(values[5].x);
|
||||
expect(values[5].x).to.be.lessThan(values[6].x);
|
||||
expect(values[6].x).to.be.lessThan(values[7].x);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue