Merge branch 'master' of github.com:elastic/kibana into feature/hapi-server

This commit is contained in:
Chris Cowan 2015-05-11 10:56:11 -07:00
commit 1281e1317e
233 changed files with 6580 additions and 2531 deletions

View file

@ -1,5 +1,4 @@
define(function (require) {
var _ = require('lodash');
define(function () {
return function (leaf) {
// walk up the branch for each parent
function walk(item, memo) {

View file

@ -7,8 +7,11 @@ define(function (require) {
return function getPoint(x, series, yScale, row, y, z) {
var zRow = z && row[z.i];
var xRow = row[x.i];
var point = {
x: unwrap(row[x.i], '_all'),
x: unwrap(xRow, '_all'),
xi: xRow && xRow.$order,
y: unwrap(row[y.i]),
z: zRow && unwrap(zRow),
aggConfigResult: row[y.i],

View file

@ -1,4 +1,4 @@
define(function (require) {
define(function () {
return function PointSeriesInitX() {
return function initXAxis(chart) {
var x = chart.aspects.x;
@ -14,4 +14,4 @@ define(function (require) {
}
};
};
});
});

View file

@ -19,4 +19,4 @@ define(function (require) {
chart.yScale = xAggOutput.metricScale || null;
};
};
});
});

View file

@ -1,6 +1,5 @@
define(function (require) {
return function PointSeriesTooltipFormatter($compile, $rootScope) {
var _ = require('lodash');
var $ = require('jquery');
var $tooltipScope = $rootScope.$new();

View file

@ -8,8 +8,6 @@ define(function (require) {
.directive('kbnAggTable', function ($filter, config, Private, compileRecursiveDirective) {
var _ = require('lodash');
var orderBy = $filter('orderBy');
return {
restrict: 'E',
template: require('text!components/agg_table/agg_table.html'),
@ -54,7 +52,7 @@ define(function (require) {
}
// escape each cell in each row
var csvRows = rows.map(function (row, i) {
var csvRows = rows.map(function (row) {
return row.map(escape);
});
@ -72,16 +70,13 @@ define(function (require) {
var table = $scope.table;
if (!table) {
$scope.formattedRows = null;
$scope.rows = null;
$scope.formattedColumns = null;
return;
}
setFormattedRows(table);
setFormattedColumns(table);
});
function setFormattedColumns(table) {
self.csv.filename = (table.title() || 'table') + '.csv';
$scope.rows = table.rows;
$scope.formattedColumns = table.columns.map(function (col, i) {
var agg = $scope.table.aggConfig(col);
var field = agg.field();
@ -98,14 +93,7 @@ define(function (require) {
return formattedColumn;
});
}
function setFormattedRows(table) {
$scope.rows = table.rows;
// update the csv file's title
self.csv.filename = (table.title() || 'table') + '.csv';
}
});
}
};
});

View file

@ -2,6 +2,7 @@ define(function (require) {
return function AggTypeFactory(Private) {
var _ = require('lodash');
var AggParams = Private(require('components/agg_types/_agg_params'));
var fieldFormats = Private(require('registry/field_formats'));
/**
* Generic AggType Constructor
@ -116,8 +117,25 @@ define(function (require) {
* is created, giving the agg type a chance to modify the agg config
*/
this.decorateAggConfig = config.decorateAggConfig || null;
if (config.getFormat) {
this.getFormat = config.getFormat;
}
}
/**
* Pick a format for the values produced by this agg type,
* overriden by several metrics that always output a simple
* number
*
* @param {agg} agg - the agg to pick a format for
* @return {FieldFromat}
*/
AggType.prototype.getFormat = function (agg) {
var field = agg.field();
return field ? field.format : fieldFormats.getDefaultInstance('string');
};
return AggType;
};
});

View file

@ -6,13 +6,24 @@ define(function (require) {
return function DateRangeAggDefinition(Private, config) {
var BucketAggType = Private(require('components/agg_types/buckets/_bucket_agg_type'));
var createFilter = Private(require('components/agg_types/buckets/create_filter/date_range'));
var fieldFormats = Private(require('registry/field_formats'));
return new BucketAggType({
name: 'date_range',
title: 'Date Range',
createFilter: createFilter,
getKey: function (bucket) {
return dateRange.toString(bucket, config.get('dateFormat'));
getKey: function (bucket, key, agg) {
var formatter;
if (agg.field()) {
formatter = agg.field().format.getConverterFor('text');
} else {
formatter = fieldFormats.getDefaultInstance('date').getConverterFor('text');
}
return dateRange.toString(bucket, formatter);
},
getFormat: function () {
return fieldFormats.getDefaultInstance('string');
},
makeLabel: function (aggConfig) {
return aggConfig.params.field.displayName + ' date ranges';

View file

@ -1,15 +1,19 @@
define(function (require) {
return function MetricAggTypeProvider(Private, indexPatterns) {
return function MetricAggTypeProvider(Private) {
var _ = require('lodash');
var AggType = Private(require('components/agg_types/_agg_type'));
var fieldFormats = Private(require('registry/field_formats'));
_(MetricAggType).inherits(AggType);
function MetricAggType(config) {
MetricAggType.Super.call(this, config);
if (_.isFunction(config.getValue)) {
this.getValue = config.getValue;
}
// allow overriding any value on the prototype
_.forOwn(config, function (val, key) {
if (_.has(MetricAggType.prototype, key)) {
this[key] = val;
}
}, this);
}
/**
@ -21,15 +25,19 @@ define(function (require) {
return bucket[agg.id].value;
};
/**
* Pick a format for the values produced by this agg type,
* overriden by several metrics that always output a simple
* number
*
* @param {agg} agg - the agg to pick a format for
* @return {FieldFromat}
*/
MetricAggType.prototype.getFormat = function (agg) {
var field = agg.field();
if (field && field.type === 'date' && field.format) {
return field.format;
} else {
return indexPatterns.fieldFormats.byName.number;
}
return field ? field.format : fieldFormats.getDefaultInstance('number');
};
return MetricAggType;
};
});
});

View file

@ -16,4 +16,4 @@ define(function (require) {
]
});
};
});
});

View file

@ -1,6 +1,7 @@
define(function (require) {
return function AggTypeMetricCardinalityProvider(Private) {
var MetricAggType = Private(require('components/agg_types/metrics/_metric_agg_type'));
var fieldFormats = Private(require('registry/field_formats'));
return new MetricAggType({
name: 'cardinality',
@ -8,6 +9,9 @@ define(function (require) {
makeLabel: function (aggConfig) {
return 'Unique count of ' + aggConfig.params.field.displayName;
},
getFormat: function () {
return fieldFormats.getDefaultInstance('number');
},
params: [
{
name: 'field'
@ -15,4 +19,4 @@ define(function (require) {
]
});
};
});
});

View file

@ -1,17 +1,21 @@
define(function (require) {
return function AggTypeMetricCountProvider(Private) {
var MetricAggType = Private(require('components/agg_types/metrics/_metric_agg_type'));
var fieldFormats = Private(require('registry/field_formats'));
return new MetricAggType({
name: 'count',
title: 'Count',
hasNoDsl: true,
makeLabel: function (aggConfig) {
makeLabel: function () {
return 'Count';
},
getFormat: function () {
return fieldFormats.getDefaultInstance('number');
},
getValue: function (agg, bucket) {
return bucket.doc_count;
}
});
};
});
});

View file

@ -4,6 +4,7 @@ define(function (require) {
var MetricAggType = Private(require('components/agg_types/metrics/_metric_agg_type'));
var getResponseAggConfig = Private(require('components/agg_types/metrics/_get_response_agg_config'));
var fieldFormats = Private(require('registry/field_formats'));
var valuesEditor = require('text!components/agg_types/controls/percentile_ranks.html');
// required by the values editor
@ -39,6 +40,9 @@ define(function (require) {
return new ValueAggConfig(value);
});
},
getFormat: function () {
return fieldFormats.getInstance('percent') || fieldFormats.getDefaultInstance('number');
},
getValue: function (agg, bucket) {
// values for 1, 5, and 10 will come back as 1.0, 5.0, and 10.0 so we
// parse the keys and respond with the value that matches

View file

@ -5,6 +5,7 @@ define(function (require) {
var MetricAggType = Private(require('components/agg_types/metrics/_metric_agg_type'));
var getResponseAggConfig = Private(require('components/agg_types/metrics/_get_response_agg_config'));
var ordinalSuffix = require('utils/ordinal_suffix');
var fieldFormats = Private(require('registry/field_formats'));
var percentsEditor = require('text!components/agg_types/controls/percentiles.html');
// required by the percentiles editor
@ -40,6 +41,9 @@ define(function (require) {
return new ValueAggConfig(percent);
});
},
getFormat: function () {
return fieldFormats.getInstance('percent') || fieldFormats.getDefaultInstance('number');
},
getValue: function (agg, bucket) {
// percentiles for 1, 5, and 10 will come back as 1.0, 5.0, and 10.0 so we
// parse the keys and respond with the value that matches

View file

@ -1,9 +1,20 @@
define(function (require) {
var _ = require('lodash');
var angular = require('angular');
require('modules').get('kibana')
.config(function ($provide) {
function strictEquality(a, b) {
// are the values equal? or, are they both NaN?
return a === b || (a !== a && b !== b);
}
function errorNotAssignable(source, target) {
throw Error('Unable to accept change to bound $scope property "' + source + '"' +
' because source expression "' + target + '" is not assignable!');
}
$provide.decorator('$rootScope', function ($delegate, $parse) {
/**
* Two-way bind a value from scope to another property on scope. This
@ -12,23 +23,58 @@ define(function (require) {
*
* @param {expression} to - the location on scope to bind to
* @param {expression} from - the location on scope to bind from
* @param {Scope} $sourceScope - the scope to read "from" expression from
* @return {undefined}
*/
$delegate.constructor.prototype.$bind = function (to, from) {
var $source = this.$parent;
$delegate.constructor.prototype.$bind = function (to, from, $sourceScope) {
var $source = $sourceScope || this.$parent;
var $target = this;
var getter = $parse(from);
var setter = $parse(to).assign;
// parse expressions
var $to = $parse(to);
if (!$to.assign) errorNotAssignable(to, from);
var $from = $parse(from);
$from.assignOrFail = $from.assign || function () {
// revert the change and throw an error, child writes aren't supported
$to($target, lastSourceVal = $from($source));
errorNotAssignable(from, to);
};
setter($target, getter($source));
this.$watch(
function () { return getter($source); },
function (val) { setter($target, val); }
);
// bind scopes to expressions
var getTarget = function () { return $to($target); };
var setTarget = function (v) { return $to.assign($target, v); };
var getSource = function () { return $from($source); };
var setSource = function (v) { return $from.assignOrFail($source, v); };
// if we are syncing down a literal, then we use loose equality check
var strict = !$from.literal;
var compare = strict ? strictEquality : angular.equals;
// to support writing from the child to the parent we need to know
// which source has changed. Track the source value and anytime it
// changes (even if the target value changed too) push from source
// to target. If the source hasn't changed then the change is from
// the target and push accordingly
var lastSourceVal = getSource();
// push the initial value down, start off in sync
setTarget(lastSourceVal);
$target.$watch(function () {
var sourceVal = getSource();
var targetVal = getTarget();
var outOfSync = !compare(sourceVal, targetVal);
var sourceChanged = outOfSync && !compare(sourceVal, lastSourceVal);
if (sourceChanged) setTarget(sourceVal);
else if (outOfSync) setSource(targetVal);
return lastSourceVal = sourceVal;
}, null, !strict);
};
return $delegate;
});
});
});
});

View file

@ -0,0 +1,43 @@
define(function (require) {
return function BoundToConfigObjProvider($rootScope, config) {
var _ = require('lodash');
/**
* Create an object with properties that may be bound to config values.
* The input object is basically cloned unless one of it's own properties
* resolved to a string value that starts with an equal sign. When that is
* found, that property is forever bound to the corresponding config key.
*
* example:
*
* // name is cloned, height is bound to the defaultHeight config key
* { name: 'john', height: '=defaultHeight' };
*
* @param {Object} input
* @return {Object}
*/
function BoundToConfigObj(input) {
var self = this;
_.forOwn(input, function (val, prop) {
if (!_.isString(val) || val.charAt(0) !== '=') {
self[prop] = val;
return;
}
var configKey = val.substr(1);
update();
$rootScope.$on('init:config', update);
$rootScope.$on('change:config.' + configKey, update);
function update() {
self[prop] = config.get(configKey);
}
});
}
return BoundToConfigObj;
};
});

View file

@ -1,6 +1,6 @@
define(function (require) {
return function () {
var _ = require('lodash');
define(function () {
return function configDefaultsProvider() {
// wraped in provider so that a new instance is given to each app/test
return {
'query:queryString:options': {
@ -87,6 +87,36 @@ define(function (require) {
value: 5,
description: 'For index patterns containing timestamps in their names, look for this many recent matching ' +
'patterns from which to query the field mapping.'
},
'format:defaultTypeMap': {
type: 'json',
value: [
'{',
' "ip": { "id": "ip", "params": {} },',
' "date": { "id": "date", "params": {} },',
' "number": { "id": "number", "params": {} },',
' "_source": { "id": "_source", "params": {} },',
' "_default_": { "id": "string", "params": {} }',
'}',
].join('\n'),
description: 'Map of the format name to use by default for each field type. ' +
'"_default_" is used if the field type is not mentioned explicitly.'
},
'format:number:defaultPattern': {
type: 'string',
value: '0,0.[000]'
},
'format:bytes:defaultPattern': {
type: 'string',
value: '0,0.[000]b'
},
'format:percent:defaultPattern': {
type: 'string',
value: '0,0.[000]%'
},
'format:currency:defaultPattern': {
type: 'string',
value: '($0,0.[00])'
}
};
};

View file

@ -300,7 +300,7 @@ define(function (require) {
* @returns {object}
*/
var cleanFilter = function (filter) {
return _.omit(filter, ['$$hashKey', 'meta']);
return _.omit(filter, ['meta']);
};
// switch to filtered query if there are filters

View file

@ -1,9 +1,9 @@
define(function (require) {
return function CourierFetchRequestStatus() {
return {
ABORTED: {},
DUPLICATE: {},
INCOMPLETE: {}
ABORTED: { CourierFetchRequestStatus: 'aborted' },
DUPLICATE: { CourierFetchRequestStatus: 'duplicate' },
INCOMPLETE: { CourierFetchRequestStatus: 'incomplete' }
};
};
});

View file

@ -38,7 +38,7 @@ define(function (require) {
if (source.history) {
source.history.push(this);
source.history = _.last(source.history, 20);
source.history = _.last(source.history, 1);
}
};

View file

@ -241,6 +241,7 @@ define(function (require) {
return self.id;
});
};
return docSource.doCreate(source)
.then(finish)
.catch(function (err) {

View file

@ -2,7 +2,6 @@ define(function (require) {
var _ = require('lodash');
var $ = require('jquery');
var addWordBreaks = require('utils/add_word_breaks');
var noWhiteSpace = require('utils/no_white_space');
var module = require('modules').get('app/discover');
require('components/highlight/highlight');
@ -23,12 +22,11 @@ define(function (require) {
* <tr ng-repeat="row in rows" kbn-table-row="row"></tr>
* ```
*/
module.directive('kbnTableRow', function ($compile, config, highlightFilter, highlightTags, shortDotsFilter, courier) {
module.directive('kbnTableRow', function ($compile) {
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!components/doc_table/components/table_row/_source.html')));
return {
restrict: 'A',
@ -38,12 +36,11 @@ define(function (require) {
indexPattern: '=',
row: '=kbnTableRow'
},
link: function ($scope, $el, attrs) {
link: function ($scope, $el) {
$el.after('<tr>');
$el.empty();
var init = function () {
_formatRow($scope.row);
createSummaryRow($scope.row, $scope.row._id);
};
@ -92,37 +89,24 @@ define(function (require) {
// create a tr element that lists the value for each *column*
function createSummaryRow(row) {
var indexPattern = $scope.indexPattern;
// We just create a string here because its faster.
var newHtmls = [
openRowHtml
];
if ($scope.indexPattern.timeFieldName) {
if (indexPattern.timeFieldName) {
newHtmls.push(cellTemplate({
timefield: true,
formatted: _displayField(row, $scope.indexPattern.timeFieldName)
formatted: _displayField(row, indexPattern.timeFieldName)
}));
}
$scope.columns.forEach(function (column) {
var formatted;
var sources = _.extend({}, row.$$_formatted, row.highlight);
if (column === '_source') {
var sourceConfig = {
source: _.mapValues(sources, function (val, field) {
return _displayField(row, field, false);
}),
highlight: row.highlight,
shortDotsFilter: shortDotsFilter
};
formatted = sourceTemplate(sourceConfig);
} else {
formatted = _displayField(row, column, true);
}
newHtmls.push(cellTemplate({
timefield: false,
formatted: formatted
formatted: _displayField(row, column, true)
}));
});
@ -164,9 +148,9 @@ define(function (require) {
/**
* Fill an element with the value of a field
*/
function _displayField(row, field, breakWords) {
var text = _getValForField(row, field);
text = highlightFilter(text, row.highlight && row.highlight[field]);
function _displayField(row, fieldName, breakWords) {
var indexPattern = $scope.indexPattern;
var text = indexPattern.formatField(row, fieldName);
if (breakWords) {
text = addWordBreaks(text, MIN_LINE_LENGTH);
@ -181,56 +165,6 @@ define(function (require) {
return text;
}
/**
* get the value of a field from a row, serialize it to a string
* and truncate it if necessary
*
* @param {object} row - the row to pull the value from
* @param {string} field - the name of the field (dot-seperated paths are accepted)
* @return {[type]} a string, which should be inserted as text, or an element
*/
function _getValForField(row, field) {
var val;
if (row.highlight && row.highlight[field]) {
// Strip out the highlight tags so we have the "original" value
var untagged = _.map(row.highlight[field], function (value) {
return value
.split(highlightTags.pre).join('')
.split(highlightTags.post).join('');
});
return _formatField(untagged, 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;
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) {
$scope.indexPattern.flattenHit(row);
row.$$_formatted = row.$$_formatted || _.mapValues(row.$$_flattened, _formatField);
return row.$$_formatted;
}
init();
}
};

View file

@ -1,4 +1,5 @@
define(function (require) {
var _ = require('lodash');
require('modules').get('kibana')
.run(function ($rootScope, docTitle) {
@ -27,7 +28,7 @@ define(function (require) {
parts.push(baseTitle);
}
return parts.filter(Boolean).join(' - ');
return _(parts).flatten().compact().join(' - ');
}
self.change = function (title, complete) {
@ -48,4 +49,4 @@ define(function (require) {
return function DoctitleProvider(docTitle) {
return docTitle;
};
});
});

View file

@ -49,12 +49,27 @@
tooltip-placement="top"
tooltip="Objects in arrays are not well supported."
class="fa fa-warning text-color-warning ng-scope doc-viewer-object-array"></i>
<div class="doc-viewer-value" ng-bind-html="(typeof(formatted[field]) === 'undefined' ? hit[field] : formatted[field]) | highlight : hit.highlight[field] | trustAsHtml"></div>
<div class="doc-viewer-value" ng-bind-html="typeof(formatted[field]) === 'undefined' ? hit[field] : formatted[field] | trustAsHtml"></div>
</td>
</tr>
</tbody>
</table>
<div id="json-ace" ng-show="mode == 'json'" readonly ui-ace="{ useWrapMode: true, advanced: { highlightActiveLine: false }, rendererOptions: { showPrintMargin: false, maxLines: 4294967296 }, mode: 'json' }" ng-model="hit_json"></div>
<div
id="json-ace"
ng-show="mode == 'json'"
ng-model="hitJson"
readonly
ui-ace="{
useWrapMode: true,
advanced: {
highlightActiveLine: false
},
rendererOptions: {
showPrintMargin: false,
maxLines: 4294967296
},
mode: 'json'
}"></div>
</div>
</div>

View file

@ -8,8 +8,6 @@ define(function (require) {
require('modules').get('kibana')
.directive('docViewer', function (config, Private) {
var formats = Private(require('components/index_patterns/_field_formats'));
return {
restrict: 'E',
template: html,
@ -21,21 +19,11 @@ define(function (require) {
},
link: function ($scope, $el, attr) {
// If a field isn't in the mapping, use this
var defaultFormat = formats.defaultByType.string;
$scope.mode = 'table';
$scope.mapping = $scope.indexPattern.fields.byName;
$scope.flattened = $scope.indexPattern.flattenHit($scope.hit);
$scope.hit_json = angular.toJson($scope.hit, true);
$scope.formatted = _.mapValues($scope.flattened, function (value, name) {
var mapping = $scope.mapping[name];
var formatter = (mapping && mapping.format) ? mapping.format : defaultFormat;
if (_.isArray(value) && typeof value[0] === 'object') {
value = JSON.stringify(value, null, ' ');
}
return formatter.convert(value);
});
$scope.hitJson = angular.toJson($scope.hit, true);
$scope.formatted = $scope.indexPattern.formatHit($scope.hit);
$scope.fields = _.keys($scope.flattened).sort();
$scope.toggleColumn = function (fieldName) {

View file

@ -0,0 +1,26 @@
define(function (require) {
var _ = require('lodash');
var NL_RE = /\n/g;
var events = 'keydown keypress keyup change';
require('modules').get('kibana')
.directive('elasticTextarea', function () {
return {
restrict: 'A',
link: function ($scope, $el) {
function resize() {
$el.attr('rows', _.size($el.val().match(NL_RE)) + 1);
}
$el.on(events, resize);
$scope.$evalAsync(resize);
$scope.$on('$destroy', function () {
$el.off(events, resize);
});
}
};
});
});

View file

@ -0,0 +1,127 @@
<form ng-submit="editor.save()" name="form">
<div ng-if="editor.creating" class="form-group">
<label>Name</label>
<input
ng-model="editor.field.name"
required
placeholder="New Scripted Field"
input-focus
class="form-control">
</div>
<div ng-if="editor.creating && editor.indexPattern.fields.byName[editor.field.name]" class="hintbox">
<p>
<i class="fa fa-danger text-danger"></i>
<strong>Mapping Conflict:</strong>
You already have a field with the name {{ editor.field.name }}. Naming your scripted
field with the same name means you won't be able to query both fields at the same time.
</p>
</div>
<div class="form-group">
<label>Type</label>
<input
ng-model="editor.field.type"
readonly
class="form-control">
</div>
<div class="form-group">
<span class="pull-right text-warning hintbox-label" ng-click="editor.showFormatHelp = !editor.showFormatHelp">
<i class="fa fa-warning"></i> Warning
</span>
<label>Format <small>(Default: <i>{{editor.defFormatType.resolvedTitle}}</i>)</small></label>
<div class="hintbox" ng-if="editor.showFormatHelp">
<h4 class="hintbox-heading">
<i class="fa fa-warning text-warning"></i> Format Warning
</h4>
<p>
Formatting allows you to control the way that specific values are displayed. It can also cause values to be completely changed and prevent highlighting in Discover from working.
</p>
</div>
<select
ng-model="editor.selectedFormatId"
ng-options="format.id as format.title for format in editor.fieldFormatTypes"
class="form-control">
</select>
<fieldset
field-format-editor
ng-if="editor.selectedFormatId"
field="editor.field"
format-params="editor.formatParams">
</fieldset>
</div>
<div class="form-group">
<label for="editor.field.count">Popularity</label>
<div class="input-group">
<input
ng-model="editor.field.count"
type="number"
class="form-control">
<span class="input-group-btn">
<button
type="button"
ng-click="editor.field.count = editor.field.count + 1"
aria-label="Plus"
class="btn btn-default">
<i aria-hidden="true" class="fa fa-plus"></i>
</button>
<button
type="button"
ng-click="editor.field.count = editor.field.count - 1"
aria-label="Minus"
class="btn btn-default">
<i aria-hidden="true" class="fa fa-minus"></i>
</button>
</span>
</div>
</div>
<div ng-if="editor.field.scripted">
<div class="form-group">
<label>Script</label>
<textarea required class="form-control text-monospace" ng-model="editor.field.script"></textarea>
</div>
<div class="form-group">
<div ng-bind-html="editor.scriptingWarning" class="hintbox"></div>
</div>
<div class="form-group">
<div ng-bind-html="editor.scriptingInfo" class="hintbox"></div>
</div>
</div>
<div class="form-group">
<button
type="button"
ng-click="editor.cancel()"
aria-label="Cancel"
class="btn btn-primary">
Cancel
</button>
<button
type="button"
ng-if="editor.field.scripted && !editor.creating"
confirm-click="editor.delete()"
confirmation="Are you sure want to delete '{{ editor.field.name }}'? This action is irreversible!"
aria-label="Delete"
class="btn btn-danger">
Delete Field
</button>
<button
ng-disabled="form.$invalid"
type="submit"
aria-label="{{ editor.creating ? 'Create' : 'Update' }} Field"
class="btn btn-success">
{{ editor.creating ? 'Create' : 'Update' }} Field
</button>
</div>
</form>

View file

@ -0,0 +1,141 @@
define(function (require) {
require('components/field_format_editor/field_format_editor');
require('modules')
.get('kibana')
.directive('fieldEditor', function (Private, $sce) {
var _ = require('lodash');
var fieldFormats = Private(require('registry/field_formats'));
var Field = Private(require('components/index_patterns/_field'));
var scriptingInfo = $sce.trustAsHtml(require('text!components/field_editor/scripting_info.html'));
var scriptingWarning = $sce.trustAsHtml(require('text!components/field_editor/scripting_warning.html'));
return {
restrict: 'E',
template: require('text!components/field_editor/field_editor.html'),
scope: {
getIndexPattern: '&indexPattern',
getField: '&field'
},
controllerAs: 'editor',
controller: function ($scope, Notifier, kbnUrl) {
var self = this;
var notify = new Notifier({ location: 'Field Editor' });
self.scriptingInfo = scriptingInfo;
self.scriptingWarning = scriptingWarning;
self.indexPattern = $scope.getIndexPattern();
self.field = shadowCopy($scope.getField());
self.formatParams = self.field.format.params();
// only init on first create
self.creating = !self.indexPattern.fields.byName[self.field.name];
self.selectedFormatId = _.get(self.indexPattern, ['fieldFormatMap', self.field.name, 'type', 'id']);
self.defFormatType = initDefaultFormat();
self.fieldFormatTypes = [self.defFormatType].concat(fieldFormats.byFieldType[self.field.type] || []);
self.cancel = redirectAway;
self.save = function () {
var indexPattern = self.indexPattern;
var fields = indexPattern.fields;
var field = self.field.toActualField();
_.remove(fields, { name: field.name });
fields.push(field);
if (!self.selectedFormatId) {
delete indexPattern.fieldFormatMap[field.name];
} else {
indexPattern.fieldFormatMap[field.name] = self.field.format;
}
return indexPattern.save()
.then(function () {
notify.info('Saved Field "' + self.field.name + '"');
redirectAway();
});
};
self.delete = function () {
var indexPattern = self.indexPattern;
var field = self.field;
_.remove(indexPattern.fields, { name: field.name });
return indexPattern.save()
.then(function () {
notify.info('Deleted Field "' + field.name + '"');
redirectAway();
});
};
$scope.$watch('editor.selectedFormatId', function (cur, prev) {
var format = self.field.format;
var changedFormat = cur !== prev;
var missingFormat = cur && (!format || format.type.id !== cur);
if (!changedFormat || !missingFormat) return;
// reset to the defaults, but make sure it's an object
self.formatParams = _.assign({}, getFieldFormatType().paramDefaults);
});
$scope.$watch('editor.formatParams', function () {
var FieldFormat = getFieldFormatType();
self.field.format = new FieldFormat(self.formatParams);
}, true);
// copy the defined properties of the field to a plain object
// which is mutable, and capture the changed seperately.
function shadowCopy(field) {
var changes = {};
var shadowProps = {
toActualField: {
// bring the shadow copy out of the shadows
value: function toActualField() {
return new Field(self.indexPattern, _.defaults({}, changes, field.$$spec));
}
}
};
Object.getOwnPropertyNames(field).forEach(function (prop) {
var desc = Object.getOwnPropertyDescriptor(field, prop);
shadowProps[prop] = {
enumerable: desc.enumerable,
get: function () {
return _.has(changes, prop) ? changes[prop] : field[prop];
},
set: function (v) {
changes[prop] = v;
}
};
});
return Object.create(null, shadowProps);
}
function redirectAway() {
kbnUrl.changeToRoute(self.indexPattern, self.field.scripted ? 'scriptedFields' : 'indexedFields');
}
function getFieldFormatType() {
if (self.selectedFormatId) return fieldFormats.getType(self.selectedFormatId);
else return fieldFormats.getDefaultType(self.field.type);
}
function initDefaultFormat() {
var def = Object.create(fieldFormats.getDefaultType(self.field.type));
// explicitly set to undefined to prevent inheritting the prototypes id
def.id = undefined;
def.resolvedTitle = def.title;
def.title = '- default - ';
return def;
}
}
};
});
});

View file

@ -0,0 +1,32 @@
<h4>
<i class="fa fa-question-circle text-info"></i> Scripting Help
</h4>
<p>
By default, Elasticsearch scripts use <a target="_window" href="http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_lucene_expressions_scripts">Lucene Expressions <i class="fa-link fa"></i></a>, which is a lot like JavaScript, but limited to basic arithmetic, bitwise and comparison operations. We'll let you do some reading on <a target="_window" href="http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting.html#_lucene_expressions_scripts">Lucene Expressions<i class="fa-link fa"></i></a> To access values in the document use the following format:
</p>
<p><code>doc['some_field'].value</code></p>
<p>
There are a few limitations when using Lucene Expressions:
</p>
<ul>
<li>Only numeric fields may be accessed</li>
<li> Stored fields are not available </li>
<li> If a field is sparse (only some documents contain a value), documents missing the field will have a value of 0 </li>
</ul>
<p>
Here are all the operations available to scripted fields:
</p>
<ul>
<li> Arithmetic operators: + - * / % </li>
<li> Bitwise operators: | & ^ ~ << >> >>> </li>
<li> Boolean operators (including the ternary operator): && || ! ?: </li>
<li> Comparison operators: < <= == >= > </li>
<li> Common mathematic functions: abs ceil exp floor ln log10 logn max min sqrt pow </li>
<li> Trigonometric library functions: acosh acos asinh asin atanh atan atan2 cosh cos sinh sin tanh tan </li>
<li> Distance functions: haversin </li>
<li> Miscellaneous functions: min, max </li>
</ul>

View file

@ -0,0 +1,11 @@
<h4>
<i class="fa fa-warning text-warning"></i> Proceed with caution
</h4>
<p>
Please familiarize yourself with <a target="_window" href="http://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-script-fields.html#search-request-script-fields">script fields <i class="fa-link fa"></i></a> and with <a target="_window" href="http://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-script">scripts in aggregations <i class="fa-link fa"></i></a> before using scripted fields.
</p>
<p>
Scripted fields can be used to display and aggregate calculated values. As such, they can be very slow, and if done incorrectly, can cause Kibana to be unusable. There's no safety net here. If you make a typo, unexpected exceptions will be thrown all over the place!
</p>

View file

@ -0,0 +1,122 @@
define(function (require) {
var _ = require('lodash');
var $ = require('jquery');
require('modules')
.get('app/settings')
.directive('fieldFormatEditor', function (Private, $compile) {
return {
restrict: 'A',
scope: {
getField: '&field',
getFormatParams: '&formatParams'
},
controllerAs: 'editor',
controller: function ($scope) {
var self = this;
// bind the scope values to the controller, down with $scope.values
$scope.editor = this;
$scope.$bind('editor.field', 'getField()', $scope);
$scope.$bind('editor.formatParams', 'getFormatParams()', $scope);
/**
* Read the FieldFormat's editor property and convert it into
* a "pseudoDirective". For clarity I'm reusing the directive def
* object api, but for simplicity not implementing the entire thing.
*
* possible configs:
* string:
* - used as an angular template
* directive def object, with support for the following opts:
* - template
* - compile or link
* - scope (creates isolate, reads from parent scope, not attributes)
* - controller
* - controllerAs
*
* @param {angular.element} $el - template
* @param {object} directiveDef - the directive definition object
* @return {undefined}
*/
$scope.$watch('editor.field.format.type', function (FieldFormat) {
var opts = FieldFormat && FieldFormat.editor;
if (!opts) {
delete self.$$pseudoDirective;
return;
}
if (typeof opts === 'string') {
self.$$pseudoDirective = {
template: opts
};
return;
}
self.$$pseudoDirective = {
template: opts.template,
compile: opts.compile || function () {
return opts.link;
},
scope: opts.scope || false,
controller: opts.controller,
controllerAs: opts.controllerAs
};
});
},
link: function ($scope, $el) {
var scopesToTeardown = [];
function setupScope(opts) {
if (typeof opts !== 'object') {
return scopesToTeardown[scopesToTeardown.push($scope.$new()) - 1];
}
var isolate = scopesToTeardown[scopesToTeardown.push($scope.$new(true)) - 1];
_.forOwn(opts, function (from, to) {
isolate.$bind(to, from, $scope);
});
return isolate;
}
$scope.$watch('editor.$$pseudoDirective', function (directive) {
$el.empty();
_.invoke(scopesToTeardown.splice(0), '$destroy');
if (!directive) return $el.hide();
else $el.show();
var askedForChild = !!directive.scope;
var reuseScope = !askedForChild && !directive.controller;
var $formatEditor = $('<div>').html(directive.template);
var $formatEditorScope = reuseScope ? $scope : setupScope(directive.scope);
if (directive.controller) {
// bind the controller to the injected element
var cntrlAs = (directive.controllerAs ? ' as ' + directive.controllerAs : '');
$formatEditorScope.Controller = directive.controller;
$formatEditor.attr('ng-controller', 'Controller' + cntrlAs);
}
var attrs = {};
var linkFns = directive.compile && directive.compile($el, attrs);
if (!linkFns || _.isFunction(linkFns)) {
linkFns = {
pre: _.noop,
post: linkFns || _.noop
};
}
$el.html($formatEditor);
linkFns.pre($formatEditorScope, $formatEditor, attrs);
$compile($formatEditor)($formatEditorScope);
linkFns.post($formatEditorScope, $formatEditor, attrs);
});
}
};
});
});

View file

@ -0,0 +1,21 @@
<div class="form-group">
<small class="pull-right">
<a ng-href="https://adamwdraper.github.io/Numeral-js/" target="_blank">
Docs <i class="fa fa-link"></i>
</a>
</small>
<label>
Numeral.js format pattern
<small>
(Default: "{{ editor.field.format.type.paramDefaults.pattern }}")
</small>
</label>
</div>
<field-format-editor-pattern
ng-model="editor.formatParams.pattern"
placeholder="editor.field.format.type.paramDefaults.pattern"
inputs="cntrl.sampleInputs">
</field-format-editor-pattern>

View file

@ -0,0 +1,12 @@
define(function (require) {
require('components/field_format_editor/pattern/pattern');
require('modules')
.get('kibana')
.directive('fieldEditorNumeral', function () {
return {
restrict: 'E',
template: require('text!components/field_format_editor/numeral/numeral.html')
};
});
});

View file

@ -0,0 +1,11 @@
<div class="form-group">
<input
ng-model="model"
placeholder="{{ placeholder }}"
class="form-control">
</div>
<field-format-editor-samples
ng-model="model"
inputs="inputs">
</field-format-editor-samples>

View file

@ -0,0 +1,26 @@
define(function (require) {
require('components/field_format_editor/samples/samples');
require('modules')
.get('kibana')
.directive('fieldFormatEditorPattern', function () {
return {
restrict: 'E',
template: require('text!components/field_format_editor/pattern/pattern.html'),
require: ['ngModel', '^fieldEditor'],
scope: true,
link: function ($scope, $el, attrs, cntrls) {
var ngModelCntrl = cntrls[0];
$scope.$bind('inputs', attrs.inputs);
$scope.$bind('placeholder', attrs.placeholder);
// bind our local model with the outside ngModel
$scope.$watch('model', ngModelCntrl.$setViewValue);
ngModelCntrl.$render = function () {
$scope.model = ngModelCntrl.$viewValue;
};
}
};
});
});

View file

@ -0,0 +1,32 @@
<div class="form-group hintbox" ng-if="error">
<h4 class="hintbox-heading">
<i class="fa fa-danger text-danger"></i> Format error
</h4>
<p>
An error occured while trying to use this format configuration.
</p>
<pre>{{ error.message }}</pre>
</div>
<div class="form-group" ng-if="samples">
<hr>
<label>Samples</label>
<table class="table">
<thead>
<tr>
<th>
Input
</th>
<th>
Formatted
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="sample in samples">
<td ng-bind="sample[0]"></td>
<td ng-bind-html="sample[1]"></td>
</tr>
</tbody>
</table>
</div>

View file

@ -0,0 +1,45 @@
define(function (require) {
var _ = require('lodash');
require('modules')
.get('kibana')
.directive('fieldFormatEditorSamples', function ($sce, Promise) {
return {
restrict: 'E',
template: require('text!components/field_format_editor/samples/samples.html'),
require: ['?^ngModel', '^fieldEditor'],
scope: true,
link: function ($scope, $el, attrs, cntrls) {
var ngModelCntrl = cntrls[0];
$scope.samples = null;
$scope.$bind('inputs', attrs.inputs);
$scope.$watchMulti([
'editor.field.format',
'[]inputs'
], function () {
$scope.samples = null;
var field = $scope.editor.field;
if (!field || !field.format) {
return;
}
Promise.try(function () {
var converter = field.format.getConverterFor('html');
$scope.samples = _.map($scope.inputs, function (input) {
return [input, $sce.trustAsHtml(converter(input))];
});
})
.then(validity, validity);
});
function validity(err) {
$scope.error = err;
ngModelCntrl && ngModelCntrl.$setValidity('patternExecutes', !err);
}
}
};
});
});

View file

@ -12,21 +12,25 @@
<div class="bar" ng-show="filters.length">
<div class="filter" ng-class="{ negate: filter.meta.negate, disabled: filter.meta.disabled }" ng-repeat="filter in filters">
<div class="filter-description">
<!--<span><i class="fa" ng-class="{'fa-plus': !filter.meta.negate, 'fa-minus': filter.meta.negate}"></i></span>-->
<span ng-if="filter.$state.store == 'globalState'"><i class="fa fa-fw fa-thumb-tack pinned"></i></span>
<span>{{ filter.meta.key }}:</span>
<span>"{{ filter.meta.value }}"</span>
</div>
<div class="filter-actions">
<a class="action filter-toggle" ng-click="toggleFilter(filter)">
<i ng-show="filter.meta.disabled" class="fa fa-square-o"></i>
<i ng-hide="filter.meta.disabled" class="fa fa-check-square-o"></i>
<i ng-show="filter.meta.disabled" class="fa fa-fw fa-square-o disabled"></i>
<i ng-hide="filter.meta.disabled" class="fa fa-fw fa-check-square-o enabled"></i>
</a>
<a class="action filter-pin" ng-click="pinFilter(filter)">
<i ng-show="filter.$state.store == 'globalState'" class="fa fa-fw fa-thumb-tack pinned"></i>
<i ng-hide="filter.$state.store == 'globalState'" class="fa fa-fw fa-thumb-tack fa-rotate-270 unpinned"></i>
</a>
<a class="action filter-invert" ng-click="invertFilter(filter)">
<i ng-show="filter.meta.negate" class="fa fa-search-plus"></i>
<i ng-hide="filter.meta.negate" class="fa fa-search-minus"></i>
<i ng-show="filter.meta.negate" class="fa fa-fw fa-search-plus negative"></i>
<i ng-hide="filter.meta.negate" class="fa fa-fw fa-search-minus positive"></i>
</a>
<a class="action filter-remove" ng-click="removeFilter(filter)">
<i class="fa fa-trash"></i>
<i class="fa fa-fw fa-trash"></i>
</a>
</div>
</div>
@ -52,6 +56,12 @@
<div class="filter-link">
<div class="filter-description"><a ng-click="toggleAll(true)">Disable</a></div>
</div>
<div class="filter-link">
<div class="filter-description"><a ng-click="pinAll(true)">Pin</a></div>
</div>
<div class="filter-link">
<div class="filter-description"><a ng-click="pinAll(false)">Unpin</a></div>
</div>
<div class="filter-link">
<div class="filter-description"><a ng-click="invertAll()">Invert</a></div>
</div>

View file

@ -4,34 +4,43 @@ define(function (require) {
var template = require('text!components/filter_bar/filter_bar.html');
var moment = require('moment');
var toggleFilter = require('components/filter_bar/lib/toggleFilter');
var toggleAll = require('components/filter_bar/lib/toggleAll');
var invertFilter = require('components/filter_bar/lib/invertFilter');
var invertAll = require('components/filter_bar/lib/invertAll');
var removeFilter = require('components/filter_bar/lib/removeFilter');
var removeAll = require('components/filter_bar/lib/removeAll');
var filterAppliedAndUnwrap = require('components/filter_bar/lib/filterAppliedAndUnwrap');
module.directive('filterBar', function (Private, Promise) {
module.directive('filterBar', function (Private, Promise, getAppState) {
var mapAndFlattenFilters = Private(require('components/filter_bar/lib/mapAndFlattenFilters'));
var mapFlattenAndWrapFilters = Private(require('components/filter_bar/lib/mapFlattenAndWrapFilters'));
var extractTimeFilter = Private(require('components/filter_bar/lib/extractTimeFilter'));
var filterOutTimeBasedFilter = Private(require('components/filter_bar/lib/filterOutTimeBasedFilter'));
var filterAppliedAndUnwrap = require('components/filter_bar/lib/filterAppliedAndUnwrap');
var changeTimeFilter = Private(require('components/filter_bar/lib/changeTimeFilter'));
var queryFilter = Private(require('components/filter_bar/query_filter'));
return {
restrict: 'E',
template: template,
scope: {
state: '='
},
scope: {},
link: function ($scope, $el, attrs) {
// bind query filter actions to the scope
[
'addFilters',
'toggleFilter',
'toggleAll',
'pinFilter',
'pinAll',
'invertFilter',
'invertAll',
'removeFilter',
'removeAll'
].forEach(function (method) {
$scope[method] = queryFilter[method];
});
$scope.state = getAppState();
$scope.applyFilters = function (filters) {
var newFilters = filterAppliedAndUnwrap(filters);
$scope.state.filters = _.union($scope.state.filters, newFilters);
// add new filters
$scope.addFilters(filterAppliedAndUnwrap(filters));
$scope.newFilters = [];
// change time filter
if ($scope.changeTimeFilter && $scope.changeTimeFilter.meta && $scope.changeTimeFilter.meta.apply) {
changeTimeFilter($scope.changeTimeFilter);
}
@ -42,10 +51,20 @@ define(function (require) {
$scope.changeTimeFilter = null;
};
// update the scope filter list on filter changes
$scope.$listen(queryFilter, 'update', function () {
updateFilters();
});
// when appState changes, update scope's state
$scope.$watch(getAppState, function (appState) {
$scope.state = appState;
});
$scope.$watch('state.$newFilters', function (filters) {
if (!filters) return;
// If the filters is not undefined and the length is greater then
// If filters is not undefined and the length is greater than
// one we need to set the newFilters attribute and allow the
// users to decide what they want to apply.
if (filters.length > 1) {
@ -72,24 +91,22 @@ define(function (require) {
return filters;
})
.then(filterOutTimeBasedFilter)
.then(function (filters) {
$scope.state.filters = _.union($scope.state.filters, filters);
});
.then($scope.addFilters);
}
});
$scope.$watch('state.filters', function (filters) {
function updateFilters() {
var filters = queryFilter.getFilters();
mapAndFlattenFilters(filters).then(function (results) {
$scope.filters = results;
// used to display the current filters in the state
$scope.filters = _.sortBy(results, function (filter) {
return !filter.meta.pinned;
});
$scope.$emit('filterbar:updated');
});
});
}
$scope.toggleFilter = toggleFilter($scope);
$scope.toggleAll = toggleAll($scope);
$scope.invertFilter = invertFilter($scope);
$scope.invertAll = invertAll($scope);
$scope.removeFilter = removeFilter($scope);
$scope.removeAll = removeAll($scope);
updateFilters();
}
};
});

View file

@ -99,8 +99,8 @@ filter-bar .bar {
}
> .filter-actions {
font-size: 1.3em;
line-height: 1.1em;
font-size: 1.1em;
line-height: 1.4em;
position: absolute;
padding: 4px 8px;
top: 0;
@ -109,18 +109,22 @@ filter-bar .bar {
display: none;
text-align: center;
white-space: nowrap;
}
> .filter-actions > .action {
border-right: 1px solid rgba(255, 255, 255, 0.4);
padding-right: 6px;
margin-right: 8px;
}
> * {
border-right: 1px solid rgba(255, 255, 255, 0.4);
padding-right: 0;
margin-right: 5px;
> .filter-actions > .action:last-child {
border-right: 0;
padding-right: 0;
margin-right: 0;
&:last-child {
border-right: 0;
padding-right: 0;
margin-right: 0;
}
.unpinned {
.opacity(.7);
}
}
}
&.negate {

View file

@ -29,8 +29,7 @@ define(function (require) {
if (!filters.length) return;
filters = uniqFilters(filters);
filters = dedupFilters($state.filters, filters);
filters = dedupFilters($state.filters, uniqFilters(filters));
// We need to add a bunch of filter deduping here.
$state.$newFilters = filters;
}

View file

@ -0,0 +1,33 @@
define(function (require) {
var _ = require('lodash');
var angular = require('angular');
var excludedAttributes;
var comparators;
/**
* Compare two filters to see if they match
* @param {object} first The first filter to compare
* @param {object} second The second filter to compare
* @param {object} comparatorOptions Parameters to use for comparison
* @returns {bool} Filters are the same
*/
return function (first, second, comparatorOptions) {
excludedAttributes = ['$$hashKey', 'meta'];
comparators = _.defaults(comparatorOptions || {}, {
state: false,
negate: false,
disabled: false,
});
if (!comparators.state) excludedAttributes.push('$state');
return _.isEqual(mapFilter(first), mapFilter(second));
};
function mapFilter(filter) {
var cleaned = _.omit(filter, excludedAttributes);
if (comparators.negate) cleaned.negate = filter.meta && !!filter.meta.negate;
if (comparators.disabled) cleaned.disabled = filter.meta && !!filter.meta.disabled;
return cleaned;
}
});

View file

@ -1,12 +1,22 @@
define(function (require) {
var _ = require('lodash');
var excludedAttributes = ['meta', '$$hashKey'];
return function (existing, filters) {
filters = _.filter(filters, function (item) {
return !_.find(existing, function (existingFilter) {
return _.isEqual(_.omit(existingFilter, excludedAttributes), _.omit(item, excludedAttributes));
var angular = require('angular');
var compareFilters = require('components/filter_bar/lib/compareFilters');
/**
* Combine 2 filter collections, removing duplicates
* @param {object} existing The filters to compare to
* @param {object} filters The filters being added
* @param {object} comparatorOptions Parameters to use for comparison
* @returns {object} An array of filters that were not in existing
*/
return function (existingFilters, filters, comparatorOptions) {
if (!_.isArray(filters)) filters = [filters];
return _.filter(filters, function (filter) {
return !_.find(existingFilters, function (existingFilter) {
return compareFilters(existingFilter, filter, comparatorOptions);
});
});
return filters;
};
});

View file

@ -1,17 +0,0 @@
define(function (require) {
return function ($scope) {
var invertFilter = require('components/filter_bar/lib/invertFilter')($scope);
/**
* Removes all filters
* @returns {void}
*/
return function () {
$scope.filters.forEach(function (filter) {
invertFilter(filter);
});
$scope.state.filters = $scope.filters;
};
};
});

View file

@ -1,19 +0,0 @@
define(function (require) {
var _ = require('lodash');
return function ($scope) {
/**
* Inverts the nagate value on the filter
* @param {object} filter The filter to toggle
& @param {boolean} force disabled true/false
* @returns {void}
*/
return function (filter) {
// Toggle the negate meta state
filter.meta.negate = !filter.meta.negate;
// Save the filters back to the searchSource
$scope.state.filters = $scope.filters;
return filter;
};
};
});

View file

@ -1,5 +1,4 @@
define(function (require) {
var _ = require('lodash');
define(function () {
return function mapScriptProvider(Promise, courier) {
return function (filter) {
var key, value, field;

View file

@ -5,8 +5,13 @@ define(function (require) {
return _.deepGet(filter, 'meta.disabled');
};
return function (newFitlers, oldFilters) {
var diff = _.difference(oldFilters, newFitlers);
return (diff.length && _.every(diff, pluckDisabled));
/**
* Checks to see if only disabled filters have been changed
* @returns {bool} Only disabled filters
*/
return function (newFilters, oldFilters) {
return _.every(newFilters.concat(oldFilters), function (newFilter) {
return pluckDisabled(newFilter);
});
};
});

View file

@ -0,0 +1,19 @@
define(function (require) {
var _ = require('lodash');
var makeComparable = function (filter) {
return _.omit(filter, ['$state', '$$hashKey']);
};
/**
* Checks to see if only disabled filters have been changed
* @returns {bool} Only disabled filters
*/
return function (newFilters, oldFilters) {
var comparableOldFilters = _.map(oldFilters, makeComparable);
return _.every(newFilters, function (newFilter, i) {
var match = _.find(comparableOldFilters, makeComparable(newFilter));
return !!match;
});
};
});

View file

@ -1,11 +0,0 @@
define(function (require) {
return function ($scope) {
/**
* Removes all filters
* @returns {void}
*/
return function () {
$scope.state.filters = [];
};
};
});

View file

@ -1,20 +0,0 @@
define(function (require) {
var _ = require('lodash');
return function ($scope) {
/**
* Removes the filter from the searchSource
* @param {object} filter The filter to remove
* @returns {void}
*/
return function (invalidFilter) {
// Remove the filter from the the scope $filters and map it back
// to the original format to save in searchSource
$scope.state.filters = _($scope.filters)
.filter(function (filter) {
return filter !== invalidFilter;
})
.value();
};
};
});

View file

@ -1,19 +0,0 @@
define(function (require) {
var _ = require('lodash');
return function ($scope) {
var toggleFilter = require('components/filter_bar/lib/toggleFilter')($scope);
/**
* Disables all filters
* @params {boolean} force disable/enable all filters
* @returns {void}
*/
return function (force) {
$scope.filters.forEach(function (filter) {
toggleFilter(filter, force);
});
$scope.state.filters = $scope.filters;
};
};
});

View file

@ -1,20 +0,0 @@
define(function (require) {
var _ = require('lodash');
return function ($scope) {
/**
* Toggles the filter between enabled/disabled.
* @param {object} filter The filter to toggle
& @param {boolean} force disabled true/false
* @returns {void}
*/
return function (filter, force) {
// Toggle the disabled flag
var disabled = _.isUndefined(force) ? !filter.meta.disabled : force;
filter.meta.disabled = disabled;
// Save the filters back to the searchSource
$scope.state.filters = $scope.filters;
return filter;
};
};
});

View file

@ -1,10 +1,16 @@
define(function (require) {
var _ = require('lodash');
var dedupFilters = require('components/filter_bar/lib/dedupFilters');
return function (filters) {
/**
* Remove duplicate filters from an array of filters
* @param {array} filters The filters to remove duplicates from
* @returns {object} The original filters array with duplicates removed
*/
return function (filters, comparatorOptions) {
var results = [];
_.each(filters, function (filter) {
results = _.union(results, dedupFilters(results, [filter]));
results = _.union(results, dedupFilters(results, [filter], comparatorOptions));
});
return results;
};

View file

@ -1,24 +0,0 @@
define(function (require) {
return function watchFiltersProvider(Private, Promise, Notifier) {
var _ = require('lodash');
var onlyDisabled = require('components/filter_bar/lib/onlyDisabled');
var EventEmitter = Private(require('factories/events'));
var notify = new Notifier({ location: 'Fitler Bar' });
return function ($scope, handlers) {
var emitter = new EventEmitter();
$scope.$watch('state.filters', function (newFilters, oldFilters) {
if (newFilters === oldFilters) return;
return emitter.emit('update')
.then(function () {
if (onlyDisabled(newFilters, oldFilters)) return;
return emitter.emit('fetch');
});
});
return emitter;
};
};
});

View file

@ -0,0 +1,329 @@
define(function (require) {
var _ = require('lodash');
return function (Private, $rootScope, getAppState, globalState) {
var EventEmitter = Private(require('factories/events'));
var onlyDisabled = require('components/filter_bar/lib/onlyDisabled');
var onlyStateChanged = require('components/filter_bar/lib/onlyStateChanged');
var uniqFilters = require('components/filter_bar/lib/uniqFilters');
var compareFilters = require('components/filter_bar/lib/compareFilters');
var queryFilter = new EventEmitter();
queryFilter.getFilters = function () {
var compareOptions = { disabled: true, negate: true };
var appFilters = queryFilter.getAppFilters();
var globalFilters = queryFilter.getGlobalFilters();
return uniqFilters(globalFilters.concat(appFilters), compareOptions);
};
queryFilter.getAppFilters = function () {
var appState = getAppState();
if (!appState || !appState.filters) return [];
return (appState.filters) ? _.map(appState.filters, appendStoreType('appState')) : [];
};
queryFilter.getGlobalFilters = function () {
if (!globalState.filters) return [];
return _.map(globalState.filters, appendStoreType('globalState'));
};
/**
* Adds new filters to the scope and state
* @param {object|array} fitlers Filter(s) to add
* @param {bool} global Should be added to global state
* @returns {object} Resulting new filter list
*/
queryFilter.addFilters = function (filters, global) {
var appState = getAppState();
var state = (global) ? globalState : appState;
if (!_.isArray(filters)) {
filters = [filters];
}
if (global) {
// simply concat global filters, they will be deduped
globalState.filters = globalState.filters.concat(filters);
} else if (appState) {
if (!appState.filters) appState.filters = [];
var mergeOptions = { disabled: true, negate: false };
var appFilters = appState.filters.concat(filters);
var merged = mergeAndMutateFilters(globalState.filters, appFilters, mergeOptions);
globalState.filters = merged[0];
appState.filters = merged[1];
}
return saveState();
};
/**
* Removes the filter from the proper state
* @param {object} matchFilter The filter to remove
* @returns {object} Resulting new filter list
*/
queryFilter.removeFilter = function (matchFilter) {
var state = getStateByFilter(matchFilter);
if (!state) return;
_.pull(state.filters, matchFilter);
return saveState();
};
/**
* Removes all filters
* @returns {object} Resulting new filter list
*/
queryFilter.removeAll = function () {
var appState = getAppState();
appState.filters = [];
globalState.filters = [];
return saveState();
};
/**
* Toggles the filter between enabled/disabled.
* @param {object} filter The filter to toggle
& @param {boolean} force Disabled true/false
* @returns {object} updated filter
*/
queryFilter.toggleFilter = function (filter, force) {
// Toggle the disabled flag
var disabled = _.isUndefined(force) ? !filter.meta.disabled : !!force;
filter.meta.disabled = disabled;
// Save the filters back to the searchSource
saveState();
return filter;
};
/**
* Disables all filters
* @params {boolean} force Disable/enable all filters
* @returns {object} Resulting updated filter list
*/
queryFilter.toggleAll = function (force) {
function doToggle(filter) {
queryFilter.toggleFilter(filter, force);
}
executeOnFilters(doToggle);
return queryFilter.getFilters();
};
/**
* Inverts the nagate value on the filter
* @param {object} filter The filter to toggle
* @returns {object} updated filter
*/
queryFilter.invertFilter = function (filter) {
// Toggle the negate meta state
filter.meta.negate = !filter.meta.negate;
saveState();
return filter;
};
/**
* Inverts all filters
* @returns {object} Resulting updated filter list
*/
queryFilter.invertAll = function () {
executeOnFilters(queryFilter.invertFilter);
return queryFilter.getFilters();
};
/**
* Pins the filter to the global state
* @param {object} filter The filter to pin
* @param {boolean} force pinned state
* @returns {object} filter passed in
*/
queryFilter.pinFilter = function (filter, force) {
var appState = getAppState();
if (!appState) return filter;
// ensure that both states have a filters property
if (!_.isArray(globalState.filters)) globalState.filters = [];
if (!_.isArray(appState.filters)) appState.filters = [];
var appIndex = _.indexOf(appState.filters, filter);
var globalIndex = _.indexOf(globalState.filters, filter);
if (appIndex === -1 && globalIndex === -1) return;
if (appIndex !== -1 && force !== false) {
appState.filters.splice(appIndex, 1);
globalState.filters.push(filter);
} else if (globalIndex !== -1 && force !== true) {
globalState.filters.splice(globalIndex, 1);
appState.filters.push(filter);
}
saveState();
return filter;
};
/**
* Pins all filters
* @params {boolean} force Pin/Unpin all filters
* @returns {object} Resulting updated filter list
*/
queryFilter.pinAll = function (force) {
function pin(filter) {
queryFilter.pinFilter(filter, force);
}
executeOnFilters(pin);
return queryFilter.getFilters();
};
initWatchers();
return queryFilter;
/**
* Saves both app and global states, ensuring filters are persisted
* @returns {object} Resulting filter list, app and global combined
*/
function saveState() {
var appState = getAppState();
if (appState) appState.save();
globalState.save();
return queryFilter.getFilters();
}
function appendStoreType(type) {
return function (filter) {
filter.$state = {
store: type
};
return filter;
};
}
// get state (app or global) or the filter passed in
function getStateByFilter(filter) {
var appState = getAppState();
if (appState) {
var appIndex = _.indexOf(appState.filters, filter);
if (appIndex !== -1) return appState;
}
var globalIndex = _.indexOf(globalState.filters, filter);
if (globalIndex !== -1) return globalState;
return false;
}
// helper to run a function on all filters in all states
function executeOnFilters(fn) {
var appState = getAppState();
var appFilters;
if (appState && appState.filters) {
appFilters = appState.filters;
} else {
appFilters = [];
}
globalState.filters.concat(appFilters).forEach(fn);
}
function mergeAndMutateFilters(globalFilters, appFilters, compareOptions) {
appFilters = appFilters || [];
globalFilters = globalFilters || [];
compareOptions = _.defaults(compareOptions || {}, { disabled: true, negate: true });
// existing globalFilters should be mutated by appFilters
appFilters = _.filter(appFilters, function (filter) {
var match = _.find(globalFilters, function (globalFilter) {
return compareFilters(globalFilter, filter, compareOptions);
});
// if the filter remains, it doesn't match any filters in global state
if (!match) return true;
// filter matches a filter in globalFilters, mutate existing global filter
_.assign(match.meta, filter.meta);
return false;
});
appFilters = uniqFilters(appFilters, { disabled: true });
globalFilters = uniqFilters(globalFilters, { disabled: true });
return [globalFilters, appFilters];
}
/**
* Initializes state watchers that use the event emitter
* @returns {void}
*/
function initWatchers() {
var removeAppStateWatchers;
$rootScope.$watch(getAppState, function () {
removeAppStateWatchers && removeAppStateWatchers();
removeAppStateWatchers = initAppStateWatchers();
});
function initAppStateWatchers() {
// multi watch on the app and global states
var stateWatchers = [{
fn: $rootScope.$watch,
deep: true,
get: queryFilter.getGlobalFilters
}, {
fn: $rootScope.$watch,
deep: true,
get: queryFilter.getAppFilters
}];
// when states change, use event emitter to trigger updates and fetches
return $rootScope.$watchMulti(stateWatchers, function (next, prev) {
var doUpdate = false;
var doFetch = false;
var newFilters = [];
var oldFilters = [];
// iterate over each state type, checking for changes
stateWatchers.forEach(function (watcher, i) {
var nextVal = next[i];
var prevVal = prev[i];
newFilters = newFilters.concat(nextVal);
oldFilters = oldFilters.concat(prevVal);
// no update or fetch if there was no change
if (nextVal === prevVal) return;
if (nextVal) doUpdate = true;
// don't trigger fetch when only disabled filters
if (!onlyDisabled(nextVal, prevVal)) doFetch = true;
});
// make sure change wasn't only a state move
if (doFetch && newFilters.length === oldFilters.length) {
if (onlyStateChanged(newFilters, oldFilters)) doFetch = false;
}
// reconcile filter in global and app states
var filters = mergeAndMutateFilters(next[0], next[1]);
globalState.filters = filters[0];
var appState = getAppState();
if (appState) {
appState.filters = filters[1];
}
saveState();
if (!doUpdate) return;
return queryFilter.emit('update')
.then(function () {
if (!doFetch) return;
return queryFilter.emit('fetch');
});
});
}
}
};
});

View file

@ -1,73 +1,79 @@
// Adds a filter to a passed state
define(function (require) {
var _ = require('lodash');
var self = this;
return function (Private) {
var _ = require('lodash');
var queryFilter = Private(require('components/filter_bar/query_filter'));
var filterManager = {};
this.init = function ($state) {
self.$state = $state;
};
filterManager.add = function (field, values, operation, index) {
values = _.isArray(values) ? values : [values];
var fieldName = _.isObject(field) ? field.name : field;
var filters = _.flatten([queryFilter.getAppFilters()], true);
var newFilters = [];
this.add = function (field, values, operation, index) {
var negate = (operation === '-');
values = _.isArray(values) ? values : [values];
// Have we been passed a simple name or an actual field object?
var fieldName = _.isObject(field) ? field.name : field;
var negate = operation === '-';
var filters = _.flatten([self.$state.filters], true);
// TODO: On array fields, negating does not negate the combination, rather all terms
_.each(values, function (value) {
var existing = _.find(filters, function (filter) {
if (!filter) return;
if (fieldName === '_exists_' && filter.exists) {
return filter.exists.field === value;
}
if (filter.query) {
return filter.query.match[fieldName] && filter.query.match[fieldName].query === value;
}
});
if (existing) {
if (existing.meta.negate !== negate) {
existing.meta.negate = negate;
}
return;
}
switch (fieldName) {
case '_exists_':
filters.push({ meta: { negate: negate, index: index }, exists: { field: value } });
break;
default:
// TODO: On array fields, negating does not negate the combination, rather all terms
_.each(values, function (value) {
var filter;
if (field.scripted) {
var existing = _.find(filters, function (filter) {
if (!filter) return;
if (fieldName === '_exists_' && filter.exists) {
return filter.exists.field === value;
}
if (filter.query) {
return filter.query.match[fieldName] && filter.query.match[fieldName].query === value;
}
});
if (existing) {
existing.meta.disabled = false;
if (existing.meta.negate !== negate) {
queryFilter.invertFilter(existing);
}
return;
}
switch (fieldName) {
case '_exists_':
filter = {
meta: { negate: negate, index: index, field: fieldName },
script: {
script: '(' + field.script + ') == value',
lang: field.lang,
params: {
value: value
}
meta: {
negate: negate,
index: index
},
exists: {
field: value
}
};
} else {
filter = { meta: { negate: negate, index: index }, query: { match: {} } };
filter.query.match[fieldName] = { query: value, type: 'phrase' };
break;
default:
if (field.scripted) {
filter = {
meta: { negate: negate, index: index, field: fieldName },
script: {
script: '(' + field.script + ') == value',
lang: field.lang,
params: {
value: value
}
}
};
} else {
filter = { meta: { negate: negate, index: index }, query: { match: {} } };
filter.query.match[fieldName] = { query: value, type: 'phrase' };
}
break;
}
filters.push(filter);
break;
}
});
newFilters.push(filter);
});
self.$state.filters = filters;
queryFilter.addFilters(newFilters);
};
return filterManager;
};
return this;
});

View file

@ -9,8 +9,6 @@ define(function (require) {
return function (formatted, highlight) {
if (typeof formatted === 'object') formatted = angular.toJson(formatted);
formatted = _.escape(formatted);
_.each(highlight, function (section) {
section = _.escape(section);
@ -31,4 +29,4 @@ define(function (require) {
return formatted;
};
});
});
});

View file

@ -0,0 +1,79 @@
define(function (require) {
return function FieldObjectProvider(Private, shortDotsFilter, $rootScope, Notifier) {
var notify = new Notifier({ location: 'IndexPattern Field' });
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
var fieldTypes = Private(require('components/index_patterns/_field_types'));
var fieldFormats = Private(require('registry/field_formats'));
var ObjDefine = require('utils/obj_define');
function Field(indexPattern, spec) {
// unwrap old instances of Field
if (spec instanceof Field) spec = spec.$$spec;
// constuct this object using ObjDefine class, which
// extends the Field.prototype but gets it's properties
// defined using the logic below
var obj = new ObjDefine(spec, Field.prototype);
if (spec.name === '_source') {
spec.type = '_source';
}
// find the type for this field, fallback to unkown type
var type = fieldTypes.byName[spec.type];
if (spec.type && !type) {
notify.error(
'Unkown field type "' + spec.type + '"' +
' for field "' + spec.name + '"' +
' in indexPattern "' + indexPattern.id + '"'
);
}
if (!type) type = fieldTypes.byName.unknown;
var format = spec.format;
if (!format || !(format instanceof FieldFormat)) {
format = indexPattern.fieldFormatMap[spec.name] || fieldFormats.getDefaultInstance(spec.type);
}
var indexed = !!spec.indexed;
var scripted = !!spec.scripted;
var sortable = indexed && type.sortable;
var bucketable = indexed || scripted;
var filterable = spec.name === '_id' || scripted || (indexed && type.filterable);
obj.fact('name');
obj.fact('type');
obj.writ('count', spec.count || 0);
// scripted objs
obj.fact('scripted', scripted);
obj.writ('script', scripted ? spec.script : null);
obj.writ('lang', scripted ? (spec.lang || 'expression') : null);
// mapping info
obj.fact('indexed', indexed);
obj.fact('analyzed', !!spec.analyzed);
obj.fact('doc_values', !!spec.doc_values);
// usage flags, read-only and won't be saved
obj.comp('format', format);
obj.comp('sortable', sortable);
obj.comp('bucketable', bucketable);
obj.comp('filterable', filterable);
// computed values
obj.comp('indexPattern', indexPattern);
obj.comp('displayName', shortDotsFilter(spec.name));
obj.comp('$$spec', spec);
return obj.create();
}
Field.prototype.routes = {
edit: '/settings/indices/{{indexPattern.id}}/field/{{name}}'
};
return Field;
};
});

View file

@ -0,0 +1,99 @@
define(function (require) {
return function FieldFormatClassProvider(config, $rootScope, Private) {
var _ = require('lodash');
var contentTypes = Private(require('components/index_patterns/_field_format/contentTypes'));
function FieldFormat(params) {
var self = this;
// give the constructor a more appropriate name
self.type = self.constructor;
// keep the params and defaults seperate
self._params = params || {};
self._paramDefaults = self.type.paramDefaults || {};
// one content type, so assume text
if (_.isFunction(self._convert)) {
self._convert = { text: self._convert };
}
contentTypes.setup(self);
}
/**
* Convert a raw value to a formated string
* @param {any} value
* @param {string} [contentType=text] - optional content type, the only two contentTypes
* currently supported are "html" and "text", which helps
* formatters adjust to different contexts
* @return {string} - the formatted string, which is assumed to be html, safe for
* injecting into the DOM or a DOM attribute
*/
FieldFormat.prototype.convert = function (value, contentType) {
return this.getConverterFor(contentType)(value);
};
/**
* Get a convert function that is bound to a specific contentType
* @param {string} [contentType=text]
* @return {function} - a bound converter function
*/
FieldFormat.prototype.getConverterFor = function (contentType) {
return this._convert[contentType || 'text'];
};
/**
* Get the value of a param. This value may be a default value.
*
* @param {string} name - the param name to fetch
* @return {any}
*/
FieldFormat.prototype.param = function (name) {
var val = this._params[name];
if (val || val === false || val === 0) {
// truthy, false, or 0 are fine
// '', NaN, null, undefined, etc are not
return val;
}
return this._paramDefaults[name];
};
/**
* Get all of the params in a single object
* @return {object}
*/
FieldFormat.prototype.params = function () {
return _.cloneDeep(_.defaults({}, this._params, this._paramDefaults));
};
/**
* serialize this format to a simple POJO, with only the params
* that are not default
*
* @return {object}
*/
FieldFormat.prototype.toJSON = function () {
var type = this.type;
var defaults = this._paramDefaults;
var params = _.transform(this._params, function (uniqParams, val, param) {
if (val !== defaults[param]) {
uniqParams[param] = val;
}
}, {});
if (!_.size(params)) {
params = undefined;
}
return {
id: type.id,
params: params
};
};
return FieldFormat;
};
});

View file

@ -0,0 +1,65 @@
define(function (require) {
return function contentTypesProvider(highlightFilter) {
var _ = require('lodash');
var angular = require('angular');
var types = {
html: function (format, convert) {
return function recurse(value, field, hit) {
if (!value || typeof value.map !== 'function') {
return convert.call(format, value, field, hit);
}
var subVals = value.map(function (v) {
return recurse(v, field, hit);
});
var useMultiLine = subVals.some(function (sub) {
return sub.indexOf('\n') > -1;
});
return subVals.join(',' + (useMultiLine ? '\n' : ' '));
};
},
text: function (format, convert) {
return function recurse(value) {
if (!value || typeof value.map !== 'function') {
return convert.call(format, value);
}
// format a list of values. In text contexts we just use JSON encoding
return angular.toJson(value.map(recurse), true);
};
}
};
function fallbackText(value) {
return _.asPrettyString(value);
}
function fallbackHtml(value, field, hit) {
var formatted = _.escape(this.convert(value, 'text'));
if (!hit || !hit.highlight || !hit.highlight[field.name]) {
return formatted;
} else {
return highlightFilter(formatted, hit.highlight[field.name]);
}
}
function setup(format) {
var src = format._convert || {};
var converters = format._convert = {};
converters.text = types.text(format, src.text || fallbackText);
converters.html = types.html(format, src.html || fallbackHtml);
return format._convert;
}
return {
types: types,
setup: setup
};
};
});

View file

@ -1,175 +0,0 @@
/**
### Formatting a value
To format a response value, you need to get ahold of the field list, which is usually available at `indexPattern.fields`. Each field object has a `format` property*, which is an object detailed in [_field_formats.js](https://github.com/elastic/kibana4/blob/master/src/kibana/components/index_patterns/_field_formats.js).
Once you have the field that a response value came from, pass the value to `field.format.convert(value)` and a formatted string representation of the field will be returned.
\* the `format` property on field object's is a non-enumerable getter, meaning that if you itterate/clone/stringify the field object the format property will not be present.
### Changing a field's format
Currently only one field format exists, `"string"`, which just [flattens any value down to a string](https://github.com/elastic/kibana4/blob/master/src/kibana/components/index_patterns/_field_formats.js#L18-L24).
To change the format for a specific field you can either change the default for a field type modify the [default format mapping here](https://github.com/elastic/kibana4/blob/master/src/kibana/components/index_patterns/_field_formats.js#L37-L46).
To change the format for a specific indexPattern's field, add the field and format name to `indexPattern.customFormats` object property.
```js
$scope.onChangeFormat = function (field, format) {
indexPattern.customFormats[field.name] = format.name;
};
```
### Passing the formats to a chart
Currently, the [histogram formatter](https://github.com/elastic/kibana4/blob/master/src/plugins/visualize/saved_visualizations/resp_converters/histogram.js) passes the formatting function as the `xAxisFormatter` and `yAxisFormatter` function.
*/
define(function (require) {
return function FieldFormattingService($rootScope, config) {
var _ = require('lodash');
var angular = require('angular');
var moment = require('moment');
function stringConverter(val) {
return formatField(val, function (val) {
if (_.isObject(val)) {
return angular.toJson(val);
}
else if (val == null) {
return '';
}
else {
return '' + val;
}
});
}
var formats = [
{
types: [
'number',
'boolean',
'date',
'ip',
'attachment',
'geo_point',
'geo_shape',
'string',
'conflict'
],
name: 'string',
convert: stringConverter
},
{
types: [
'date'
],
name: 'date',
convert: function (val) {
return formatField(val, function (val) {
if (_.isNumber(val) || _.isDate(val)) {
return moment(val).format(config.get('dateFormat'));
} else {
return val;
}
});
}
},
{
types: [
'ip'
],
name: 'ip',
convert: function (val) {
return formatField(val, function (val) {
if (!isFinite(val)) return val;
return [val >>> 24, val >>> 16 & 0xFF, val >>> 8 & 0xFF, val & 0xFF].join('.');
});
}
},
{
types: [
'number'
],
name: 'kilobytes',
convert: function (val) {
return formatField(val, function (val) {
return (val / 1024).toFixed(config.get('format:numberPrecision')) + ' kb';
});
}
},
{
types: [
'number',
'murmur3'
],
name: 'number',
convert: function (val) {
return formatField(val, function (val) {
if (_.isNumber(val)) {
return +val.toFixed(config.get('format:numberPrecision'));
} else {
return stringConverter(val);
}
});
}
}
];
function formatField(value, fn) {
if (_.isArray(value)) {
if (value.length === 1) {
return fn(value[0]);
} else {
return angular.toJson(_.map(value, fn));
}
} else {
return fn(value);
}
}
formats.byType = _.transform(formats, function (byType, formatter) {
formatter.types.forEach(function (type) {
var list = byType[type] || (byType[type] = []);
list.push(formatter);
});
}, {});
formats.byName = _.indexBy(formats, 'name');
formats.defaultByType = {
number: formats.byName.number,
murmur3: formats.byName.number,
date: formats.byName.date,
boolean: formats.byName.string,
ip: formats.byName.ip,
attachment: formats.byName.string,
geo_point: formats.byName.string,
geo_shape: formats.byName.string,
string: formats.byName.string,
conflict: formats.byName.string
};
/**
* Wrap the dateFormat.convert function in memoize,
* as moment is a huge performance issue if not memoized.
*
* @return {void}
*/
function memoizeDateFormat() {
var format = formats.byName.date;
if (!format._origConvert) {
format._origConvert = format.convert;
}
format.convert = _.memoize(format._origConvert);
}
// memoize once config is ready, and every time the date format changes
$rootScope.$on('init:config', memoizeDateFormat);
$rootScope.$on('change:config.dateFormat', memoizeDateFormat);
return formats;
};
});

View file

@ -0,0 +1,20 @@
define(function (require) {
return function FieldListProvider(Private) {
var Field = Private(require('components/index_patterns/_field'));
var IndexedArray = require('utils/indexed_array/index');
var _ = require('lodash');
_(FieldList).inherits(IndexedArray);
function FieldList(indexPattern, specs) {
FieldList.Super.call(this, {
index: ['name'],
group: ['type'],
initialSet: specs.map(function (field) {
return new Field(indexPattern, field);
})
});
}
return FieldList;
};
});

View file

@ -16,8 +16,10 @@ define(function (require) {
{ name: 'geo_point', sortable: false, filterable: false },
{ name: 'geo_shape', sortable: false, filterable: false },
{ name: 'attachment', sortable: false, filterable: false },
{ name: 'murmur3', sortable: false, filterable: false }
{ name: 'murmur3', sortable: false, filterable: false },
{ name: 'unknown', sortable: false, filterable: false },
{ name: '_source', sortable: false, filterable: false },
]
});
};
});
});

View file

@ -2,7 +2,6 @@
// returns a flattened version
define(function (require) {
return function FlattenHitProvider(config, $rootScope) {
var _ = require('lodash');
var metaFields = config.get('metaFields');
@ -49,13 +48,15 @@ define(function (require) {
return flat;
}
function cachedFlatten(indexPattern, hit) {
return hit.$$_flattened || (hit.$$_flattened = flattenHit(indexPattern, hit));
}
return function (indexPattern) {
function cachedFlatten(hit) {
return hit.$$_flattened || (hit.$$_flattened = flattenHit(indexPattern, hit));
}
cachedFlatten.uncached = flattenHit;
cachedFlatten.uncached = _.partial(flattenHit, indexPattern);
return cachedFlatten;
return cachedFlatten;
};
};
});

View file

@ -0,0 +1,48 @@
// Takes a hit, merges it with any stored/scripted fields, and with the metaFields
// returns a formated version
define(function (require) {
var _ = require('lodash');
return function (indexPattern, defaultFormat) {
function convert(hit, val, fieldName) {
var field = indexPattern.fields.byName[fieldName];
if (!field) return defaultFormat.convert(val, 'html');
return field.format.getConverterFor('html')(val, field, hit);
}
function formatHit(hit) {
if (hit.$$_formatted) return hit.$$_formatted;
// use and update the partial cache, but don't rewrite it. _source is stored in partials
// but not $$_formatted
var partials = hit.$$_partialFormatted || (hit.$$_partialFormatted = {});
var cache = hit.$$_formatted = {};
_.forOwn(indexPattern.flattenHit(hit), function (val, fieldName) {
// sync the formatted and partial cache
var formatted = partials[fieldName] == null ? convert(hit, val, fieldName) : partials[fieldName];
cache[fieldName] = partials[fieldName] = formatted;
});
return cache;
}
formatHit.formatField = function (hit, fieldName) {
var partials = hit.$$_partialFormatted;
if (partials && partials[fieldName] != null) {
return partials[fieldName];
}
if (!partials) {
partials = hit.$$_partialFormatted = {};
}
var val = fieldName === '_source' ? hit._source : indexPattern.flattenHit(hit)[fieldName];
return partials[fieldName] = convert(hit, val, fieldName);
};
return formatHit;
};
});

View file

@ -9,7 +9,7 @@ define(function (require) {
fielddataFields = _.pluck(self.fields.byType.date, 'name');
_.each(self.getFields('scripted'), function (field) {
_.each(self.getScriptedFields(), function (field) {
scriptFields[field.name] = { script: field.script, lang: field.lang };
});

View file

@ -1,22 +1,20 @@
define(function (require) {
return function IndexPatternFactory(Private, timefilter, Notifier, config, Promise) {
return function IndexPatternFactory(Private, timefilter, Notifier, config, Promise, $rootScope) {
var _ = require('lodash');
var angular = require('angular');
var errors = require('errors');
var angular = require('angular');
var fieldformats = Private(require('registry/field_formats'));
var getIds = Private(require('components/index_patterns/_get_ids'));
var mapper = Private(require('components/index_patterns/_mapper'));
var fieldFormats = Private(require('components/index_patterns/_field_formats'));
var intervals = Private(require('components/index_patterns/_intervals'));
var fieldTypes = Private(require('components/index_patterns/_field_types'));
var flattenHit = Private(require('components/index_patterns/_flatten_hit'));
var getComputedFields = require('components/index_patterns/_get_computed_fields');
var shortDotsFilter = Private(require('filters/short_dots'));
var DocSource = Private(require('components/courier/data_source/doc_source'));
var mappingSetup = Private(require('utils/mapping_setup'));
var IndexedArray = require('utils/indexed_array/index');
var FieldList = Private(require('components/index_patterns/_field_list'));
var flattenHit = Private(require('components/index_patterns/_flatten_hit'));
var formatHit = require('components/index_patterns/_format_hit');
var type = 'index-pattern';
@ -26,17 +24,35 @@ define(function (require) {
title: 'string',
timeFieldName: 'string',
intervalName: 'string',
customFormats: 'json',
fields: 'json'
fields: 'json',
fieldFormatMap: {
type: 'string',
_serialize: function (map) {
if (map == null) return;
var count = 0;
var serialized = _.transform(map, function (flat, format, field) {
if (!format) return;
count++;
flat[field] = format;
});
if (count) return angular.toJson(serialized);
},
_deserialize: function (map) {
if (map == null) return {};
return _.mapValues(angular.fromJson(map), function (mapping) {
var FieldFormat = fieldformats.byId[mapping.id];
return FieldFormat && new FieldFormat(mapping.params);
});
}
}
});
function IndexPattern(id) {
var self = this;
// set defaults
self.id = id;
self.title = id;
self.customFormats = {};
setId(id);
var docSource = new DocSource();
@ -47,6 +63,11 @@ define(function (require) {
.type(type)
.id(self.id);
// listen for config changes and update field list
$rootScope.$on('change:config', function () {
initFields();
});
return mappingSetup.isDefined(type)
.then(function (defined) {
// create mapping for this type if one does not exist
@ -69,7 +90,7 @@ define(function (require) {
}
});
// Give obj all of the values in _source.fields
// Give obj all of the values in _source
_.assign(self, resp._source);
self._indexFields();
@ -84,51 +105,8 @@ define(function (require) {
});
};
function setIndexedValue(key, value) {
value = value || self[key];
self[key] = new IndexedArray({
index: ['name'],
group: ['type'],
initialSet: value.map(function (field) {
field.count = field.count || 0;
if (field.hasOwnProperty('format')) return field;
var type = fieldTypes.byName[field.type];
Object.defineProperties(field, {
bucketable: {
value: field.indexed || field.scripted
},
displayName: {
get: function () {
return shortDotsFilter(field.name);
}
},
filterable: {
value: field.name === '_id' || ((field.indexed && type && type.filterable) || field.scripted)
},
format: {
get: function () {
var formatName = self.customFormats && self.customFormats[field.name];
return formatName ? fieldFormats.byName[formatName] : fieldFormats.defaultByType[field.type];
}
},
sortable: {
value: field.indexed && type && type.sortable
},
scripted: {
// enumerable properties end up in the JSON
enumerable: true,
value: !!field.scripted
},
lang: {
enumerable: true,
value: field.scripted ? field.lang || 'expression' : undefined
}
});
return field;
})
});
function initFields(fields) {
self.fields = new FieldList(self, fields || self.fields || []);
}
self._indexFields = function () {
@ -136,7 +114,7 @@ define(function (require) {
if (!self.fields) {
return self.refreshFields();
} else {
setIndexedValue('fields');
initFields();
}
}
};
@ -144,13 +122,13 @@ define(function (require) {
self.addScriptedField = function (name, script, type, lang) {
type = type || 'string';
var scriptFields = _.pluck(self.getFields('scripted'), 'name');
var scriptFields = _.pluck(self.getScriptedFields(), 'name');
if (_.contains(scriptFields, name)) {
throw new errors.DuplicateField(name);
}
var scriptedField = self.fields.push({
self.fields.push({
name: name,
script: script,
type: type,
@ -185,11 +163,12 @@ define(function (require) {
}
};
self.getFields = function (type) {
var getScripted = (type === 'scripted');
return _.where(self.fields, function (field) {
return field.scripted ? getScripted : !getScripted;
});
self.getNonScriptedFields = function () {
return _.where(self.fields, { scripted: false });
};
self.getScriptedFields = function () {
return _.where(self.fields, { scripted: true });
};
self.getInterval = function () {
@ -223,47 +202,44 @@ define(function (require) {
// clear the indexPattern list cache
getIds.clearCache();
return body;
};
// index the document
var finish = function (id) {
self.id = id;
return self.id;
};
function setId(id) {
return self.id = id;
}
self.create = function () {
var body = self.prepBody();
return docSource.doCreate(body)
.then(finish).catch(function (err) {
.then(setId)
.catch(function (err) {
var confirmMessage = 'Are you sure you want to overwrite this?';
if (_.deepGet(err, 'origError.status') === 409 && window.confirm(confirmMessage)) {
return docSource.doIndex(body).then(finish);
return docSource.doIndex(body).then(setId);
}
return Promise.resolve(false);
});
};
self.save = function () {
var body = self.prepBody();
return docSource.doIndex(body).then(finish);
return docSource.doIndex(body).then(setId);
};
self.refreshFields = function () {
return mapper.clearCache(self)
.then(function () {
return self._fetchFields()
.then(self.save);
});
.then(self._fetchFields)
.then(self.save);
};
self._fetchFields = function () {
return mapper.getFieldsForIndexPattern(self, true)
.then(function (fields) {
// append existing scripted fields
fields = fields.concat(self.getFields('scripted'));
setIndexedValue('fields', fields);
fields = fields.concat(self.getScriptedFields());
// initialize self.field with this field list
initFields(fields);
});
};
@ -275,10 +251,21 @@ define(function (require) {
return '' + self.toJSON();
};
self.flattenHit = _.partial(flattenHit, self);
self.metaFields = config.get('metaFields');
self.getComputedFields = getComputedFields.bind(self);
self.flattenHit = flattenHit(self);
self.formatHit = formatHit(self, fieldformats.getDefaultInstance('string'));
self.formatField = self.formatHit.formatField;
}
IndexPattern.prototype.routes = {
edit: '/settings/indices/{{id}}',
addField: '/settings/indices/{{id}}/create-field',
indexedFields: '/settings/indices/{{id}}?_a=(tab:indexedFields)',
scriptedFields: '/settings/indices/{{id}}?_a=(tab:scriptedFields)'
};
return IndexPattern;
};
});

View file

@ -21,6 +21,9 @@ define(function (require) {
_timestamp: {
indexed: true,
type: 'date'
},
_source: {
type: '_source'
}
};
@ -40,4 +43,4 @@ define(function (require) {
return mapping;
};
};
});
});

View file

@ -1,12 +1,12 @@
<dl class="source truncate-by-height">
<% _.each(highlight, function (value, field) { /* show fields that match the query first */ %>
<dt><%= shortDotsFilter(field) %>:</dt>
<dt><%- shortDotsFilter(field) %>:</dt>
<dd><%= source[field] %></dd>
<%= ' ' %>
<% }); %>
<% _.each(source, function (value, field) { %>
<% if (_.has(highlight, field)) return; %>
<dt><%= shortDotsFilter(field) %>:</dt>
<dt><%- shortDotsFilter(field) %>:</dt>
<dd><%= value %></dd>
<%= ' ' %>
<% }); %>

View file

@ -1,8 +1,6 @@
define(function (require) {
return function transformMappingIntoFields(Private, configFile, config) {
var _ = require('lodash');
var MappingConflict = require('errors').MappingConflict;
var castMappingType = Private(require('components/index_patterns/_cast_mapping_type'));
var mapField = Private(require('components/index_patterns/_map_field'));
@ -19,7 +17,7 @@ define(function (require) {
var fields = {};
_.each(response, function (index, indexName) {
if (indexName === configFile.kibana_index) return;
_.each(index.mappings, function (mappings, typeName) {
_.each(index.mappings, function (mappings) {
_.each(mappings, function (field, name) {
var keys = Object.keys(field.mapping);
if (keys.length === 0 || (name[0] === '_') && !_.contains(config.get('metaFields'), name)) return;

View file

@ -42,7 +42,7 @@ define(function (require) {
self.intervals = Private(require('components/index_patterns/_intervals'));
self.mapper = Private(require('components/index_patterns/_mapper'));
self.patternToWildcard = Private(require('components/index_patterns/_pattern_to_wildcard'));
self.fieldFormats = Private(require('components/index_patterns/_field_formats'));
self.fieldFormats = Private(require('registry/field_formats'));
self.IndexPattern = IndexPattern;
});
});

View file

@ -1,7 +1,7 @@
define(function (require) {
require('modules')
.get('kibana')
.directive('paginatedTable', function ($filter, config, Private) {
.directive('paginatedTable', function ($filter) {
var _ = require('lodash');
var orderBy = $filter('orderBy');
@ -63,17 +63,23 @@ define(function (require) {
};
// update the sordedRows result
$scope.$watch('rows', rowSorter);
$scope.$watchCollection('paginatedTable.sort', rowSorter);
function rowSorter() {
if (self.sort.direction == null) {
$scope.sortedRows = $scope.rows.slice(0);
$scope.$watchMulti([
'rows',
'columns',
'[]paginatedTable.sort'
], function resortRows() {
if (!$scope.rows || !$scope.columns) {
$scope.sortedRows = false;
return;
}
$scope.sortedRows = orderBy($scope.rows, self.sort.getter, self.sort.direction === 'desc');
}
var sort = self.sort;
if (sort.direction == null) {
$scope.sortedRows = $scope.rows.slice(0);
} else {
$scope.sortedRows = orderBy($scope.rows, sort.getter, sort.direction === 'desc');
}
});
}
};
});

View file

@ -58,8 +58,7 @@ define(function (require) {
State.prototype.fetch = function () {
var stash = this._readFromURL();
// nothing to read from the url?
// we should save if were are ordered to persist
// nothing to read from the url? save if ordered to persist
if (stash === null) {
if (this._persistAcrossApps) {
return this.save();

View file

@ -0,0 +1,4 @@
{
"extends": "../../.jshintrc",
"unused": true
}

View file

@ -0,0 +1 @@
<field-format-editor-numeral></field-format-editor-numeral>

View file

@ -0,0 +1,20 @@
<div class="form-group">
<small class="pull-right">
<a ng-href="http://momentjs.com/" target="_blank">
Docs <i class="fa fa-link"></i>
</a>
</small>
<label>
moment.js format pattern
<small>
(Default: "{{ editor.field.format.type.paramDefaults.pattern }}")
</small>
</label>
<field-format-editor-pattern
ng-model="editor.formatParams.pattern"
inputs="cntrl.sampleInputs"
></field-format-editor-pattern>
</div>

View file

@ -0,0 +1,10 @@
<div class="form-group">
<label>Transform</label>
<select
ng-model="editor.formatParams.transform"
ng-options="opt.id as opt.name for opt in editor.field.format.type.transformOpts"
class="form-control">
</select>
</div>
<field-format-editor-samples inputs="editor.field.format.type.sampleInputs"></field-format-editor-samples>

View file

@ -0,0 +1,124 @@
<div class="form-group">
<label>Type</label>
<select
ng-model="editor.formatParams.type"
ng-options="type.id as type.name for type in editor.field.format.type.urlTypes"
class="form-control">
</select>
</div>
<div class="form-group">
<span class="pull-right text-info hintbox-label" ng-click="editor.showUrlTmplHelp = !editor.showUrlTmplHelp">
<i class="fa fa-info"></i> Url Template Help
</span>
<label>Url Template</label>
<div class="hintbox" ng-if="editor.showUrlTmplHelp">
<h4 class="hintbox-heading">
<i class="fa fa-question-circle text-info"></i> Url Template Help
</h4>
<p>
If a field only contains part of a url then a "Url Template" can be used to format the value as a complete url. The format is a string which uses double curly brace notation <code>{&shy;{ }&shy;}</code> to inject values. The following values can be accessed:
</p>
<ul>
<li>
<strong>value</strong> &mdash; The uri-escaped value
</li>
<li>
<strong>rawValue</strong> &mdash; The unescaped value
</li>
</ul>
<table class="table table-striped table-bordered">
<caption>Examples</caption>
<thead>
<tr>
<th>Value</th>
<th>Template</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>1234</td>
<td>http://company.net/profiles?user_id={&shy;{value}&shy;}</td>
<td>http://company.net/profiles?user_id=1234</td>
</tr>
<tr>
<td>users/admin</td>
<td>http://company.net/groups?id={&shy;{value}&shy;}</td>
<td>http://company.net/groups?id=users%2Fadmin</td>
</tr>
<tr>
<td>/images/favicon.ico</td>
<td>http://www.site.com{&shy;{rawValue}&shy;}</td>
<td>http://www.site.com/images/favicon.ico</td>
</tr>
</tbody>
</table>
</div>
<input ng-model="editor.formatParams.urlTemplate" class="form-control">
</div>
<div class="form-group">
<span class="pull-right text-info hintbox-label" ng-click="editor.showLabelTmplHelp = !editor.showLabelTmplHelp">
<i class="fa fa-info"></i> Label Template Help
</span>
<label>Label Template</label>
<div class="hintbox" ng-if="editor.showLabelTmplHelp">
<h4 class="hintbox-heading">
<i class="fa fa-question-circle text-info"></i> Label Template Help
</h4>
<p>
If the url in this field is large, it might be useful to provide an alternate template for the text version of the url. This will be displayed instead of the url, but will still link to the url. The format is a string which uses double curly brace notation <code>{&shy;{ }&shy;}</code> to inject values. The following values can be accessed:
</p>
<ul>
<li>
<strong>value</strong> &mdash; The fields value
</li>
<li>
<strong>url</strong> &mdash; The formatted url
</li>
</ul>
<table class="table table-striped table-bordered">
<caption>Examples</caption>
<thead>
<tr>
<th>Value</th>
<th>Url Template</th>
<th>Label Template</th>
<th>Result</th>
</tr>
</thead>
<tbody>
<tr>
<td>1234</td>
<td>http://company.net/profiles?user_id={&shy;{value}&shy;}</td>
<td>User #{&shy;{value}&shy;}</td>
<td>
<a href="http://company.net/profiles?user_id=1234">User #1234</a>
</td>
</tr>
<tr>
<td>/assets/main.css</td>
<td>http://site.com{&shy;{rawValue}&shy;}</td>
<td>View Asset</td>
<td>
<a href="http://site.com/assets/main.css">View Asset</a>
</td>
</tr>
</tbody>
</table>
</div>
<input ng-model="editor.formatParams.labelTemplate" class="form-control">
</div>
<field-format-editor-samples inputs="url.samples[editor.formatParams.type]"></field-format-editor-samples>

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Steven Skelton
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,11 @@
define(function (require) {
var fieldFormats = require('registry/field_formats');
fieldFormats.register(require('components/stringify/types/Url'));
fieldFormats.register(require('components/stringify/types/Bytes'));
fieldFormats.register(require('components/stringify/types/Date'));
fieldFormats.register(require('components/stringify/types/Ip'));
fieldFormats.register(require('components/stringify/types/Number'));
fieldFormats.register(require('components/stringify/types/Percent'));
fieldFormats.register(require('components/stringify/types/String'));
fieldFormats.register(require('components/stringify/types/Source'));
});

View file

@ -0,0 +1,10 @@
define(function (require) {
return function BytesFormatProvider(Private) {
var Numeral = Private(require('components/stringify/types/_Numeral'));
return Numeral.factory({
id: 'bytes',
title: 'Bytes',
sampleInputs: [1024, 5150000, 1990000000]
});
};
});

View file

@ -0,0 +1,56 @@
define(function (require) {
return function DateTimeFormatProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
var BoundToConfigObj = Private(require('components/bound_to_config_obj'));
var moment = require('moment');
require('components/field_format_editor/pattern/pattern');
_(DateTime).inherits(FieldFormat);
function DateTime(params) {
DateTime.Super.call(this, params);
}
DateTime.id = 'date';
DateTime.title = 'Date';
DateTime.fieldType = 'date';
DateTime.paramDefaults = new BoundToConfigObj({
pattern: '=dateFormat'
});
DateTime.editor = {
template: require('text!components/stringify/editors/date.html'),
controllerAs: 'cntrl',
controller: function ($interval, $scope) {
var self = this;
self.sampleInputs = [
Date.now(),
+moment().startOf('year'),
+moment().endOf('year')
];
$scope.$on('$destroy', $interval(function () {
self.sampleInputs[0] = Date.now();
}, 1000));
}
};
DateTime.prototype._convert = function (val) {
// don't give away our ref to converter so
// we can hot-swap when config changes
var pattern = this.param('pattern');
if (this._memoizedPattern !== pattern) {
this._memoizedPattern = pattern;
this._memoizedConverter = _.memoize(function converter(val) {
return moment(val).format(pattern);
});
}
return this._memoizedConverter(val);
};
return DateTime;
};
});

View file

@ -0,0 +1,24 @@
define(function (require) {
return function IpFormatProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
_(Ip).inherits(FieldFormat);
function Ip(params) {
Ip.Super.call(this, params);
}
Ip.id = 'ip';
Ip.title = 'IP Address';
Ip.fieldType = 'ip';
Ip.prototype._convert = function (val) {
if (!isFinite(val)) return val;
// shazzam!
return [val >>> 24, val >>> 16 & 0xFF, val >>> 8 & 0xFF, val & 0xFF].join('.');
};
return Ip;
};
});

View file

@ -0,0 +1,12 @@
define(function (require) {
return function NumberFormatProvider(Private) {
var Numeral = Private(require('components/stringify/types/_Numeral'));
return Numeral.factory({
id: 'number',
title: 'Number',
sampleInputs: [
10000, 12.345678, -1, -999, 0.52
]
});
};
});

View file

@ -0,0 +1,25 @@
define(function (require) {
return function NumberFormatProvider(Private) {
var _ = require('lodash');
var BoundToConfigObj = Private(require('components/bound_to_config_obj'));
var Numeral = Private(require('components/stringify/types/_Numeral'));
return Numeral.factory({
id: 'percent',
title: 'Percentage',
editorTemplate: require('text!components/stringify/editors/_numeral.html'),
paramDefaults: new BoundToConfigObj({
pattern: '=format:percent:defaultPattern',
fractional: true
}),
sampleInputs: [
0.10, 0.99999, 1, 100, 1000
],
prototype: {
_convert: _.compose(Numeral.prototype._convert, function (val) {
return this.param('fractional') ? val : val / 100;
})
}
});
};
});

View file

@ -0,0 +1,41 @@
define(function (require) {
return function _SourceProvider(Private, shortDotsFilter) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
var noWhiteSpace = require('utils/no_white_space');
var template = _.template(noWhiteSpace(require('text!components/stringify/types/_source.html')));
var angular = require('angular');
_(Source).inherits(FieldFormat);
function Source(params) {
Source.Super.call(this, params);
}
Source.id = '_source';
Source.title = '_source';
Source.fieldType = '_source';
Source.prototype._convert = {
text: angular.toJson,
html: function sourceToHtml(source, field, hit) {
if (!field) return this.getConverter('text')(source, field, hit);
var highlights = (hit && hit.highlight) || {};
var formatted = field.indexPattern.formatHit(hit);
var highlightPairs = [];
var sourcePairs = [];
_.keys(formatted).forEach(function (key) {
var pairs = highlights[key] ? highlightPairs : sourcePairs;
var field = shortDotsFilter(key);
var val = formatted[key];
pairs.push([field, val]);
}, []);
return template({ defPairs: highlightPairs.concat(sourcePairs) });
}
};
return Source;
};
});

View file

@ -0,0 +1,59 @@
define(function (require) {
return function _StringProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
require('components/field_format_editor/samples/samples');
_(_String).inherits(FieldFormat);
function _String(params) {
_String.Super.call(this, params);
}
_String.id = 'string';
_String.title = 'String';
_String.fieldType = [
'number',
'boolean',
'date',
'ip',
'attachment',
'geo_point',
'geo_shape',
'string',
'murmur3',
'unknown',
'conflict'
];
_String.paramDefaults = {
transform: false
};
_String.editor = require('text!components/stringify/editors/string.html');
_String.transformOpts = [
{ id: false, name: '- none -' },
{ id: 'lower', name: 'Lower Case' },
{ id: 'upper', name: 'Upper Case' },
{ id: 'short', name: 'Short Dots' }
];
_String.sampleInputs = [
'A Quick Brown Fox.',
'com.organizations.project.ClassName',
'hostname.net'
];
_String.prototype._convert = function (val) {
switch (this.param('transform')) {
case 'lower': return String(val).toLowerCase();
case 'upper': return String(val).toUpperCase();
case 'short': return _.shortenDottedString(val);
default: return _.asPrettyString(val);
}
};
return _String;
};
});

View file

@ -0,0 +1,129 @@
define(function (require) {
return function UrlFormatProvider(Private, highlightFilter) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
require('components/field_format_editor/pattern/pattern');
_(Url).inherits(FieldFormat);
function Url(params) {
Url.Super.call(this, params);
this._compileTemplate = _.memoize(this._compileTemplate);
}
Url.id = 'url';
Url.title = 'Url';
Url.fieldType = [
'number',
'boolean',
'date',
'ip',
'string',
'murmur3',
'unknown',
'conflict'
];
Url.editor = {
template: require('text!components/stringify/editors/url.html'),
controllerAs: 'url',
controller: function ($scope) {
var iconPattern = 'components/stringify/icons/{{value}}.png';
this.samples = {
a: [ 'john', '/some/pathname/asset.png', 1234 ],
img: [ 'go', 'stop', ['de', 'ne', 'us', 'ni'], 'cv' ]
};
$scope.$watch('editor.formatParams.type', function (type, prev) {
var params = $scope.editor.formatParams;
if (type === 'img' && type !== prev && !params.urlTemplate) {
params.urlTemplate = iconPattern;
}
});
}
};
Url.templateMatchRE = /{{([\s\S]+?)}}/g;
Url.paramDefaults = {
type: 'a',
urlTemplate: null,
labelTemplate: null
};
Url.urlTypes = [
{ id: 'a', name: 'Link' },
{ id: 'img', name: 'Image' }
];
Url.prototype._formatUrl = function (value) {
var template = this.param('urlTemplate');
if (!template) return value;
return this._compileTemplate(template)({
value: encodeURIComponent(value),
rawValue: value
});
};
Url.prototype._formatLabel = function (value, url) {
var template = this.param('labelTemplate');
if (url == null) url = this._formatUrl(value);
if (!template) return url;
return this._compileTemplate(template)({
value: value,
url: url
});
};
Url.prototype._convert = {
text: function (value) {
return this._formatLabel(value);
},
html: function (rawValue, field, hit) {
var url = _.escape(this._formatUrl(rawValue));
var label = _.escape(this._formatLabel(rawValue, url));
switch (this.param('type')) {
case 'img':
return '<img src="' + url + '" alt="' + label + '" title="' + label + '">';
default:
if (hit && hit.highlight && hit.highlight[field.name]) {
label = highlightFilter(label, hit.highlight[field.name]);
}
return '<a href="' + url + '" target="_blank">' + label + '</a>';
}
}
};
Url.prototype._compileTemplate = function (template) {
var parts = template.split(Url.templateMatchRE).map(function (part, i) {
// trim all the odd bits, the variable names
return (i % 2) ? part.trim() : part;
});
return function (locals) {
// replace all the odd bits with their local var
var output = '';
var i = -1;
while (++i < parts.length) {
if (i % 2) {
if (locals.hasOwnProperty(parts[i])) {
var local = locals[parts[i]];
output += local == null ? '' : local;
}
} else {
output += parts[i];
}
}
return output;
};
};
return Url;
};
});

View file

@ -0,0 +1,56 @@
define(function (require) {
return function AbstractNumeralFormatProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
var BoundToConfigObj = Private(require('components/bound_to_config_obj'));
var numeral = require('numeral')();
require('components/field_format_editor/numeral/numeral');
_(Numeral).inherits(FieldFormat);
function Numeral(params) {
Numeral.Super.call(this, params);
}
Numeral.prototype._convert = function (val) {
if (typeof val !== 'number') {
val = parseFloat(val);
}
if (isNaN(val)) return '';
return numeral.set(val).format(this.param('pattern'));
};
Numeral.factory = function (opts) {
_(Class).inherits(Numeral);
function Class(params) {
Class.Super.call(this, params);
}
Class.id = opts.id;
Class.title = opts.title;
Class.fieldType = 'number';
Class.paramDefaults = opts.paramDefaults || new BoundToConfigObj({
pattern: '=format:' + opts.id + ':defaultPattern',
});
Class.editor = {
template: opts.editorTemplate || require('text!components/field_format_editor/numeral/numeral.html'),
controllerAs: 'cntrl',
controller: opts.controller || function () {
this.sampleInputs = opts.sampleInputs;
}
};
if (opts.prototype) {
_.assign(Class.prototype, opts.prototype);
}
return Class;
};
return Numeral;
};
});

View file

@ -0,0 +1,7 @@
<dl class="source truncate-by-height">
<% defPairs.forEach(function (def) { %>
<dt><%- def[0] %>:</dt>
<dd><%= def[1] %></dd>
<%= ' ' %>
<% }); %>
</dl>

View file

@ -1,86 +1,74 @@
define(function (require) {
var _ = require('lodash');
require('filters/uriescape');
require('filters/rison');
var _ = require('lodash');
var rison = require('utils/rison');
var location = require('modules').get('kibana/url');
location.service('kbnUrl', function ($route, $location, $rootScope, globalState, $parse, getAppState) {
require('modules').get('kibana/url')
.service('kbnUrl', function (Private) { return Private(KbnUrlProvider); });
function KbnUrlProvider($route, $location, $rootScope, globalState, $parse, getAppState) {
var self = this;
var reloading;
var unbindListener;
/**
* Navigate to a url
*
* @param {String} url - the new url, can be a template. See #eval
* @param {Object} [paramObj] - optional set of parameters for the url template
* @return {undefined}
*/
self.change = function (url, paramObj) {
self._changeLocation('url', url, paramObj);
};
/**
* Same as #change except only changes the url's path,
* leaving the search string and such intact
*
* @param {String} path - the new path, can be a template. See #eval
* @param {Object} [paramObj] - optional set of parameters for the path template
* @return {undefined}
*/
self.changePath = function (path, paramObj) {
self._changeLocation('path', path, paramObj);
};
/**
* Same as #change except that it removes the current url from history
*
* @param {String} url - the new url, can be a template. See #eval
* @param {Object} [paramObj] - optional set of parameters for the url template
* @return {undefined}
*/
self.redirect = function (url, paramObj) {
self._changeLocation('url', url, paramObj, true);
};
/**
* Same as #redirect except only changes the url's path,
* leaving the search string and such intact
*
* @param {String} path - the new path, can be a template. See #eval
* @param {Object} [paramObj] - optional set of parameters for the path template
* @return {undefined}
*/
self.redirectPath = function (path, paramObj) {
self._changeLocation('path', path, paramObj, true);
};
self._changeLocation = function (type, url, paramObj, replace) {
var prev = {
path: $location.path(),
search: $location.search()
};
url = self.eval(url, paramObj);
$location[type](url);
if (replace) $location.replace();
var next = {
path: $location.path(),
search: $location.search()
};
if (self.shouldAutoReload(next, prev)) {
var appState = getAppState();
if (appState) appState.destroy();
reloading = $rootScope.$on('$locationChangeSuccess', function () {
// call the "unlisten" function returned by $on
reloading();
reloading = false;
$route.reload();
});
}
};
self.eval = function (url, paramObj) {
/**
* Evaluate a url template. templates can contain double-curly wrapped
* expressions that are evaluated in the context of the paramObj
*
* @param {String} template - the url template to evaluate
* @param {Object} [paramObj] - the variables to expose to the template
* @return {String} - the evaluated result
* @throws {Error} If any of the expressions can't be parsed.
*/
self.eval = function (template, paramObj) {
paramObj = paramObj || {};
return parseUrlPrams(url, paramObj);
};
self.getRoute = function () {
return $route.current && $route.current.$$route;
};
self.shouldAutoReload = function (next, prev) {
if (reloading) return false;
var route = self.getRoute();
if (!route) return false;
if (next.path !== prev.path) return false;
var reloadOnSearch = route.reloadOnSearch;
var searchSame = _.isEqual(next.search, prev.search);
return (reloadOnSearch && searchSame) || !reloadOnSearch;
};
function parseUrlPrams(url, paramObj) {
return url.replace(/\{\{([^\}]+)\}\}/g, function (match, expr) {
return template.replace(/\{\{([^\}]+)\}\}/g, function (match, expr) {
// remove filters
var key = expr.split('|')[0].trim();
@ -99,7 +87,104 @@ define(function (require) {
return $parse(expr)(paramObj);
});
}
};
});
/**
* convert an object's route to an href, compatible with
* window.location.href= and <a href="">
*
* @param {Object} obj - any object that list's it's routes at obj.routes{}
* @param {string} route - the route name
* @return {string} - the computed href
*/
self.getRouteHref = function (obj, route) {
return '#' + self.getRouteUrl(obj, route);
};
/**
* convert an object's route to a url, compatible with url.change() or $location.url()
*
* @param {Object} obj - any object that list's it's routes at obj.routes{}
* @param {string} route - the route name
* @return {string} - the computed url
*/
self.getRouteUrl = function (obj, route) {
var template = obj && obj.routes && obj.routes[route];
if (template) return self.eval(template, obj);
};
/**
* Similar to getRouteUrl, supports objects which list their routes,
* and redirects to the named route. See #redirect
*
* @param {Object} obj - any object that list's it's routes at obj.routes{}
* @param {string} route - the route name
* @return {undefined}
*/
self.redirectToRoute = function (obj, route) {
self.redirect(self.getRouteUrl(obj, route));
};
/**
* Similar to getRouteUrl, supports objects which list their routes,
* and changes the url to the named route. See #change
*
* @param {Object} obj - any object that list's it's routes at obj.routes{}
* @param {string} route - the route name
* @return {undefined}
*/
self.changeToRoute = function (obj, route) {
self.change(self.getRouteUrl(obj, route));
};
/////
// private api
/////
var reloading;
self._changeLocation = function (type, url, paramObj, replace) {
var prev = {
path: $location.path(),
search: $location.search()
};
url = self.eval(url, paramObj);
$location[type](url);
if (replace) $location.replace();
var next = {
path: $location.path(),
search: $location.search()
};
if (self._shouldAutoReload(next, prev)) {
var appState = getAppState();
if (appState) appState.destroy();
reloading = $rootScope.$on('$locationChangeSuccess', function () {
// call the "unlisten" function returned by $on
reloading();
reloading = false;
$route.reload();
});
}
};
self._shouldAutoReload = function (next, prev) {
if (reloading) return false;
var route = $route.current && $route.current.$$route;
if (!route) return false;
if (next.path !== prev.path) return false;
var reloadOnSearch = route.reloadOnSearch;
var searchSame = _.isEqual(next.search, prev.search);
return (reloadOnSearch && searchSame) || !reloadOnSearch;
};
}
return KbnUrlProvider;
});

View file

@ -1,7 +1,7 @@
define(function (require) {
return function AggConfigFactory(Private, fieldTypeFilter) {
var _ = require('lodash');
var fieldFormats = Private(require('components/index_patterns/_field_formats'));
var fieldFormats = Private(require('registry/field_formats'));
function AggConfig(vis, opts) {
var self = this;
@ -255,7 +255,7 @@ define(function (require) {
};
AggConfig.prototype.getKey = function (bucket, key) {
return this.type.getKey(bucket, key);
return this.type.getKey(bucket, key, this);
};
AggConfig.prototype.makeLabel = function () {
@ -267,16 +267,16 @@ define(function (require) {
return this.params.field;
};
AggConfig.prototype.fieldFormatter = function () {
AggConfig.prototype.fieldFormatter = function (contentType) {
var field = this.field();
var format = field && field.format;
var strFormat = fieldFormats.defaultByType.string;
var strFormat = fieldFormats.getDefaultInstance('string');
if (this.type.getFormat) {
if (this.type) {
format = this.type.getFormat(this) || format;
}
return (format || strFormat).convert;
return (format || strFormat).getConverterFor(contentType);
};
AggConfig.prototype.fieldName = function () {

View file

@ -1,9 +1,12 @@
define(function (require) {
define(function () {
var i = 0;
function AggConfigResult(aggConfig, parent, value, key) {
this.$parent = parent;
this.key = key;
this.value = value;
this.aggConfig = aggConfig;
this.$parent = parent;
this.$order = ++i;
if (aggConfig.schema.group === 'buckets') {
this.type = 'bucket';
@ -32,8 +35,8 @@ define(function (require) {
return this.aggConfig.createFilter(this.key);
};
AggConfigResult.prototype.toString = function () {
return this.aggConfig.fieldFormatter()(this.value);
AggConfigResult.prototype.toString = function (contentType) {
return this.aggConfig.fieldFormatter(contentType)(this.value);
};
AggConfigResult.prototype.valueOf = function () {

Some files were not shown because too many files have changed in this diff Show more