[registry/fieldFormats] replace indexPattern/fieldFormats with a registry

This commit is contained in:
Spencer Alger 2015-01-22 15:10:38 -07:00
parent fcd2d6e42a
commit 171ea25ca0
6 changed files with 54 additions and 328 deletions

View file

@ -79,6 +79,42 @@ define(function (require) {
'truncate:maxHeight': {
value: 115,
description: 'The maximum height that a cell in a table should occupy. Set to 0 to disable truncation.'
},
'deafultFormat:ip': {
value: 'string',
description: 'The default format to be used for fields of type ip.'
},
'deafultFormat:date': {
value: 'string',
description: 'The default format to be used for fields of type date.'
},
'deafultFormat:string': {
value: 'string',
description: 'The default format to be used for fields of type string.'
},
'deafultFormat:number': {
value: 'string',
description: 'The default format to be used for fields of type number.'
},
'deafultFormat:boolean': {
value: 'string',
description: 'The default format to be used for fields of type boolean.'
},
'deafultFormat:conflict': {
value: 'string',
description: 'The default format to be used for fields of type conflict.'
},
'deafultFormat:geo_point': {
value: 'string',
description: 'The default format to be used for fields of type geo_point.'
},
'deafultFormat:geo_shape': {
value: 'string',
description: 'The default format to be used for fields of type geo_shape.'
},
'deafultFormat:attachment': {
value: 'string',
description: 'The default format to be used for fields of type attachment.'
}
};
});

View file

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

View file

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

View file

@ -1,7 +1,7 @@
define(function (require) {
return function AggConfigFactory(Private, fieldTypeFilter) {
var _ = require('lodash');
var fieldFormats = Private(require('components/index_patterns/_field_formats'));
var fieldFormats = Private(require('registry/field_formats'));
function AggConfig(vis, opts) {
var self = this;

View file

@ -0,0 +1,16 @@
define(function (require) {
var _ = require('lodash');
return require('registry/_registry')({
name: 'fieldFormats',
index: ['name'],
group: ['compatability'],
constructor: function (config) {
this.defaultForType = function (type) {
var name = config.get('deafultFormat:' + type);
return this.byName[name] || _.asString;
};
}
});
});

View file

@ -1,151 +0,0 @@
define(function (require) {
return ['Field Formatters', function () {
var _ = require('lodash');
var moment = require('moment');
var _config;
var formatters;
var formatter;
var types = [
'number',
'boolean',
'date',
'ip',
'attachment',
'geo_point',
'geo_shape',
'murmur3',
'string',
'conflict'
];
function formatFn(typeOrName) {
return (formatters.byName[typeOrName] || formatters.defaultByType[typeOrName]).convert;
}
beforeEach(module('kibana'));
beforeEach(inject(function (Private, $injector, config) {
_config = config;
formatters = Private(require('components/index_patterns/_field_formats'));
}));
it('should be an Object', function () {
expect(formatters).to.be.an(Object);
});
it('should have formatters indexed by type and by name', function () {
expect(formatters.byType).to.be.an(Object);
expect(formatters.byName).to.be.an(Object);
});
it('should have 1 or more formatters for each of ' + types.join(','), function () {
_.each(types, function (type) {
expect(formatters.byType[type]).to.be.an(Array);
_.each(formatters.byType[type], function (formatter) {
expect(formatter.convert).to.be.a(Function);
});
});
});
it('should expose default formatters for each type', function () {
_.each(types, function (type) {
expect(formatters.defaultByType[type]).to.be.an(Object);
});
});
describe('Array handling', function () {
it('should unwrap single item arrays', function () {
formatter = formatFn('string');
expect(formatter(['foo'])).to.not.be.an(Array);
expect(formatter(['foo'])).to.be('foo');
});
it('should stringify arrays longer than 1 element', function () {
formatter = formatFn('ip');
expect(formatter([0, 2130706433])).to.not.be.an(Array);
expect(formatter([0, 2130706433])).to.be('["0.0.0.0","127.0.0.1"]');
});
});
describe('string formatter', function () {
beforeEach(function () {
formatter = formatFn('string');
});
it('should the string value of the field', function () {
expect(formatter('foo')).to.be('foo');
expect(formatter(5)).to.be('5');
});
it('should return JSON for objects', function () {
expect(formatter({foo: true})).to.be('{"foo":true}');
});
it('should return an empty string for null', function () {
expect(formatter(null)).to.be('');
});
});
describe('date formatter', function () {
var dateFormat = 'YYYY-MM-DD';
beforeEach(function () {
_config.set('dateFormat', dateFormat);
formatter = formatFn('date');
});
it('should format numbers', function () {
expect(formatter(0)).to.be(moment(0).format(dateFormat));
});
it('should format dates', function () {
expect(formatter(new Date(0))).to.be(moment(0).format(dateFormat));
});
it('should not format strings', function () {
expect(formatter('2014-11')).to.be('2014-11');
});
});
describe('ip formatter', function () {
beforeEach(function () {
formatter = formatFn('ip');
});
it('should format numbers', function () {
expect(formatter(2130706433)).to.be('127.0.0.1');
});
it('should coerce numbers that are strings', function () {
expect(formatter('2130706433')).to.be('127.0.0.1');
});
it('should not coerce strings that are not numbers', function () {
expect(formatter('foo')).to.be('foo');
});
});
describe('kilobyte formatter', function () {
beforeEach(function () {
formatter = formatFn('kilobytes');
});
it('should be a function', function () {
expect(formatter).to.be.a(Function);
});
it('should format a number as kilobytes', function () {
expect(formatter(1024)).to.be('1.000 kb');
});
});
}];
});