Merge pull request #24 from spenceralger/visualize_skelly

Visualize skeleton in place
This commit is contained in:
spenceralger 2014-03-21 16:06:36 -07:00
commit 0ba3e1ba6c
26 changed files with 1175 additions and 29 deletions

View file

@ -0,0 +1,140 @@
define(function (require) {
var _ = require('lodash');
var app = require('modules').get('app/visualize');
require('../factories/vis');
require('../services/aggs');
app.controller('Visualize', function ($scope, courier, createNotifier, Vis, Aggs) {
var notify = createNotifier({
location: 'Visualize Controller'
});
// the object detailing the visualization
var vis = $scope.vis = window.vis = new Vis({
metric: {
label: 'Y-Axis',
min: 1,
max: 1
},
segment: {
label: 'X-Axis',
min: 1,
max: 1
},
group: {
label: 'Color',
max: 10
},
split: {
label: 'Rows & Columns',
max: 2
}
}, {
split: [
{
field: 'response',
size: 5,
agg: 'terms'
},
{
field: '_type',
size: 5,
agg: 'terms'
}
],
segment: [
{
field: '@timestamp',
interval: 'week'
}
],
group: [
{
field: 'extension',
size: 5,
agg: 'terms',
global: true
}
]
});
vis.dataSource.$scope($scope);
$scope.refreshFields = function () {
$scope.fields = null;
vis.dataSource.clearFieldCache().then(getFields, notify.error);
};
function getFields() {
vis.dataSource.getFields(function (err, fieldsHash) {
if (err) return notify.error(err);
// create a sorted list of the fields for display purposes
$scope.fields = _(fieldsHash)
.keys()
.sort()
.transform(function (fields, name) {
var field = fieldsHash[name];
field.name = name;
fields.push(field);
})
.value();
$scope.fields.byName = fieldsHash;
});
}
// get the fields for initial display
getFields();
$scope.Vis = Vis;
$scope.Aggs = Aggs;
$scope.updateDataSource = function () {
notify.event('update data source');
var config = _.groupBy(vis.getConfig(), function (config) {
switch (config.categoryName) {
case 'group':
case 'segment':
return 'dimension';
default:
return config.categoryName;
}
});
if (!config.dimension) {
// use the global aggregation if we don't have any dimensions
config.dimension = [{
agg: 'global'
}];
}
var dsl = {};
var i = 0;
var nest = (function () {
var current = dsl;
return function (config) {
current.aggs = {};
var key = '_agg_' + (i++);
var aggDsl = {};
aggDsl[config.agg] = config.aggParams;
current = current.aggs[key] = aggDsl;
};
}());
config.split && config.split.forEach(nest);
config.dimension && config.dimension.forEach(nest);
config.metric && config.metric.forEach(nest);
notify.log('config', config);
notify.log('aggs', dsl.aggs);
vis.dataSource.aggs(dsl.aggs).fetch();
notify.event('update data source', true);
};
});
});

View file

@ -0,0 +1,25 @@
define(function (require) {
var module = require('modules').get('app/visualize');
var $ = require('jquery');
module.directive('visCanvas', function () {
return {
restrict: 'A',
link: function ($scope, $el) {
var $window = $(window);
var $header = $('.content > nav.navbar:first()');
function stretchVis() {
$el.css('height', $window.height() - $header.height() - 30);
}
stretchVis();
$window.on('resize', stretchVis);
$scope.$on('$destroy', function () {
$window.off('resize', stretchVis);
});
}
};
});
});

View file

@ -0,0 +1,22 @@
define(function (require) {
var html = require('text!../partials/config_category.html');
require('./config_editor');
require('modules')
.get('app/visualize')
.directive('visConfigCategory', function () {
return {
restrict: 'E',
scope: {
categoryName: '=',
vis: '=',
fields: '='
},
template: html,
link: function ($scope, $el) {
$scope.category = $scope.vis[$scope.categoryName];
}
};
});
});

View file

@ -0,0 +1,58 @@
define(function (require) {
var app = require('modules').get('app/visualize');
var _ = require('lodash');
var templates = {
orderAndSize: require('text!../partials/controls/order_and_size.html'),
interval: require('text!../partials/controls/interval.html'),
globalLocal: require('text!../partials/controls/global_local.html')
};
app.directive('visConfigControls', function ($compile, Vis, Aggs) {
return {
restrict: 'E',
scope: {
config: '='
},
link: function ($scope, $el, attr) {
var $controls = $el.find('.agg-param-controls');
$scope.$watch('config.agg', function (aggName) {
var agg = Aggs.aggsByName[aggName];
var controlsHtml = '';
if (agg) {
var aggParams = $scope.aggParams = agg.params;
_.forOwn(aggParams, function (param, name) {
// if the param doesn't have options, or a default value, skip it
if (!param.options) return;
// if there isn't currently a value, or the current value is not one of the options, set it to the default
if (!$scope.config[name] || !_.find(param.options, { val: $scope.config[name] })) {
$scope.config[name] = param.default;
}
});
if (aggParams.order && aggParams.size) {
controlsHtml += ' ' + templates.orderAndSize;
}
if (aggParams.interval) {
controlsHtml += ' ' + templates.interval;
}
if ($scope.config.categoryName === 'group') {
controlsHtml += ' ' + templates.globalLocal;
}
}
$controls.html($compile(controlsHtml)($scope));
});
$scope.Aggs = Aggs;
$scope.Vis = Vis;
}
};
});
});

View file

@ -0,0 +1,133 @@
define(function (require) {
var app = require('modules').get('app/visualize');
var _ = require('lodash');
var $ = require('jquery');
require('filters/field_type');
app.directive('visConfigEditor', function ($compile, Vis, Aggs) {
var categoryOptions = {
metric: {
template: require('text!../partials/editor/metric.html')
},
segment: {
template: require('text!../partials/editor/dimension.html'),
setup: setupDimension
},
group: {
template: require('text!../partials/editor/dimension.html'),
setup: setupDimension
},
split: {
template: require('text!../partials/editor/dimension.html'),
setup: setupDimension
}
};
var controlTemplates = {
orderAndSize: require('text!../partials/controls/order_and_size.html'),
interval: require('text!../partials/controls/interval.html'),
globalLocal: require('text!../partials/controls/global_local.html')
};
// generalized setup for group and segment
function setupDimension($scope, $el) {
var $controls = $el.find('.agg-param-controls');
function getAvailableAggsForField() {
if (!$scope.config.field || !$scope.fields) return;
var field = $scope.fields.byName[$scope.config.field];
// clear the previous choices
$scope.availableAggs = void 0;
// get the new choices
var aggs = Aggs.aggsByFieldType[field.type];
if (!aggs || aggs.length === 0) {
// init or invalid field type
$scope.config.agg = void 0;
return;
}
if (aggs.length === 1) {
// only once choice, make it for the user
$scope.config.agg = aggs[0].name;
return;
}
// set the new choices
$scope.availableAggs = aggs;
// update the agg only if it is not currently a valid option
if (!$scope.config.agg || !_.find(aggs, { name: $scope.config.agg })) {
$scope.config.agg = aggs[0].name;
return;
}
}
// since this depends on the field and field list, watch both
$scope.$watch('config.field', getAvailableAggsForField);
$scope.$watch('fields', getAvailableAggsForField);
$scope.$watch('config.agg', function (aggName) {
var agg = Aggs.aggsByName[aggName];
var controlsHtml = '';
if (agg) {
var params = $scope.aggParams = agg.params;
_.forOwn(params, function (param, name) {
// if the param doesn't have options, or a default value, skip it
if (!param.options) return;
// if there isn't currently a value, or the current value is not one of the options, set it to the default
if (!$scope.config[name] || !_.find(param.options, { val: $scope.config[name] })) {
$scope.config[name] = param.default;
}
});
if (params.order && params.size) {
controlsHtml += ' ' + controlTemplates.orderAndSize;
}
if (params.interval) {
controlsHtml += ' ' + controlTemplates.interval;
if (!controlsHtml.match(/aggParams\.interval\.options/)) debugger;
}
if ($scope.config.categoryName === 'group') {
controlsHtml += ' ' + controlTemplates.globalLocal;
}
}
$controls.html($compile(controlsHtml)($scope));
});
}
return {
restrict: 'E',
scope: {
config: '=',
fields: '=',
vis: '='
},
link: function ($scope, $el, attr) {
var categoryName = $scope.config.categoryName;
var opts = categoryOptions[categoryName];
$scope.Aggs = Aggs;
$scope.Vis = Vis;
// attach a copy of the template to the scope and render
$el.html($compile(opts.template)($scope));
_.defaults($scope.val, opts.defVal || {});
if (opts.setup) opts.setup($scope, $el);
// rather than accessing vis.{{categoryName}} everywhere
$scope[categoryName] = $scope.vis[categoryName];
}
};
});
});

View file

@ -0,0 +1,31 @@
define(function (require) {
var converters = require('../resp_converters/index');
// var K4D3 = require('K4D3');
function VisualizationDirective() {
return {
restrict: 'E',
template: '<div class="chart"><pre>{{ results | json }}</pre></div>',
scope: {
vis: '='
},
link: function ($scope, $el) {
var vis = $scope.vis;
vis
.dataSource
.on('results', function (resp) {
$scope.results = vis.buildChartDataFromResponse(resp).groups;
});
if (!vis.dataSource._$scope) {
// only link if the dataSource isn't already linked
vis.dataSource.$scope($scope);
}
}
};
}
require('modules').get('kibana/directive')
.directive('visualization', VisualizationDirective);
});

View file

@ -0,0 +1,306 @@
define(function (require) {
var converters = require('../resp_converters/index');
var _ = require('lodash');
require('../services/aggs');
function VisFactory(Aggs, $rootScope, $q, createNotifier) {
var notify = createNotifier({
location: 'Visualization'
});
function Vis(config, state) {
config = config || {};
// the visualization type
this.type = config.type || 'histogram';
// the dataSource that will populate the
this.dataSource = $rootScope.rootDataSource.extend().size(0);
// master list of configs, addConfig() writes here and to the list within each
// config category, removeConfig() does the inverse
this.configs = [];
// setup each config category
Vis.configCategories.forEach(function (category) {
var myCat = _.defaults(config[category.name] || {}, category.defaults);
myCat.configs = [];
this[category.name] = myCat;
}, this);
if (state) {
// restore the passed in state
this.setState(state);
} else {
this._fillConfigsToMinimum();
}
}
Vis.configCategories = [
{
name: 'segment',
defaults: {
min: 0,
max: Infinity
},
configDefaults: {
size: 5
}
},
{
name: 'metric',
defaults: {
min: 0,
max: 1
},
configDefaults: {
agg: 'count'
}
},
{
name: 'group',
defaults: {
min: 0,
max: 1
},
configDefaults: {
global: true,
size: 5
}
},
{
name: 'split',
defaults: {
min: 0,
max: 2
},
configDefaults: {
size: 5
}
}
];
Vis.configCategoriesByName = _.indexBy(Vis.configCategories, 'name');
Vis.prototype.addConfig = function (categoryName) {
var category = Vis.configCategoriesByName[categoryName];
var config = _.defaults({}, category.configDefaults);
config.categoryName = category.name;
this.configs.push(config);
this[category.name].configs.push(config);
return config;
};
Vis.prototype.removeConfig = function (config) {
if (!config) return;
_.pull(this.configs, config);
_.pull(this[config.categoryName].configs, config);
};
Vis.prototype.setState = function (state) {
var vis = this;
vis.dataSource.getFields(function (fields) {
vis.configs = [];
_.each(state, function (categoryStates, configCategoryName) {
if (!vis[configCategoryName]) return;
vis[configCategoryName].configs = [];
categoryStates.forEach(function (configState) {
var config = vis.addConfig(configCategoryName);
_.assign(config, configState);
});
});
vis._fillConfigsToMinimum();
});
};
Vis.prototype._fillConfigsToMinimum = function () {
var vis = this;
// satify the min count for each category
Vis.configCategories.forEach(function (category) {
var myCat = vis[category.name];
if (myCat.configs.length < myCat.min) {
_.times(myCat.min - myCat.configs.length, function () {
vis.addConfig(category.name);
});
}
});
};
/**
* Create a list of config objects, which are ready to be turned into aggregations,
* in the order which they should be executed.
*
* @return {Array} - The list of config objects
*/
Vis.prototype.getConfig = function () {
var cats = {
split: [],
global: [],
segment: [],
local: [],
metric: []
};
this.configs.forEach(function (config) {
var pos = config.categoryName;
if (pos === 'group') pos = config.global ? 'global' : 'local';
if (!config.field || !config.agg) return;
var agg = Aggs.aggsByName[config.agg];
if (!agg || agg.name === 'count') return;
var params = {
categoryName: config.categoryName,
agg: config.agg,
aggParams: {
field: config.field
}
};
// ensure that all of the declared params for the agg are declared on the config
var valid = _.every(agg.params, function (paramDef, name) {
if (!config[name]) return;
if (!paramDef.custom && paramDef.options && !_.find(paramDef.options, { val: config[name] })) return;
// copy over the param
params.aggParams[name] = config[name];
// allow provide a hook to covert string values into more complex structures
if (paramDef.toJSON) {
params.aggParams[name] = paramDef.toJSON(params.aggParams[name]);
}
return true;
});
if (valid) cats[pos].push(params);
});
return cats.split.concat(cats.global, cats.segment, cats.local, cats.metric);
};
/**
* Transform an ES Response into data for this visualization
* @param {object} resp The elasticsearch response
* @return {array} An array of flattened response rows
*/
Vis.prototype.buildChartDataFromResponse = function (resp) {
notify.event('convert ES response');
function createGroup(bucket) {
var g = {};
if (bucket) g.key = bucket.key;
return g;
}
function finishRow(bucket) {
// collect the count and bail, free metric!!
level.rows.push(row.concat(bucket.value === void 0 ? bucket.doc_count : bucket.value));
}
// all aggregations will be prefixed with:
var aggKeyPrefix = '_agg_';
var converter = converters[this.type];
// as we move into the different aggs, shift configs
var childConfigs = this.getConfig();
var lastCol = childConfigs[childConfigs.length - 1];
// into stack, and then back when we leave a level
var stack = [];
var row = [];
var chartData = createGroup();
var level = chartData;
(function splitAndFlatten(bucket) {
var col = childConfigs.shift();
// add it to the top of the stack
stack.unshift(col);
_.forOwn(bucket, function (result, key) {
// filter out the non prefixed keys
if (key.substr(0, aggKeyPrefix.length) !== aggKeyPrefix) return;
if (col.categoryName === 'split') {
var parent = level;
result.buckets.forEach(function (bucket) {
var group = createGroup(bucket);
if (parent.groups) parent.groups.push(group);
else parent.groups = [group];
level = group;
splitAndFlatten(bucket);
if (group.rows && group.columns) {
group.data = converter(group.columns, group.rows);
delete group.rows;
delete group.columns;
}
});
level = parent;
return;
}
if (!level.columns || !level.rows) {
// setup this level to receive records
level.columns = [stack[0]].concat(childConfigs);
level.rows = [];
// the columns might now end in a metric, but the rows will
if (childConfigs[childConfigs.length - 1].categoryName !== 'metric') {
level.columns.push({
categoryName: 'metric',
agg: 'count'
});
}
}
if (col.categoryName === 'metric') {
// one row per bucket
finishRow(result);
} else {
// keep digging
result.buckets.forEach(function (bucket) {
// track this bucket's "value" in our temporary row
row.push(bucket.key);
if (col === lastCol) {
// also grab the bucket's count
finishRow(bucket);
} else {
splitAndFlatten(bucket);
}
row.pop();
});
}
});
childConfigs.unshift(stack.shift());
})(resp.aggregations);
notify.event('convert ES response', true);
return chartData;
};
return Vis;
}
require('modules')
.get('kibana/services')
.factory('Vis', VisFactory);
});

View file

@ -1 +1,22 @@
<h1>Visualize</h1>
<!-- <div class="container-fluid"> -->
<div ng-controller="Visualize">
<div class="row">
<div class="col-md-3">
<form ng-submit="updateDataSource()">
<ul class="vis-config-panel">
<li ng-repeat="category in Vis.configCategories">
<vis-config-category
category-name="category.name"
vis="vis"
fields="fields">
</vis-config-category>
</li>
</ul>
<button type="submit" class="btn btn-primary">Update</button>
</form>
</div>
<div class="col-md-9 vis-canvas">
<visualization vis-canvas vis="vis"></visualization>
</div>
</div>
</div>

View file

@ -1,5 +1,9 @@
define(function (require) {
require('css!./styles/main.css');
var app = require('modules').get('app/visualize');
require('./controllers/visualize');
require('./directives/config_category');
require('./directives/canvas');
require('./directives/visualization');
});

View file

@ -0,0 +1,22 @@
<div
class="panel panel-default"
ng-if="categoryName !== 'group' || vis.segment.configs.length > 0">
<div class="panel-heading" >
{{ category.label }}
<button
ng-if="category.configs.length < category.max"
ng-click="vis.addConfig(categoryName)"
class="btn btn-default" >
<i class="fa fa-plus"></i>
</button>
</div>
<div class="panel-body">
<div ng-repeat="config in category.configs">
<vis-config-editor
config="config"
vis="vis"
fields="fields">
</vis-config-editor>
</div>
</div>
</div>

View file

@ -0,0 +1,22 @@
<div class="btn-group pull-right">
<button
type="button"
class="btn btn-primary"
ng-model="config.global"
tooltip="Use the {{group.label}} values for every {{vis.segment.label}} value"
tooltip-position="top"
tooltip-append-to-body="true"
btn-radio="true">
Global
</button>
<button
type="button"
class="btn btn-primary"
tooltip="Break up each {{vis.segment.label}} value by {{group.label}}"
tooltip-position="top"
tooltip-append-to-body="true"
ng-model="config.global"
btn-radio="false">
Local
</button>
</div>

View file

@ -0,0 +1,23 @@
<table class="agg-config-interval">
<tr>
<td>
<select
ng-model="config.interval"
ng-options="opt.val as opt.display for opt in aggParams.interval.options"
class="form-control"
name="interval">
</select>
</td>
<!-- <td ng-if="config.interval === 'customInterval'">
<input
ng-model="config.customInterval"
class="form-control"
type="number"
name="custom interval">
</td>
<td ng-if="config.interval === 'customInterval'">
<kbn-info info="'interval bewteen timestamp measurements, in milliseconds'"></kbn-info>
</td> -->
</tr>
</table>
</div>

View file

@ -0,0 +1,13 @@
<div class="row">
<div class="col-sm-6">
<select
class="form-control"
name="order"
ng-model="config.order"
ng-options="opt.val as opt.display for opt in aggParams.order.options">
</select>
</div>
<div class="col-sm-6">
<input class="form-control" type="number" ng-model="config.size" name="size">
</div>
</div>

View file

@ -0,0 +1,19 @@
<div class="form-group">
<label for="field">
Field
</label>
<select
class="form-control"
name="field"
ng-model="config.field"
ng-options="field.name as field.name group by field.type for field in fields">
</select>
</div>
<div class="form-group" ng-show="availableAggs">
<select
name="agg"
ng-model="config.agg"
ng-options="agg.name as agg.display for agg in availableAggs">
</select>
</div>
<div class="agg-param-controls">

View file

@ -0,0 +1,21 @@
<div class="form-group">
<label for="stat">Stat</label>
<select
class="form-control"
name="stat"
ng-model="config.agg"
ng-options="agg.name as agg.name for agg in Aggs.metricAggs">
</select>
</div>
<div class="form-group" ng-if="config.agg && config.agg !== 'count'">
<label for="field">
Field to {{config.agg}}&nbsp;
<kbn-info placement="right" info="'Field to use for the ' + metric.label"></kbn-info>
</label>
<select
class="form-control"
name="field"
ng-model="config.field"
ng-options="field.name as field.name for field in fields | fieldType:'number'">
</select>
</div>

View file

@ -0,0 +1,4 @@
define(function () {
var _ = require('lodash');
var aggKeyPrefix = '_agg_';
})

View file

@ -0,0 +1,35 @@
define(function (require) {
var _ = require('lodash');
return function (columns, rows) {
var serieses = [];
var seriesesByLabel = {};
// index of color
var iColor = _.findIndex(columns, { categoryName: 'group' });
// index of x-axis
var iX = _.findIndex(columns, { categoryName: 'segment'});
// index of y-axis
var iY = _.findIndex(columns, { categoryName: 'metric'});
rows.forEach(function (row) {
var series = seriesesByLabel[iColor === -1 ? 'undefined' : row[iColor]];
if (!series) {
series = {
label: '' + row[iColor],
children: []
};
serieses.push(series);
seriesesByLabel[series.label] = series;
}
series.children.push([
row[iX], // x-axis value
row[iY === -1 ? row.length - 1 : iY] // y-axis value
]);
});
return serieses;
};
});

View file

@ -0,0 +1,5 @@
define(function (require) {
return {
histogram: require('./histogram')
};
});

View file

@ -0,0 +1,120 @@
define(function (require) {
require('utils/mixins');
var _ = require('lodash');
function AggsService() {
this.metricAggs = [
{
name: 'count',
display: 'Count'
},
{
name: 'avg',
display: 'Average'
},
{
name: 'sum',
display: 'Sum'
},
{
name: 'min',
display: 'Min'
},
{
name: 'max',
display: 'Max'
}
];
this.metricAggsByName = _.indexBy(this.metricAggs, 'name');
this.bucketAggs = [
{
name: 'histogram',
display: 'Histogram',
params: {
size: {},
order: {
options: [
{ display: 'Top', val: 'desc' },
{ display: 'Bottom', val: 'asc' }
],
default: 'desc',
toJSON: function (val) {
return { _count: val };
}
}
}
},
{
name: 'terms',
display: 'Terms',
params: {
size: {},
order: {
options: [
{ display: 'Top', val: 'desc' },
{ display: 'Bottom', val: 'asc' }
],
default: 'desc',
toJSON: function (val) {
return { _count: val };
}
}
}
},
{
name: 'date_histogram',
display: 'Date Histogram',
params: {
interval: {
options: [
{ display: 'Hourly', val: 'hour' },
{ display: 'Daily', val: 'day' },
{ display: 'Weekly', val: 'week' },
{ display: 'Monthly', val: 'month' },
{ display: 'Quarterly', val: 'quarter' },
{ display: 'Yearly', val: 'year' }
],
default: 'hour'
},
}
}
];
this.bucketAggsByName = _.indexBy(this.bucketAggs, 'name');
this.aggsByName = _.assign({}, this.bucketAggsByName, this.metricAggsByName);
this.aggsByFieldType = {
number: [
this.bucketAggsByName.histogram,
this.bucketAggsByName.terms,
// 'range'
],
date: [
// 'date range',
this.bucketAggsByName.date_histogram,
],
boolean: [
// 'terms'
],
ip: [
// 'ipv4 range'
],
geo_point: [
// 'geo distance'
],
geo_shape: [
// 'geohash grid'
],
string: [
// 'significant terms',
this.bucketAggsByName.terms,
// 'range'
]
};
}
require('modules').get('app/visualize')
.service('Aggs', AggsService);
});

View file

@ -0,0 +1,17 @@
define(function (require) {
var app = require('modules').get('app/visualize');
function VisualizationsService(es, courier, $q, $timeout) {
this.get = function (id) {
var defer = $q.defer();
$timeout(function () {
defer.reject('not implemented');
}, 2000);
return defer.promise();
};
}
app.service('Visualizations', VisualizationsService);
});

View file

@ -0,0 +1,32 @@
.vis-config-panel {
padding: 0;
}
.vis-config-panel > li {
list-style: none;
}
.vis-config-panel > li .panel-heading {
position: relative;
}
.vis-config-panel > li .panel-heading button {
position: absolute;
top: 6px;
right: 7px;
padding: 3px 8px;
}
.vis-config-panel .agg-config-interval td {
padding-left: 10px;
}
.vis-config-panel .agg-config-interval td:first-child {
padding-left: 0px;
}
.vis-canvas {
padding: 15px 0;
}
.vis-canvas visualization {
display: block;
background: black;
height: 800px;
width: 95%;
margin: 0 auto;
overflow: auto;
}

View file

@ -0,0 +1,40 @@
.vis-config-panel {
padding: 0;
> li {
list-style: none;
.panel-heading {
position: relative;
button {
position: absolute;
top: 6px;
right: 7px;
padding: 3px 8px;
}
}
}
.agg-config-interval {
td {
padding-left: 10px;
&:first-child {
padding-left: 0px;
}
}
}
}
.vis-canvas {
padding: 15px 0;
visualization {
display: block;
background: black;
height: 800px;
width: 95%;
margin: 0 auto;
overflow: auto;
}
}

View file

@ -5,6 +5,7 @@ define(function (require) {
require('services/config');
require('services/courier');
require('directives/info');
require('angular-bootstrap');
require('modules')
@ -30,7 +31,7 @@ define(function (require) {
});
$rootScope.rootDataSource = courier.createSource('search')
.index('_all');
.index('logstash-*');
$scope.opts = {
activeFetchInterval: void 0,

View file

@ -7,6 +7,7 @@ define(function (require) {
var setTO = setTimeout;
var clearTO = clearTimeout;
var log = (typeof KIBANA_DIST === 'undefined') ? _.bindKey(console, 'log') : _.noop;
var consoleGroups = ('group' in window.console) && ('groupCollapsed' in window.console) && ('groupEnd' in window.console);
var fatalToastTemplate = (function lazyTemplate(tmpl) {
var compiled;
@ -74,14 +75,6 @@ define(function (require) {
return msg + '\n' + stack.map(function (line) { return ' ' + line; }).join('\n');
}
/**
* Track application lifecycle events
* @type {[type]}
*/
var lifecycleEvents = window.kibanaLifecycleEvents = {};
var applicationBooted;
/**
* Functionality to check that
*/
@ -95,26 +88,65 @@ define(function (require) {
this._notifs = notifs;
}
NotifyManager.prototype.lifecycle = function (name, success) {
var status;
if (name === 'bootstrap' && success === true) applicationBooted = true;
NotifyManager.prototype.log = log;
if (success === void 0) {
// start
lifecycleEvents[name] = now();
} else {
// end
if (success) {
lifecycleEvents[name] = now() - (lifecycleEvents[name] || 0);
status = lifecycleEvents[name].toFixed(2) + ' ms';
// general functionality used by .event() and .lifecycle()
function createGroupLogger(type, opts) {
// Track the groups managed by this logger
var groups = window[type + 'Groups'] = {};
return function (name, success) {
var status;
if (success === void 0) {
// start
groups[name] = now();
} else {
lifecycleEvents[name] = false;
status = 'failure';
}
}
groups[name] = now() - (groups[name] || 0);
var time = ' in ' + groups[name].toFixed(2) + 'ms';
log('KBN: ' + name + (status ? ' - ' + status : ''));
};
// end
if (success) {
status = 'complete' + time;
} else {
groups[name] = false;
status = 'failure' + time;
}
}
if (consoleGroups) {
if (status) {
console.log(status);
console.groupEnd();
} else {
if (opts.open) {
console.group(name);
} else {
console.groupCollapsed(name);
}
}
} else {
log('KBN: ' + name + (status ? ' - ' + status : ''));
}
};
}
/**
* Log a sometimes redundant event
* @param {string} name - The name of the group
* @param {boolean} success - Simple flag stating whether the event succeeded
*/
NotifyManager.prototype.event = createGroupLogger('event', {
open: false
});
/**
* Log a major, important, event in the lifecycle of the application
* @param {string} name - The name of the lifecycle event
* @param {boolean} success - Simple flag stating whether the lifecycle event succeeded
*/
NotifyManager.prototype.lifecycle = createGroupLogger('lifecycle', {
open: true
});
/**
* Kill the page, and display an error

View file

@ -144,7 +144,7 @@ define(function (require) {
*******/
function _change(key, val) {
notify.lifecycle('config change: ' + key + ': ' + vals[key] + ' -> ' + val);
notify.log('config change: ' + key + ': ' + vals[key] + ' -> ' + val);
triggerWatchers(watchers[key], val, vals[key]);
vals[key] = val;
}