Merge branch 'master' into fixed_scales
Conflicts: src/kibana/components/vislib/lib/handler/types/point_series.js src/kibana/components/vislib/lib/y_axis.js src/kibana/plugins/vis_types/controls/point_series_options.html src/kibana/plugins/vis_types/controls/point_series_options.js test/unit/specs/vislib/lib/y_axis.js
|
@ -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) {
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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) {
|
|||
}
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,4 +19,4 @@ define(function (require) {
|
|||
chart.yScale = xAggOutput.metricScale || null;
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
define(function (require) {
|
||||
return function PointSeriesTooltipFormatter($compile, $rootScope) {
|
||||
var _ = require('lodash');
|
||||
var $ = require('jquery');
|
||||
|
||||
var $tooltipScope = $rootScope.$new();
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,4 +16,4 @@ define(function (require) {
|
|||
]
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) {
|
|||
]
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
@ -11,7 +12,10 @@ define(function (require) {
|
|||
|
||||
var valueProps = {
|
||||
makeLabel: function () {
|
||||
return 'Percentile rank ' + this.key + ' of "' + this.fieldDisplayName() + '"';
|
||||
var field = this.field();
|
||||
var format = (field && field.format) || fieldFormats.getDefaultInstance('number');
|
||||
|
||||
return 'Percentile rank ' + format.convert(this.key, 'text') + ' of "' + this.fieldDisplayName() + '"';
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -39,12 +43,15 @@ 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
|
||||
return _.find(bucket[agg.parentId].values, function (value, key) {
|
||||
return agg.key === parseFloat(key);
|
||||
});
|
||||
}) / 100;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
43
src/kibana/components/bound_to_config_obj.js
Normal 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;
|
||||
|
||||
};
|
||||
});
|
|
@ -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])'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
define(function (require) {
|
||||
return function CourierFetchRequestStatus() {
|
||||
return {
|
||||
ABORTED: {},
|
||||
DUPLICATE: {},
|
||||
INCOMPLETE: {}
|
||||
ABORTED: { CourierFetchRequestStatus: 'aborted' },
|
||||
DUPLICATE: { CourierFetchRequestStatus: 'duplicate' },
|
||||
INCOMPLETE: { CourierFetchRequestStatus: 'incomplete' }
|
||||
};
|
||||
};
|
||||
});
|
|
@ -17,18 +17,14 @@ define(function (require) {
|
|||
var parent = DocRequest.Super.prototype.canStart.call(this);
|
||||
if (!parent) return false;
|
||||
|
||||
// _getStoredVersion updates the internal
|
||||
// cache returned by _getVersion, so _getVersion
|
||||
// must be called first
|
||||
var version = this.source._getVersion();
|
||||
var version = this.source._version;
|
||||
var storedVersion = this.source._getStoredVersion();
|
||||
|
||||
// conditions that equal "fetch This DOC!"
|
||||
var unknownVersion = !version && !storedVersion;
|
||||
var versionMismatch = version !== storedVersion;
|
||||
var localVersionCleared = version && !storedVersion;
|
||||
var unkown = !version && !storedVersion;
|
||||
var mismatch = version !== storedVersion;
|
||||
|
||||
if (unknownVersion || versionMismatch || localVersionCleared) return true;
|
||||
return Boolean(mismatch || (unkown && !this.started));
|
||||
};
|
||||
|
||||
DocRequest.prototype.handleResponse = function (resp) {
|
||||
|
@ -43,4 +39,4 @@ define(function (require) {
|
|||
|
||||
return DocRequest;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ define(function (require) {
|
|||
}
|
||||
|
||||
AbstractReq.prototype.canStart = function () {
|
||||
return !this.stopped && !this.source._fetchDisabled;
|
||||
return Boolean(!this.stopped && !this.source._fetchDisabled);
|
||||
};
|
||||
|
||||
AbstractReq.prototype.start = function () {
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -115,4 +115,4 @@ define(function (require) {
|
|||
|
||||
return AbstractReq;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -241,6 +241,7 @@ define(function (require) {
|
|||
return self.id;
|
||||
});
|
||||
};
|
||||
|
||||
return docSource.doCreate(source)
|
||||
.then(finish)
|
||||
.catch(function (err) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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) {
|
||||
|
|
26
src/kibana/components/elastic_textarea.js
Normal 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);
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
});
|
127
src/kibana/components/field_editor/field_editor.html
Normal 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>
|
141
src/kibana/components/field_editor/field_editor.js
Normal 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
});
|
32
src/kibana/components/field_editor/scripting_info.html
Normal 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>
|
11
src/kibana/components/field_editor/scripting_warning.html
Normal 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>
|
122
src/kibana/components/field_format_editor/field_format_editor.js
Normal 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);
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
|
12
src/kibana/components/field_format_editor/numeral/numeral.js
Normal 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')
|
||||
};
|
||||
});
|
||||
});
|
|
@ -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>
|
26
src/kibana/components/field_format_editor/pattern/pattern.js
Normal 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;
|
||||
};
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -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>
|
45
src/kibana/components/field_format_editor/samples/samples.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
33
src/kibana/components/filter_bar/lib/compareFilters.js
Normal 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;
|
||||
}
|
||||
});
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
});
|
|
@ -1,5 +1,4 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
define(function () {
|
||||
return function mapScriptProvider(Promise, courier) {
|
||||
return function (filter) {
|
||||
var key, value, field;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
});
|
||||
|
|
19
src/kibana/components/filter_bar/lib/onlyStateChanged.js
Normal 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;
|
||||
});
|
||||
};
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
define(function (require) {
|
||||
return function ($scope) {
|
||||
/**
|
||||
* Removes all filters
|
||||
* @returns {void}
|
||||
*/
|
||||
return function () {
|
||||
$scope.state.filters = [];
|
||||
};
|
||||
};
|
||||
});
|
|
@ -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();
|
||||
};
|
||||
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
});
|
329
src/kibana/components/filter_bar/query_filter.js
Normal 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');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
79
src/kibana/components/index_patterns/_field.js
Normal 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;
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
};
|
||||
});
|
|
@ -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
|
||||
};
|
||||
};
|
||||
});
|
|
@ -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;
|
||||
};
|
||||
});
|
20
src/kibana/components/index_patterns/_field_list.js
Normal 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;
|
||||
};
|
||||
});
|
|
@ -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 },
|
||||
]
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
});
|
||||
|
|
48
src/kibana/components/index_patterns/_format_hit.js
Normal 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;
|
||||
};
|
||||
|
||||
});
|
|
@ -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 };
|
||||
});
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -21,6 +21,9 @@ define(function (require) {
|
|||
_timestamp: {
|
||||
indexed: true,
|
||||
type: 'date'
|
||||
},
|
||||
_source: {
|
||||
type: '_source'
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -40,4 +43,4 @@ define(function (require) {
|
|||
return mapping;
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
<%= ' ' %>
|
||||
<% }); %>
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
define(function (require) {
|
||||
var _ = require('lodash');
|
||||
var nextTick = require('utils/next_tick');
|
||||
var $ = require('jquery');
|
||||
var modules = require('modules');
|
||||
var module = modules.get('kibana/notify');
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
4
src/kibana/components/stringify/.jshintrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "../../.jshintrc",
|
||||
"unused": true
|
||||
}
|
1
src/kibana/components/stringify/editors/_numeral.html
Normal file
|
@ -0,0 +1 @@
|
|||
<field-format-editor-numeral></field-format-editor-numeral>
|
20
src/kibana/components/stringify/editors/date.html
Normal 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>
|
10
src/kibana/components/stringify/editors/string.html
Normal 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>
|
124
src/kibana/components/stringify/editors/url.html
Normal 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>{­{ }­}</code> to inject values. The following values can be accessed:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>value</strong> — The uri-escaped value
|
||||
</li>
|
||||
<li>
|
||||
<strong>rawValue</strong> — 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={­{value}­}</td>
|
||||
<td>http://company.net/profiles?user_id=1234</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>users/admin</td>
|
||||
<td>http://company.net/groups?id={­{value}­}</td>
|
||||
<td>http://company.net/groups?id=users%2Fadmin</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>/images/favicon.ico</td>
|
||||
<td>http://www.site.com{­{rawValue}­}</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>{­{ }­}</code> to inject values. The following values can be accessed:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>value</strong> — The fields value
|
||||
</li>
|
||||
<li>
|
||||
<strong>url</strong> — 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={­{value}­}</td>
|
||||
<td>User #{­{value}­}</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{­{rawValue}­}</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>
|
BIN
src/kibana/components/stringify/icons/cv.png
Normal file
After Width: | Height: | Size: 802 B |
BIN
src/kibana/components/stringify/icons/de.png
Normal file
After Width: | Height: | Size: 124 B |
20
src/kibana/components/stringify/icons/flag-icon.LICENSE
Normal 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.
|
BIN
src/kibana/components/stringify/icons/go.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/kibana/components/stringify/icons/ne.png
Normal file
After Width: | Height: | Size: 336 B |
BIN
src/kibana/components/stringify/icons/ni.png
Normal file
After Width: | Height: | Size: 919 B |
BIN
src/kibana/components/stringify/icons/stop.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
src/kibana/components/stringify/icons/us.png
Normal file
After Width: | Height: | Size: 1 KiB |
11
src/kibana/components/stringify/register.js
Normal 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'));
|
||||
});
|
10
src/kibana/components/stringify/types/Bytes.js
Normal 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]
|
||||
});
|
||||
};
|
||||
});
|
56
src/kibana/components/stringify/types/Date.js
Normal 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;
|
||||
};
|
||||
});
|
24
src/kibana/components/stringify/types/Ip.js
Normal 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;
|
||||
};
|
||||
});
|
12
src/kibana/components/stringify/types/Number.js
Normal 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
|
||||
]
|
||||
});
|
||||
};
|
||||
});
|
25
src/kibana/components/stringify/types/Percent.js
Normal 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;
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
41
src/kibana/components/stringify/types/Source.js
Normal 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;
|
||||
};
|
||||
});
|
59
src/kibana/components/stringify/types/String.js
Normal 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;
|
||||
};
|
||||
});
|
129
src/kibana/components/stringify/types/Url.js
Normal 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;
|
||||
};
|
||||
});
|
56
src/kibana/components/stringify/types/_Numeral.js
Normal 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;
|
||||
};
|
||||
});
|
7
src/kibana/components/stringify/types/_source.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<dl class="source truncate-by-height">
|
||||
<% defPairs.forEach(function (def) { %>
|
||||
<dt><%- def[0] %>:</dt>
|
||||
<dd><%= def[1] %></dd>
|
||||
<%= ' ' %>
|
||||
<% }); %>
|
||||
</dl>
|
|
@ -6,7 +6,7 @@ define(function (require) {
|
|||
var moment = require('moment');
|
||||
|
||||
require('directives/input_datetime');
|
||||
require('directives/greater_than');
|
||||
require('directives/inequality');
|
||||
require('components/timepicker/quick_ranges');
|
||||
require('components/timepicker/refresh_intervals');
|
||||
require('components/timepicker/time_units');
|
||||
|
|