[fieldFormat] refactor contentTypes, text is now the default

This commit is contained in:
Spencer Alger 2015-04-29 14:55:19 -07:00
parent 9e8eb3aecb
commit 874c32e01b
22 changed files with 264 additions and 155 deletions

View file

@ -17,7 +17,7 @@ define(function () {
// the field exists.
var bucket = item.name;
if (col) {
bucket = col.fieldFormatter('text')(bucket);
bucket = col.fieldFormatter()(bucket);
}
// Add the row to the tooltipScope.rows

View file

@ -23,7 +23,7 @@ define(function (require) {
}
if (series) {
point.series = series.agg.fieldFormatter('text')(unwrap(row[series.i]));
point.series = series.agg.fieldFormatter()(unwrap(row[series.i]));
}
if (yScale) {

View file

@ -2,7 +2,7 @@ define(function () {
return function PointSeriesInitX() {
return function initXAxis(chart) {
var x = chart.aspects.x;
chart.xAxisFormatter = x.agg ? x.agg.fieldFormatter('text') : String;
chart.xAxisFormatter = x.agg ? x.agg.fieldFormatter() : String;
chart.xAxisLabel = x.col.title;
if (!x.agg || !x.agg.type.ordered) return;

View file

@ -8,10 +8,10 @@ define(function (require) {
if (_.isArray(y)) {
// TODO: vis option should allow choosing this format
chart.yAxisFormatter = y[0].agg.fieldFormatter('text');
chart.yAxisFormatter = y[0].agg.fieldFormatter();
chart.yAxisLabel = ''; // use the legend
} else {
chart.yAxisFormatter = y.agg.fieldFormatter('text');
chart.yAxisFormatter = y.agg.fieldFormatter();
chart.yAxisLabel = y.col.title;
}

View file

@ -18,14 +18,14 @@ define(function (require) {
var value = result.value;
var detail = {
value: agg.fieldFormatter('text')(value),
value: agg.fieldFormatter()(value),
label: agg.makeLabel()
};
if (agg === datum.aggConfigResult.aggConfig) {
detail.percent = event.percent;
if (datum.yScale != null) {
detail.value = agg.fieldFormatter('text')(value * datum.yScale);
detail.value = agg.fieldFormatter()(value * datum.yScale);
}
}

View file

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

View file

@ -10,9 +10,9 @@ define(function (require) {
key = _.keys(filter.range)[0];
field = indexPattern.fields.byName[key];
from = (filter.range[key].gte != null) ? filter.range[key].gte : filter.range[key].gt;
from = field.format.convert(from, 'text');
from = field.format.convert(from);
to = (filter.range[key].lte != null) ? filter.range[key].lte : filter.range[key].lt;
to = field.format.convert(to, 'text');
to = field.format.convert(to);
value = from + ' to ' + to;
return { key: key, value: value };
});

View file

@ -9,7 +9,7 @@ define(function () {
key = filter.meta.field;
field = indexPattern.fields.byName[key];
value = filter.script.params.value;
value = field.format.convert(value, 'text');
value = field.format.convert(value);
return { key: key, value: value };
});
}

View file

@ -10,7 +10,7 @@ define(function (require) {
key = _.keys(filter.query.match)[0];
field = indexPattern.fields.byName[key];
value = filter.query.match[key].query;
value = field.format.convert(value, 'text');
value = field.format.convert(value);
return { key: key, value: value };
});
}

View file

@ -2,7 +2,7 @@ define(function (require) {
return function FieldObjectProvider(Private, shortDotsFilter, $rootScope, Notifier, kbnUrl) {
var notify = new Notifier({ location: 'IndexPattern Field' });
var FieldFormat = Private(require('components/index_patterns/_field_format'));
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');

View file

@ -1,124 +0,0 @@
define(function (require) {
return function FieldFormatClassProvider(config, $rootScope) {
var _ = require('lodash');
function FieldFormat(params) {
var self = this;
if (!self._convert) {
throw new Error('#_convert must be implemented by the FieldFormat subclass');
}
// 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 || {};
// memoize after default contentType is enforced so that
// #getConverterFor() and #getConverterFor('default') are ===
var getBoundConverter = _.memoize(function (contentType) {
return function boundConverter(value) {
if (value && typeof value.map === 'function') {
// rudimentary array testing
return JSON.stringify(value.map(boundConverter));
}
if (typeof value === 'number') {
value = +value;
} else {
value = _.escape(value);
}
return self._convert(value, contentType);
};
});
/**
* Convert a raw value to a formated string
* @param {any} value
* @param {string} [contentType=html] - 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
*/
self.convert = function (value, contentType) {
return self.getConverterFor(contentType)(value);
};
/**
* Get a convert function that is bound to a specific contentType
* @param {string} [contentType=html]
* @return {function} - a bound converter function, which accepts a single "value"
* argument of any type
*/
self.getConverterFor = function (contentType) {
return getBoundConverter(contentType || 'html');
};
/**
* Get the value of a param. This value may be a default value.
*
* @param {string} name - the param name to fetch
* @return {any}
*/
self.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];
};
self.params = function () {
return _.cloneDeep(_.defaults({}, this._params, this._paramDefaults));
};
}
FieldFormat.initConfig = function (input) {
return _.transform(input, function (params, val, key) {
if (!_.isString(val) || val.charAt(0) !== '=') {
params[key] = val;
return;
}
var configKey = val.substr(1);
update();
$rootScope.$on('init:config', update);
$rootScope.$on('change:config.' + configKey, update);
function update() {
params[key] = config.get(configKey);
}
}, {});
};
FieldFormat.prototype.toJSON = function () {
var type = this.type;
var defaults = this._paramDefaults;
var params = _.transform(this._params, function (uniqParams, val, param) {
if (val !== defaults[param]) {
uniqParams[param] = val;
}
}, {});
if (!_.size(params)) {
params = undefined;
}
return {
id: type.id,
params: params
};
};
return FieldFormat;
};
});

View file

@ -0,0 +1,100 @@
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=html]
* @return {function} - a bound converter function, which accepts a single "value"
* argument of any type
*/
FieldFormat.prototype.getConverterFor = function (contentType) {
return this._convert[contentType] || this._convert.text;
};
/**
* Get the value of a param. This value may be a default value.
*
* @param {string} name - the param name to fetch
* @return {any}
*/
FieldFormat.prototype.param = function (name) {
var val = this._params[name];
if (val || val === false || val === 0) {
// truthy, false, or 0 are fine
// '', NaN, null, undefined, etc are not
return val;
}
return this._paramDefaults[name];
};
/**
* Get all of the params in a single object
* @return {object}
*/
FieldFormat.prototype.params = function () {
return _.cloneDeep(_.defaults({}, this._params, this._paramDefaults));
};
/**
* serialize this format to a simple POJO, with only the params
* that are not default
*
* @return {object}
*/
FieldFormat.prototype.toJSON = function () {
var type = this.type;
var defaults = this._paramDefaults;
var params = _.transform(this._params, function (uniqParams, val, param) {
if (val !== defaults[param]) {
uniqParams[param] = val;
}
}, {});
if (!_.size(params)) {
params = undefined;
}
return {
id: type.id,
params: params
};
};
return FieldFormat;
};
});

View file

@ -0,0 +1,61 @@
define(function (require) {
return function contentTypesProvider() {
var _ = require('lodash');
var angular = require('angular');
var types = {
html: function (format, convert) {
return function recurse(value) {
var type = typeof value;
if (type === 'object' && typeof value.map === 'function') {
if (value.$$_formattedField) return value.$$_formattedField;
var subVals = value.map(recurse);
var useMultiLine = subVals.some(function (sub) {
return sub.indexOf('\n') > -1;
});
return value.$$_formattedField = subVals.join(',' + (useMultiLine ? '\n' : ' '));
}
return convert.call(format, value);
};
},
text: function (format, convert) {
return function recurse(value) {
if (value && typeof value.map === 'function') {
return angular.toJson(value.map(recurse));
}
return _.escape(convert.call(format, value));
};
}
};
function setup(format) {
var src = format._convert || {};
var converters = format._convert = {};
if (src.text) {
converters.text = types.text(format, src.text);
} else {
converters.text = types.text(format, _.escape);
}
if (src.html) {
converters.html = types.html(format, src.html);
} else {
converters.html = types.html(format, converters.text);
}
return format._convert;
}
return {
types: types,
setup: setup
};
};
});

View file

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

View file

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

View file

@ -1,7 +1,8 @@
define(function (require) {
return function DateTimeFormatProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format'));
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');
@ -15,7 +16,7 @@ define(function (require) {
DateTime.title = 'Date';
DateTime.fieldType = 'date';
DateTime.paramDefaults = FieldFormat.initConfig({
DateTime.paramDefaults = new BoundToConfigObj({
pattern: '=dateFormat'
});

View file

@ -1,7 +1,7 @@
define(function (require) {
return function IpFormatProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format'));
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
_(Ip).inherits(FieldFormat);
function Ip(params) {

View file

@ -1,14 +1,14 @@
define(function (require) {
return function NumberFormatProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format'));
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: FieldFormat.initConfig({
paramDefaults: new BoundToConfigObj({
pattern: '=format:percent:defaultPattern',
fractional: true
}),

View file

@ -1,7 +1,7 @@
define(function (require) {
return function _StringProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format'));
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
require('components/field_format_editor/samples/samples');

View file

@ -2,7 +2,7 @@ define(function (require) {
return function UrlFormatProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format'));
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
var StringFormat = Private(require('components/stringify/types/String'));
require('components/field_format_editor/pattern/pattern');
@ -34,16 +34,21 @@ define(function (require) {
{ id: 'img', name: 'Image' }
];
Url.prototype._convert = function (rawValue, contentType) {
var template = this.param('template');
var val = !template ? rawValue : this._compileTemplate(template)(rawValue);
Url.prototype._convert = {
html: function (rawValue) {
var url = this.convert(rawValue, 'text');
var value = _.escape(rawValue);
if (contentType !== 'html') return val;
switch (this.param('type')) {
case 'img': return '<img src="' + url + '" alt="' + value + '">';
default:
return '<a href="' + url + '" target="_blank">' + url + '</a>';
}
},
switch (this.param('type')) {
case 'img': return '<img src="' + val + '" alt="' + rawValue + '">';
default:
return '<a href="' + val + '" target="_blank">' + val + '</a>';
text: function (value) {
var template = this.param('template');
return !template ? value : this._compileTemplate(template)(value);
}
};

View file

@ -1,7 +1,8 @@
define(function (require) {
return function AbstractNumeralFormatProvider(Private) {
var _ = require('lodash');
var FieldFormat = Private(require('components/index_patterns/_field_format'));
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');
@ -31,7 +32,7 @@ define(function (require) {
Class.title = opts.title;
Class.fieldType = 'number';
Class.paramDefaults = opts.paramDefaults || FieldFormat.initConfig({
Class.paramDefaults = opts.paramDefaults || new BoundToConfigObj({
pattern: '=format:' + opts.id + ':defaultPattern',
});

View file

@ -2,7 +2,6 @@ define(function (require) {
describe('Stringify Component', function () {
var _ = require('lodash');
var $ = require('jquery');
var moment = require('moment');
var fieldFormats;
var FieldFormat;
@ -23,7 +22,7 @@ define(function (require) {
beforeEach(module('kibana'));
beforeEach(inject(function (Private, $injector) {
fieldFormats = Private(require('registry/field_formats'));
FieldFormat = Private(require('components/index_patterns/_field_format'));
FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
config = $injector.get('config');
$rootScope = $injector.get('$rootScope');
}));