mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Expose field formatters in kibana server (#12625)
* move field formatters to common * expose fieldFormats on server * export Format class from field_formats/types * remove use of window.atob in StringFormat * fix test and move server register under directory field_formats * cleanup * expose uiSettingDefaults on server so fieldFormats knows how to parse uiSettings * remove uiSettingDefaults decorator and clean up tests * move field_formats_service out of kibana plugin * make duration test more unstandable * prefix internal member with underscore * pass getConfig in constructor instead of methods * move getParamsDefaults for DurationFormat * add getInstance method to field_formats_server * move FieldFormat class outside of kibana plugin
This commit is contained in:
parent
7610d25aa0
commit
363a06555c
84 changed files with 1212 additions and 694 deletions
|
@ -0,0 +1,59 @@
|
|||
import expect from 'expect.js';
|
||||
import { BoolFormat } from '../boolean';
|
||||
|
||||
describe('Boolean Format', function () {
|
||||
|
||||
let boolean;
|
||||
beforeEach(() => {
|
||||
boolean = new BoolFormat();
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
input: 0,
|
||||
expected: 'false'
|
||||
},
|
||||
{
|
||||
input: 'no',
|
||||
expected: 'false'
|
||||
},
|
||||
{
|
||||
input: false,
|
||||
expected: 'false'
|
||||
},
|
||||
{
|
||||
input: 'false',
|
||||
expected: 'false'
|
||||
},
|
||||
{
|
||||
input: 1,
|
||||
expected: 'true'
|
||||
},
|
||||
{
|
||||
input: 'yes',
|
||||
expected: 'true'
|
||||
},
|
||||
{
|
||||
input: true,
|
||||
expected: 'true'
|
||||
},
|
||||
{
|
||||
input: 'true',
|
||||
expected: 'true'
|
||||
},
|
||||
{
|
||||
input: ' True ',//should handle trailing and mixed case
|
||||
expected: 'true'
|
||||
}
|
||||
].forEach((test)=> {
|
||||
it(`convert ${test.input} to boolean`, ()=> {
|
||||
expect(boolean.convert(test.input)).to.be(test.expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not convert non-boolean values, instead returning original value', ()=> {
|
||||
const s = 'non-boolean value!!';
|
||||
expect(boolean.convert(s)).to.be(s);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import expect from 'expect.js';
|
||||
import { BytesFormat } from '../bytes';
|
||||
|
||||
describe('BytesFormat', function () {
|
||||
|
||||
const config = {};
|
||||
config['format:bytes:defaultPattern'] = '0,0.[000]b';
|
||||
const getConfig = (key) => config[key];
|
||||
|
||||
it('default pattern', ()=> {
|
||||
const formatter = new BytesFormat({}, getConfig);
|
||||
expect(formatter.convert(5150000)).to.be('4.911MB');
|
||||
});
|
||||
|
||||
it('custom pattern', ()=> {
|
||||
const formatter = new BytesFormat({ pattern: '0,0b' }, getConfig);
|
||||
expect(formatter.convert('5150000')).to.be('5MB');
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
import expect from 'expect.js';
|
||||
import { ColorFormat } from '../color';
|
||||
|
||||
describe('Color Format', function () {
|
||||
|
||||
describe('field is a number', () => {
|
||||
it('should add colors if the value is in range', function () {
|
||||
const colorer = new ColorFormat({
|
||||
fieldType: 'number',
|
||||
colors: [{
|
||||
range: '100:150',
|
||||
text: 'blue',
|
||||
background: 'yellow'
|
||||
}]
|
||||
});
|
||||
expect(colorer.convert(99, 'html')).to.eql('<span ng-non-bindable>99</span>');
|
||||
expect(colorer.convert(100, 'html')).to.eql(
|
||||
'<span ng-non-bindable><span style="color: blue;background-color: yellow;">100</span></span>'
|
||||
);
|
||||
expect(colorer.convert(150, 'html')).to.eql(
|
||||
'<span ng-non-bindable><span style="color: blue;background-color: yellow;">150</span></span>'
|
||||
);
|
||||
expect(colorer.convert(151, 'html')).to.eql('<span ng-non-bindable>151</span>');
|
||||
});
|
||||
|
||||
it('should not convert invalid ranges', function () {
|
||||
const colorer = new ColorFormat({
|
||||
fieldType: 'number',
|
||||
colors: [{
|
||||
range: '100150',
|
||||
text: 'blue',
|
||||
background: 'yellow'
|
||||
}]
|
||||
});
|
||||
expect(colorer.convert(99, 'html')).to.eql('<span ng-non-bindable>99</span>');
|
||||
});
|
||||
});
|
||||
|
||||
describe('field is a string', () => {
|
||||
it('should add colors if the regex matches', function () {
|
||||
const colorer = new ColorFormat({
|
||||
fieldType: 'string',
|
||||
colors: [{
|
||||
regex: 'A.*',
|
||||
text: 'blue',
|
||||
background: 'yellow'
|
||||
}]
|
||||
});
|
||||
|
||||
const converter = colorer.getConverterFor('html');
|
||||
expect(converter('B', 'html')).to.eql('<span ng-non-bindable>B</span>');
|
||||
expect(converter('AAA', 'html')).to.eql(
|
||||
'<span ng-non-bindable><span style="color: blue;background-color: yellow;">AAA</span></span>'
|
||||
);
|
||||
expect(converter('AB', 'html')).to.eql(
|
||||
'<span ng-non-bindable><span style="color: blue;background-color: yellow;">AB</span></span>'
|
||||
);
|
||||
expect(converter('a', 'html')).to.eql('<span ng-non-bindable>a</span>');
|
||||
|
||||
expect(converter('B', 'html')).to.eql('<span ng-non-bindable>B</span>');
|
||||
expect(converter('AAA', 'html')).to.eql(
|
||||
'<span ng-non-bindable><span style="color: blue;background-color: yellow;">AAA</span></span>'
|
||||
);
|
||||
expect(converter('AB', 'html')).to.eql(
|
||||
'<span ng-non-bindable><span style="color: blue;background-color: yellow;">AB</span></span>'
|
||||
);
|
||||
expect(converter('a', 'html')).to.eql('<span ng-non-bindable>a</span>');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import expect from 'expect.js';
|
||||
import moment from 'moment-timezone';
|
||||
import { DateFormat } from '../date';
|
||||
|
||||
describe('Date Format', function () {
|
||||
let convert;
|
||||
let mockConfig;
|
||||
|
||||
beforeEach(function () {
|
||||
mockConfig = {};
|
||||
mockConfig.dateFormat = 'MMMM Do YYYY, HH:mm:ss.SSS';
|
||||
mockConfig['dateFormat:tz'] = 'Browser';
|
||||
const getConfig = (key) => mockConfig[key];
|
||||
|
||||
const date = new DateFormat({}, getConfig);
|
||||
|
||||
convert = date.convert.bind(date);
|
||||
});
|
||||
|
||||
it('decoding an undefined or null date should return an empty string', function () {
|
||||
expect(convert(null)).to.be('-');
|
||||
expect(convert(undefined)).to.be('-');
|
||||
});
|
||||
|
||||
it('should clear the memoization cache after changing the date', function () {
|
||||
function setDefaultTimezone() {
|
||||
moment.tz.setDefault(mockConfig['dateFormat:tz']);
|
||||
}
|
||||
const time = 1445027693942;
|
||||
|
||||
mockConfig['dateFormat:tz'] = 'America/Chicago';
|
||||
setDefaultTimezone();
|
||||
const chicagoTime = convert(time);
|
||||
|
||||
mockConfig['dateFormat:tz'] = 'America/Phoenix';
|
||||
setDefaultTimezone();
|
||||
const phoenixTime = convert(time);
|
||||
|
||||
expect(chicagoTime).not.to.equal(phoenixTime);
|
||||
});
|
||||
|
||||
it('should parse date math values', function () {
|
||||
expect(convert('2015-01-01||+1M/d')).to.be('January 1st 2015, 00:00:00.000');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,117 @@
|
|||
import expect from 'expect.js';
|
||||
import { DurationFormat } from '../duration';
|
||||
|
||||
describe('Duration Format', function () {
|
||||
|
||||
test({
|
||||
inputFormat: 'seconds',
|
||||
outputFormat: 'humanize',
|
||||
fixtures: [
|
||||
{
|
||||
input: -60,
|
||||
output: 'minus a minute'
|
||||
},
|
||||
{
|
||||
input: 60,
|
||||
output: 'a minute'
|
||||
},
|
||||
{
|
||||
input: 125,
|
||||
output: '2 minutes'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
test({
|
||||
inputFormat: 'minutes',
|
||||
outputFormat: 'humanize',
|
||||
fixtures: [
|
||||
{
|
||||
input: -60,
|
||||
output: 'minus an hour'
|
||||
},
|
||||
{
|
||||
input: 60,
|
||||
output: 'an hour'
|
||||
},
|
||||
{
|
||||
input: 125,
|
||||
output: '2 hours'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
test({
|
||||
inputFormat: 'minutes',
|
||||
outputFormat: 'asHours',
|
||||
fixtures: [
|
||||
{
|
||||
input: -60,
|
||||
output: '-1.00'
|
||||
},
|
||||
{
|
||||
input: 60,
|
||||
output: '1.00'
|
||||
},
|
||||
{
|
||||
input: 125,
|
||||
output: '2.08'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
test({
|
||||
inputFormat: 'seconds',
|
||||
outputFormat: 'asSeconds',
|
||||
outputPrecision: 0,
|
||||
fixtures: [
|
||||
{
|
||||
input: -60,
|
||||
output: '-60'
|
||||
},
|
||||
{
|
||||
input: 60,
|
||||
output: '60'
|
||||
},
|
||||
{
|
||||
input: 125,
|
||||
output: '125'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
test({
|
||||
inputFormat: 'seconds',
|
||||
outputFormat: 'asSeconds',
|
||||
outputPrecision: 2,
|
||||
fixtures: [
|
||||
{
|
||||
input: -60,
|
||||
output: '-60.00'
|
||||
},
|
||||
{
|
||||
input: -32.333,
|
||||
output: '-32.33'
|
||||
},
|
||||
{
|
||||
input: 60,
|
||||
output: '60.00'
|
||||
},
|
||||
{
|
||||
input: 125,
|
||||
output: '125.00'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
function test({ inputFormat, outputFormat, outputPrecision, fixtures }) {
|
||||
fixtures.forEach((fixture) => {
|
||||
const input = fixture.input;
|
||||
const output = fixture.output;
|
||||
it(`should format ${input} ${inputFormat} through ${outputFormat}${outputPrecision ? `, ${outputPrecision} decimals` : ''}`, () => {
|
||||
const duration = new DurationFormat({ inputFormat, outputFormat, outputPrecision });
|
||||
expect(duration.convert(input)).to.eql(output);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
import expect from 'expect.js';
|
||||
import { IpFormat } from '../ip';
|
||||
|
||||
describe('IP Address Format', function () {
|
||||
let ip;
|
||||
beforeEach(function () {
|
||||
ip = new IpFormat();
|
||||
});
|
||||
|
||||
it('converts a value from a decimal to a string', function () {
|
||||
expect(ip.convert(1186489492)).to.be('70.184.100.148');
|
||||
});
|
||||
|
||||
it('converts null and undefined to -', function () {
|
||||
expect(ip.convert(null)).to.be('-');
|
||||
expect(ip.convert(undefined)).to.be('-');
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import expect from 'expect.js';
|
||||
import { NumberFormat } from '../number';
|
||||
|
||||
describe('NumberFormat', function () {
|
||||
|
||||
const config = {};
|
||||
config['format:number:defaultPattern'] = '0,0.[000]';
|
||||
const getConfig = (key) => config[key];
|
||||
|
||||
it('default pattern', ()=> {
|
||||
const formatter = new NumberFormat({}, getConfig);
|
||||
expect(formatter.convert(12.345678)).to.be('12.346');
|
||||
});
|
||||
|
||||
it('custom pattern', ()=> {
|
||||
const formatter = new NumberFormat({ pattern: '0,0' }, getConfig);
|
||||
expect(formatter.convert('12.345678')).to.be('12');
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import expect from 'expect.js';
|
||||
import { PercentFormat } from '../percent';
|
||||
|
||||
describe('PercentFormat', function () {
|
||||
|
||||
const config = {};
|
||||
config['format:percent:defaultPattern'] = '0,0.[000]%';
|
||||
const getConfig = (key) => config[key];
|
||||
|
||||
it('default pattern', ()=> {
|
||||
const formatter = new PercentFormat({}, getConfig);
|
||||
expect(formatter.convert(0.99999)).to.be('99.999%');
|
||||
});
|
||||
|
||||
it('custom pattern', ()=> {
|
||||
const formatter = new PercentFormat({ pattern: '0,0%' }, getConfig);
|
||||
expect(formatter.convert('0.99999')).to.be('100%');
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import expect from 'expect.js';
|
||||
import { StringFormat } from '../string';
|
||||
|
||||
describe('String Format', function () {
|
||||
|
||||
it('convert a string to lower case', function () {
|
||||
const string = new StringFormat({
|
||||
transform: 'lower'
|
||||
});
|
||||
expect(string.convert('Kibana')).to.be('kibana');
|
||||
});
|
||||
|
||||
it('convert a string to upper case', function () {
|
||||
const string = new StringFormat({
|
||||
transform: 'upper'
|
||||
});
|
||||
expect(string.convert('Kibana')).to.be('KIBANA');
|
||||
});
|
||||
|
||||
it('decode a base64 string', function () {
|
||||
const string = new StringFormat({
|
||||
transform: 'base64'
|
||||
});
|
||||
expect(string.convert('Zm9vYmFy')).to.be('foobar');
|
||||
});
|
||||
|
||||
it('convert a string to title case', function () {
|
||||
const string = new StringFormat({
|
||||
transform: 'title'
|
||||
});
|
||||
expect(string.convert('PLEASE DO NOT SHOUT')).to.be('Please Do Not Shout');
|
||||
expect(string.convert('Mean, variance and standard_deviation.')).to.be('Mean, Variance And Standard_deviation.');
|
||||
expect(string.convert('Stay CALM!')).to.be('Stay Calm!');
|
||||
});
|
||||
|
||||
it('convert a string to short case', function () {
|
||||
const string = new StringFormat({
|
||||
transform: 'short'
|
||||
});
|
||||
expect(string.convert('dot.notated.string')).to.be('d.n.string');
|
||||
});
|
||||
|
||||
it('convert a string to unknown transform case', function () {
|
||||
const string = new StringFormat({
|
||||
transform: 'unknown_transform'
|
||||
});
|
||||
const value = 'test test test';
|
||||
expect(string.convert(value)).to.be(value);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
import expect from 'expect.js';
|
||||
import { TruncateFormat } from '../truncate';
|
||||
|
||||
describe('String TruncateFormat', function () {
|
||||
|
||||
it('truncate large string', function () {
|
||||
const truncate = new TruncateFormat({ fieldLength: 4 });
|
||||
|
||||
expect(truncate.convert('This is some text')).to.be('This...');
|
||||
});
|
||||
|
||||
it('does not truncate large string when field length is not a string', function () {
|
||||
const truncate = new TruncateFormat({ fieldLength: 'not number' });
|
||||
|
||||
expect(truncate.convert('This is some text')).to.be('This is some text');
|
||||
});
|
||||
|
||||
it('does not truncate large string when field length is null', function () {
|
||||
const truncate = new TruncateFormat({ fieldLength: null });
|
||||
|
||||
expect(truncate.convert('This is some text')).to.be('This is some text');
|
||||
});
|
||||
|
||||
it('does not truncate large string when field length larger than the text', function () {
|
||||
const truncate = new TruncateFormat({ fieldLength: 100000 });
|
||||
|
||||
expect(truncate.convert('This is some text')).to.be('This is some text');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,79 @@
|
|||
import expect from 'expect.js';
|
||||
import { UrlFormat } from '../url';
|
||||
|
||||
describe('UrlFormat', function () {
|
||||
|
||||
it('ouputs a simple <a> tab by default', function () {
|
||||
const url = new UrlFormat();
|
||||
|
||||
expect(url.convert('http://elastic.co', 'html'))
|
||||
.to.be('<span ng-non-bindable><a href="http://elastic.co" target="_blank">http://elastic.co</a></span>');
|
||||
});
|
||||
|
||||
it('outputs an <image> if type === "img"', function () {
|
||||
const url = new UrlFormat({ type: 'img' });
|
||||
|
||||
expect(url.convert('http://elastic.co', 'html'))
|
||||
.to.be('<span ng-non-bindable><img src="http://elastic.co" alt="A dynamically-specified image located at http://elastic.co"></span>');
|
||||
});
|
||||
|
||||
describe('url template', function () {
|
||||
it('accepts a template', function () {
|
||||
const url = new UrlFormat({ urlTemplate: 'url: {{ value }}' });
|
||||
expect(url.convert('url', 'html'))
|
||||
.to.be('<span ng-non-bindable><a href="url: url" target="_blank">url: url</a></span>');
|
||||
});
|
||||
|
||||
it('only outputs the url if the contentType === "text"', function () {
|
||||
const url = new UrlFormat();
|
||||
expect(url.convert('url', 'text')).to.be('url');
|
||||
});
|
||||
});
|
||||
|
||||
describe('label template', function () {
|
||||
it('accepts a template', function () {
|
||||
const url = new UrlFormat({ labelTemplate: 'extension: {{ value }}' });
|
||||
expect(url.convert('php', 'html'))
|
||||
.to.be('<span ng-non-bindable><a href="php" target="_blank">extension: php</a></span>');
|
||||
});
|
||||
|
||||
it('uses the label template for text formating', function () {
|
||||
const url = new UrlFormat({ labelTemplate: 'external {{value }}' });
|
||||
expect(url.convert('url', 'text')).to.be('external url');
|
||||
});
|
||||
|
||||
it('can use the raw value', function () {
|
||||
const url = new UrlFormat({
|
||||
labelTemplate: 'external {{value}}'
|
||||
});
|
||||
expect(url.convert('url?', 'text')).to.be('external url?');
|
||||
});
|
||||
|
||||
it('can use the url', function () {
|
||||
const url = new UrlFormat({
|
||||
urlTemplate: 'http://google.com/{{value}}',
|
||||
labelTemplate: 'external {{url}}'
|
||||
});
|
||||
expect(url.convert('url?', 'text')).to.be('external http://google.com/url%3F');
|
||||
});
|
||||
});
|
||||
|
||||
describe('templating', function () {
|
||||
it('ignores unknown variables', function () {
|
||||
const url = new UrlFormat({ urlTemplate: '{{ not really a var }}' });
|
||||
expect(url.convert('url', 'text')).to.be('');
|
||||
});
|
||||
|
||||
it('does not allow executing code in variable expressions', function () {
|
||||
const url = new UrlFormat({ urlTemplate: '{{ (__dirname = true) && value }}' });
|
||||
expect(url.convert('url', 'text')).to.be('');
|
||||
});
|
||||
|
||||
describe('', function () {
|
||||
it('does not get values from the prototype chain', function () {
|
||||
const url = new UrlFormat({ urlTemplate: '{{ toString }}' });
|
||||
expect(url.convert('url', 'text')).to.be('');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
import numeral from 'numeral';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
import numeral from '@spalger/numeral';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
|
||||
const numeralInst = numeral();
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import { asPrettyString } from '../../utils/as_pretty_string';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
|
||||
export class BoolFormat extends FieldFormat {
|
||||
_convert(value) {
|
||||
if (typeof value === 'string') {
|
||||
value = value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case false:
|
||||
case 0:
|
||||
case 'false':
|
||||
case 'no':
|
||||
return 'false';
|
||||
case true:
|
||||
case 1:
|
||||
case 'true':
|
||||
case 'yes':
|
||||
return 'true';
|
||||
default:
|
||||
return asPrettyString(value);
|
||||
}
|
||||
}
|
||||
|
||||
static id = 'boolean';
|
||||
static title = 'Boolean';
|
||||
static fieldType = ['boolean', 'number', 'string'];
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { Numeral } from './_numeral';
|
||||
|
||||
export const BytesFormat = Numeral.factory({
|
||||
id: 'bytes',
|
||||
title: 'Bytes'
|
||||
});
|
53
src/core_plugins/kibana/common/field_formats/types/color.js
Normal file
53
src/core_plugins/kibana/common/field_formats/types/color.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import _ from 'lodash';
|
||||
import { asPrettyString } from '../../utils/as_pretty_string';
|
||||
import { DEFAULT_COLOR } from './color_default';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
|
||||
const convertTemplate = _.template('<span style="<%- style %>"><%- val %></span>');
|
||||
|
||||
export class ColorFormat extends FieldFormat {
|
||||
getParamDefaults() {
|
||||
return {
|
||||
fieldType: null, // populated by editor, see controller below
|
||||
colors: [_.cloneDeep(DEFAULT_COLOR)]
|
||||
};
|
||||
}
|
||||
|
||||
findColorRuleForVal(val) {
|
||||
switch (this.param('fieldType')) {
|
||||
case 'string':
|
||||
return _.findLast(this.param('colors'), (colorParam) => {
|
||||
return new RegExp(colorParam.regex).test(val);
|
||||
});
|
||||
|
||||
case 'number':
|
||||
return _.findLast(this.param('colors'), ({ range }) => {
|
||||
if (!range) return;
|
||||
const [start, end] = range.split(':');
|
||||
return val >= Number(start) && val <= Number(end);
|
||||
});
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static id = 'color';
|
||||
static title = 'Color';
|
||||
static fieldType = [
|
||||
'number',
|
||||
'string'
|
||||
];
|
||||
}
|
||||
|
||||
ColorFormat.prototype._convert = {
|
||||
html(val) {
|
||||
const color = this.findColorRuleForVal(val);
|
||||
if (!color) return asPrettyString(val);
|
||||
|
||||
let style = '';
|
||||
if (color.text) style += `color: ${color.text};`;
|
||||
if (color.background) style += `background-color: ${color.background};`;
|
||||
return convertTemplate({ val, style });
|
||||
}
|
||||
};
|
51
src/core_plugins/kibana/common/field_formats/types/date.js
Normal file
51
src/core_plugins/kibana/common/field_formats/types/date.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
|
||||
export class DateFormat extends FieldFormat {
|
||||
constructor(params, getConfig) {
|
||||
super(params);
|
||||
|
||||
this.getConfig = getConfig;
|
||||
}
|
||||
|
||||
getParamDefaults() {
|
||||
return {
|
||||
pattern: this.getConfig('dateFormat'),
|
||||
timezone: this.getConfig('dateFormat:tz')
|
||||
};
|
||||
}
|
||||
|
||||
_convert(val) {
|
||||
// don't give away our ref to converter so
|
||||
// we can hot-swap when config changes
|
||||
const pattern = this.param('pattern');
|
||||
const timezone = this.param('timezone');
|
||||
|
||||
const timezoneChanged = this._timeZone !== timezone;
|
||||
const datePatternChanged = this._memoizedPattern !== pattern;
|
||||
if (timezoneChanged || datePatternChanged) {
|
||||
this._timeZone = timezone;
|
||||
this._memoizedPattern = pattern;
|
||||
|
||||
this._memoizedConverter = _.memoize(function converter(val) {
|
||||
if (val === null || val === undefined) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const date = moment(val);
|
||||
if (date.isValid()) {
|
||||
return date.format(pattern);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this._memoizedConverter(val);
|
||||
}
|
||||
|
||||
static id = 'date';
|
||||
static title = 'Date';
|
||||
static fieldType = 'date';
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
import moment from 'moment';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
|
||||
const ratioToSeconds = {
|
||||
picoseconds: 0.000000000001,
|
||||
nanoseconds: 0.000000001,
|
||||
microseconds: 0.000001
|
||||
};
|
||||
const HUMAN_FRIENDLY = 'humanize';
|
||||
const DEFAULT_OUTPUT_PRECISION = 2;
|
||||
const DEFAULT_INPUT_FORMAT = { text: 'Seconds', kind: 'seconds' };
|
||||
const inputFormats = [
|
||||
{ text: 'Picoseconds', kind: 'picoseconds' },
|
||||
{ text: 'Nanoseconds', kind: 'nanoseconds' },
|
||||
{ text: 'Microseconds', kind: 'microseconds' },
|
||||
{ text: 'Milliseconds', kind: 'milliseconds' },
|
||||
DEFAULT_INPUT_FORMAT,
|
||||
{ text: 'Minutes', kind: 'minutes' },
|
||||
{ text: 'Hours', kind: 'hours' },
|
||||
{ text: 'Days', kind: 'days' },
|
||||
{ text: 'Weeks', kind: 'weeks' },
|
||||
{ text: 'Months', kind: 'months' },
|
||||
{ text: 'Years', kind: 'years' }
|
||||
];
|
||||
const DEFAULT_OUTPUT_FORMAT = { text: 'Human Readable', method: 'humanize' };
|
||||
const outputFormats = [
|
||||
DEFAULT_OUTPUT_FORMAT,
|
||||
{ text: 'Milliseconds', method: 'asMilliseconds' },
|
||||
{ text: 'Seconds', method: 'asSeconds' },
|
||||
{ text: 'Minutes', method: 'asMinutes' },
|
||||
{ text: 'Hours', method: 'asHours' },
|
||||
{ text: 'Days', method: 'asDays' },
|
||||
{ text: 'Weeks', method: 'asWeeks' },
|
||||
{ text: 'Months', method: 'asMonths' },
|
||||
{ text: 'Years', method: 'asYears' }
|
||||
];
|
||||
|
||||
export class DurationFormat extends FieldFormat {
|
||||
isHuman() {
|
||||
return this.param('outputFormat') === HUMAN_FRIENDLY;
|
||||
}
|
||||
getParamDefaults() {
|
||||
return {
|
||||
inputFormat: DEFAULT_INPUT_FORMAT.kind,
|
||||
outputFormat: DEFAULT_OUTPUT_FORMAT.method,
|
||||
outputPrecision: DEFAULT_OUTPUT_PRECISION
|
||||
};
|
||||
}
|
||||
_convert(val) {
|
||||
const inputFormat = this.param('inputFormat');
|
||||
const outputFormat = this.param('outputFormat');
|
||||
const outputPrecision = this.param('outputPrecision');
|
||||
const human = this.isHuman();
|
||||
const prefix = val < 0 && human ? 'minus ' : '';
|
||||
const duration = parseInputAsDuration(val, inputFormat);
|
||||
const formatted = duration[outputFormat]();
|
||||
const precise = human ? formatted : formatted.toFixed(outputPrecision);
|
||||
return prefix + precise;
|
||||
}
|
||||
|
||||
static id = 'duration';
|
||||
static title = 'Duration';
|
||||
static fieldType = 'number';
|
||||
|
||||
static inputFormats = inputFormats;
|
||||
static outputFormats = outputFormats;
|
||||
}
|
||||
|
||||
function parseInputAsDuration(val, inputFormat) {
|
||||
const ratio = ratioToSeconds[inputFormat] || 1;
|
||||
const kind = inputFormat in ratioToSeconds ? 'seconds' : inputFormat;
|
||||
return moment.duration(val * ratio, kind);
|
||||
}
|
15
src/core_plugins/kibana/common/field_formats/types/ip.js
Normal file
15
src/core_plugins/kibana/common/field_formats/types/ip.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
|
||||
export class IpFormat extends FieldFormat {
|
||||
_convert(val) {
|
||||
if (val === undefined || val === null) return '-';
|
||||
if (!isFinite(val)) return val;
|
||||
|
||||
// shazzam!
|
||||
return [val >>> 24, val >>> 16 & 0xFF, val >>> 8 & 0xFF, val & 0xFF].join('.');
|
||||
}
|
||||
|
||||
static id = 'ip';
|
||||
static title = 'IP Address';
|
||||
static fieldType = 'ip';
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { Numeral } from './_numeral';
|
||||
|
||||
export const NumberFormat = Numeral.factory({
|
||||
id: 'number',
|
||||
title: 'Number'
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import _ from 'lodash';
|
||||
import { Numeral } from './_numeral';
|
||||
|
||||
export const PercentFormat = Numeral.factory({
|
||||
id: 'percent',
|
||||
title: 'Percentage',
|
||||
getParamDefaults: (getConfig) => {
|
||||
return {
|
||||
pattern: getConfig('format:percent:defaultPattern'),
|
||||
fractional: true
|
||||
};
|
||||
},
|
||||
prototype: {
|
||||
_convert: _.compose(Numeral.prototype._convert, function (val) {
|
||||
return this.param('fractional') ? val : val / 100;
|
||||
})
|
||||
}
|
||||
});
|
49
src/core_plugins/kibana/common/field_formats/types/source.js
Normal file
49
src/core_plugins/kibana/common/field_formats/types/source.js
Normal file
|
@ -0,0 +1,49 @@
|
|||
import _ from 'lodash';
|
||||
import { noWhiteSpace } from '../../utils/no_white_space';
|
||||
import { toJson } from '../../utils/aggressive_parse';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
import { shortenDottedString } from '../../utils/shorten_dotted_string';
|
||||
|
||||
const templateHtml = `
|
||||
<dl class="source truncate-by-height">
|
||||
<% defPairs.forEach(function (def) { %>
|
||||
<dt><%- def[0] %>:</dt>
|
||||
<dd><%= def[1] %></dd>
|
||||
<%= ' ' %>
|
||||
<% }); %>
|
||||
</dl>`;
|
||||
const template = _.template(noWhiteSpace(templateHtml));
|
||||
|
||||
export class SourceFormat extends FieldFormat {
|
||||
constructor(params, getConfig) {
|
||||
super(params);
|
||||
|
||||
this.getConfig = getConfig;
|
||||
}
|
||||
|
||||
static id = '_source';
|
||||
static title = '_source';
|
||||
static fieldType = '_source';
|
||||
}
|
||||
|
||||
SourceFormat.prototype._convert = {
|
||||
text: (value) => toJson(value),
|
||||
html: function sourceToHtml(source, field, hit) {
|
||||
if (!field) return this.getConverterFor('text')(source, field, hit);
|
||||
|
||||
const highlights = (hit && hit.highlight) || {};
|
||||
const formatted = field.indexPattern.formatHit(hit);
|
||||
const highlightPairs = [];
|
||||
const sourcePairs = [];
|
||||
|
||||
const isShortDots = this.getConfig('shortDots:enable');
|
||||
_.keys(formatted).forEach((key) => {
|
||||
const pairs = highlights[key] ? highlightPairs : sourcePairs;
|
||||
const field = isShortDots ? shortenDottedString(key) : key;
|
||||
const val = formatted[key];
|
||||
pairs.push([field, val]);
|
||||
}, []);
|
||||
|
||||
return template({ defPairs: highlightPairs.concat(sourcePairs) });
|
||||
}
|
||||
};
|
50
src/core_plugins/kibana/common/field_formats/types/string.js
Normal file
50
src/core_plugins/kibana/common/field_formats/types/string.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
import { asPrettyString } from '../../utils/as_pretty_string';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
import { shortenDottedString } from '../../utils/shorten_dotted_string';
|
||||
|
||||
export class StringFormat extends FieldFormat {
|
||||
getParamDefaults() {
|
||||
return {
|
||||
transform: false
|
||||
};
|
||||
}
|
||||
|
||||
_base64Decode(val) {
|
||||
try {
|
||||
return Buffer.from(val, 'base64').toString('utf8');
|
||||
} catch (e) {
|
||||
return asPrettyString(val);
|
||||
}
|
||||
}
|
||||
|
||||
_toTitleCase(val) {
|
||||
return val.replace(/\w\S*/g, txt => { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });
|
||||
}
|
||||
|
||||
_convert(val) {
|
||||
switch (this.param('transform')) {
|
||||
case 'lower': return String(val).toLowerCase();
|
||||
case 'upper': return String(val).toUpperCase();
|
||||
case 'title': return this._toTitleCase(val);
|
||||
case 'short': return shortenDottedString(val);
|
||||
case 'base64': return this._base64Decode(val);
|
||||
default: return asPrettyString(val);
|
||||
}
|
||||
}
|
||||
|
||||
static id = 'string';
|
||||
static title = 'String';
|
||||
static fieldType = [
|
||||
'number',
|
||||
'boolean',
|
||||
'date',
|
||||
'ip',
|
||||
'attachment',
|
||||
'geo_point',
|
||||
'geo_shape',
|
||||
'string',
|
||||
'murmur3',
|
||||
'unknown',
|
||||
'conflict'
|
||||
];
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import _ from 'lodash';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
|
||||
const omission = '...';
|
||||
|
||||
export class TruncateFormat extends FieldFormat {
|
||||
_convert(val) {
|
||||
const length = this.param('fieldLength');
|
||||
if (length > 0) {
|
||||
return _.trunc(val, {
|
||||
'length': length + omission.length,
|
||||
'omission': omission
|
||||
});
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static id = 'truncate';
|
||||
static title = 'Truncated String';
|
||||
static fieldType = ['string'];
|
||||
}
|
112
src/core_plugins/kibana/common/field_formats/types/url.js
Normal file
112
src/core_plugins/kibana/common/field_formats/types/url.js
Normal file
|
@ -0,0 +1,112 @@
|
|||
import _ from 'lodash';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
import { getHighlightHtml } from '../../highlight/highlight_html';
|
||||
|
||||
const templateMatchRE = /{{([\s\S]+?)}}/g;
|
||||
|
||||
export class UrlFormat extends FieldFormat {
|
||||
constructor(params) {
|
||||
super(params);
|
||||
this._compileTemplate = _.memoize(this._compileTemplate);
|
||||
}
|
||||
|
||||
getParamDefaults() {
|
||||
return {
|
||||
type: 'a',
|
||||
urlTemplate: null,
|
||||
labelTemplate: null
|
||||
};
|
||||
}
|
||||
|
||||
_formatLabel(value, url) {
|
||||
const template = this.param('labelTemplate');
|
||||
if (url == null) url = this._formatUrl(value);
|
||||
if (!template) return url;
|
||||
|
||||
return this._compileTemplate(template)({
|
||||
value: value,
|
||||
url: url
|
||||
});
|
||||
}
|
||||
|
||||
_formatUrl(value) {
|
||||
const template = this.param('urlTemplate');
|
||||
if (!template) return value;
|
||||
|
||||
return this._compileTemplate(template)({
|
||||
value: encodeURIComponent(value),
|
||||
rawValue: value
|
||||
});
|
||||
}
|
||||
|
||||
_compileTemplate(template) {
|
||||
const parts = template.split(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
|
||||
let output = '';
|
||||
let i = -1;
|
||||
while (++i < parts.length) {
|
||||
if (i % 2) {
|
||||
if (locals.hasOwnProperty(parts[i])) {
|
||||
const local = locals[parts[i]];
|
||||
output += local == null ? '' : local;
|
||||
}
|
||||
} else {
|
||||
output += parts[i];
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
}
|
||||
|
||||
static id = 'url';
|
||||
static title = 'Url';
|
||||
static fieldType = [
|
||||
'number',
|
||||
'boolean',
|
||||
'date',
|
||||
'ip',
|
||||
'string',
|
||||
'murmur3',
|
||||
'unknown',
|
||||
'conflict'
|
||||
];
|
||||
}
|
||||
|
||||
UrlFormat.prototype._convert = {
|
||||
text: function (value) {
|
||||
return this._formatLabel(value);
|
||||
},
|
||||
|
||||
html: function (rawValue, field, hit) {
|
||||
const url = _.escape(this._formatUrl(rawValue));
|
||||
const label = _.escape(this._formatLabel(rawValue, url));
|
||||
|
||||
switch (this.param('type')) {
|
||||
case 'img':
|
||||
// If the URL hasn't been formatted to become a meaningful label then the best we can do
|
||||
// is tell screen readers where the image comes from.
|
||||
const imageLabel =
|
||||
label === url
|
||||
? `A dynamically-specified image located at ${url}`
|
||||
: label;
|
||||
|
||||
return `<img src="${url}" alt="${imageLabel}">`;
|
||||
default:
|
||||
let linkLabel;
|
||||
|
||||
if (hit && hit.highlight && hit.highlight[field.name]) {
|
||||
linkLabel = getHighlightHtml(label, hit.highlight[field.name]);
|
||||
} else {
|
||||
linkLabel = label;
|
||||
}
|
||||
|
||||
return `<a href="${url}" target="_blank">${linkLabel}</a>`;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,11 +1,10 @@
|
|||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
import { highlightTags } from './highlight_tags';
|
||||
import { htmlTags } from './html_tags';
|
||||
|
||||
export function getHighlightHtml(fieldValue, highlights) {
|
||||
let highlightHtml = (typeof fieldValue === 'object')
|
||||
? angular.toJson(fieldValue)
|
||||
? JSON.stringify(fieldValue)
|
||||
: fieldValue;
|
||||
|
||||
_.each(highlights, function (highlight) {
|
|
@ -1,7 +1,8 @@
|
|||
import _ from 'lodash';
|
||||
import expect from 'expect.js';
|
||||
import sinon from 'sinon';
|
||||
import * as aggressiveParse from 'ui/utils/aggressive_parse';
|
||||
import * as aggressiveParse from '../aggressive_parse';
|
||||
|
||||
describe('aggressiveParse', () => {
|
||||
|
||||
let object;
|
|
@ -1,5 +1,5 @@
|
|||
import expect from 'expect.js';
|
||||
import { asPrettyString } from 'ui/utils/as_pretty_string';
|
||||
import { asPrettyString } from '../as_pretty_string';
|
||||
|
||||
describe('asPrettyString', () => {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import expect from 'expect.js';
|
||||
import { shortenDottedString } from 'ui/utils/shorten_dotted_string';
|
||||
import { shortenDottedString } from '../shorten_dotted_string';
|
||||
|
||||
describe('shortenDottedString', () => {
|
||||
|
|
@ -10,6 +10,7 @@ import { importApi } from './server/routes/api/import';
|
|||
import { exportApi } from './server/routes/api/export';
|
||||
import scripts from './server/routes/api/scripts';
|
||||
import { registerSuggestionsApi } from './server/routes/api/suggestions';
|
||||
import { registerFieldFormats } from './server/field_formats/register';
|
||||
import * as systemApi from './server/lib/system_api';
|
||||
import handleEsError from './server/lib/handle_es_error';
|
||||
import mappings from './mappings.json';
|
||||
|
@ -33,6 +34,7 @@ export default function (kibana) {
|
|||
|
||||
uiExports: {
|
||||
hacks: ['plugins/kibana/dev_tools/hacks/hide_empty_tools'],
|
||||
fieldFormats: ['plugins/kibana/field_formats/register'],
|
||||
app: {
|
||||
id: 'kibana',
|
||||
title: 'Kibana',
|
||||
|
@ -135,10 +137,12 @@ export default function (kibana) {
|
|||
importApi(server);
|
||||
exportApi(server);
|
||||
registerSuggestionsApi(server);
|
||||
registerFieldFormats(server);
|
||||
|
||||
server.expose('systemApi', systemApi);
|
||||
server.expose('handleEsError', handleEsError);
|
||||
server.expose('injectVars', injectVars);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import _ from 'lodash';
|
|||
import expect from 'expect.js';
|
||||
import ngMock from 'ng_mock';
|
||||
import { RegistryFieldFormatsProvider } from 'ui/registry/field_formats';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
import { FieldFormat } from '../../../../../ui/field_formats/field_format';
|
||||
|
||||
let fieldFormats;
|
||||
let config;
|
26
src/core_plugins/kibana/public/field_formats/register.js
Normal file
26
src/core_plugins/kibana/public/field_formats/register.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { RegistryFieldFormatsProvider } from 'ui/registry/field_formats';
|
||||
import { UrlFormat } from '../../common/field_formats/types/url';
|
||||
import { BytesFormat } from '../../common/field_formats/types/bytes';
|
||||
import { DateFormat } from '../../common/field_formats/types/date';
|
||||
import { DurationFormat } from '../../common/field_formats/types/duration';
|
||||
import { IpFormat } from '../../common/field_formats/types/ip';
|
||||
import { NumberFormat } from '../../common/field_formats/types/number';
|
||||
import { PercentFormat } from '../../common/field_formats/types/percent';
|
||||
import { StringFormat } from '../../common/field_formats/types/string';
|
||||
import { SourceFormat } from '../../common/field_formats/types/source';
|
||||
import { ColorFormat } from '../../common/field_formats/types/color';
|
||||
import { TruncateFormat } from '../../common/field_formats/types/truncate';
|
||||
import { BoolFormat } from '../../common/field_formats/types/boolean';
|
||||
|
||||
RegistryFieldFormatsProvider.register(() => UrlFormat);
|
||||
RegistryFieldFormatsProvider.register(() => BytesFormat);
|
||||
RegistryFieldFormatsProvider.register(() => DateFormat);
|
||||
RegistryFieldFormatsProvider.register(() => DurationFormat);
|
||||
RegistryFieldFormatsProvider.register(() => IpFormat);
|
||||
RegistryFieldFormatsProvider.register(() => NumberFormat);
|
||||
RegistryFieldFormatsProvider.register(() => PercentFormat);
|
||||
RegistryFieldFormatsProvider.register(() => StringFormat);
|
||||
RegistryFieldFormatsProvider.register(() => SourceFormat);
|
||||
RegistryFieldFormatsProvider.register(() => ColorFormat);
|
||||
RegistryFieldFormatsProvider.register(() => TruncateFormat);
|
||||
RegistryFieldFormatsProvider.register(() => BoolFormat);
|
27
src/core_plugins/kibana/server/field_formats/register.js
Normal file
27
src/core_plugins/kibana/server/field_formats/register.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { UrlFormat } from '../../common/field_formats/types/url';
|
||||
import { BytesFormat } from '../../common/field_formats/types/bytes';
|
||||
import { DateFormat } from '../../common/field_formats/types/date';
|
||||
import { DurationFormat } from '../../common/field_formats/types/duration';
|
||||
import { IpFormat } from '../../common/field_formats/types/ip';
|
||||
import { NumberFormat } from '../../common/field_formats/types/number';
|
||||
import { PercentFormat } from '../../common/field_formats/types/percent';
|
||||
import { StringFormat } from '../../common/field_formats/types/string';
|
||||
import { SourceFormat } from '../../common/field_formats/types/source';
|
||||
import { ColorFormat } from '../../common/field_formats/types/color';
|
||||
import { TruncateFormat } from '../../common/field_formats/types/truncate';
|
||||
import { BoolFormat } from '../../common/field_formats/types/boolean';
|
||||
|
||||
export function registerFieldFormats(server) {
|
||||
server.registerFieldFormatClass(UrlFormat);
|
||||
server.registerFieldFormatClass(BytesFormat);
|
||||
server.registerFieldFormatClass(DateFormat);
|
||||
server.registerFieldFormatClass(DurationFormat);
|
||||
server.registerFieldFormatClass(IpFormat);
|
||||
server.registerFieldFormatClass(NumberFormat);
|
||||
server.registerFieldFormatClass(PercentFormat);
|
||||
server.registerFieldFormatClass(StringFormat);
|
||||
server.registerFieldFormatClass(SourceFormat);
|
||||
server.registerFieldFormatClass(ColorFormat);
|
||||
server.registerFieldFormatClass(TruncateFormat);
|
||||
server.registerFieldFormatClass(BoolFormat);
|
||||
}
|
|
@ -1,23 +1,20 @@
|
|||
import _ from 'lodash';
|
||||
import expect from 'expect.js';
|
||||
import { asPrettyString } from 'ui/utils/as_pretty_string';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
import { asPrettyString } from '../../../core_plugins/kibana/common/utils/as_pretty_string';
|
||||
import { FieldFormat } from '../field_format';
|
||||
|
||||
describe('FieldFormat class', function () {
|
||||
|
||||
let TestFormat;
|
||||
|
||||
beforeEach(function () {
|
||||
|
||||
TestFormat = function (params) {
|
||||
TestFormat.Super.call(this, params);
|
||||
TestFormat = class _TestFormat extends FieldFormat {
|
||||
static id = 'test-format';
|
||||
static title = 'Test Format';
|
||||
_convert(val) {
|
||||
return asPrettyString(val);
|
||||
}
|
||||
};
|
||||
|
||||
TestFormat.id = 'test-format';
|
||||
TestFormat.title = 'Test Format';
|
||||
TestFormat.prototype._convert = asPrettyString;
|
||||
|
||||
_.class(TestFormat).inherits(FieldFormat);
|
||||
});
|
||||
|
||||
describe('params', function () {
|
32
src/ui/field_formats/__tests__/field_formats_service.js
Normal file
32
src/ui/field_formats/__tests__/field_formats_service.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import expect from 'expect.js';
|
||||
import { FieldFormatsService } from '../field_formats_service';
|
||||
import { NumberFormat } from '../../../core_plugins/kibana/common/field_formats/types/number';
|
||||
|
||||
describe('FieldFormatsService', function () {
|
||||
|
||||
const config = {};
|
||||
config['format:defaultTypeMap'] = {
|
||||
'number': { 'id': 'number', 'params': {} },
|
||||
'_default_': { 'id': 'string', 'params': {} }
|
||||
};
|
||||
config['format:number:defaultPattern'] = '0,0.[000]';
|
||||
const getConfig = (key) => config[key];
|
||||
const fieldFormatClasses = [NumberFormat];
|
||||
|
||||
let fieldFormats;
|
||||
beforeEach(function () {
|
||||
fieldFormats = new FieldFormatsService(fieldFormatClasses, getConfig);
|
||||
});
|
||||
|
||||
it('FieldFormats are accessible via getType method', function () {
|
||||
const Type = fieldFormats.getType('number');
|
||||
expect(Type.id).to.be('number');
|
||||
});
|
||||
|
||||
it('getDefaultInstance returns default FieldFormat instance for fieldType', function () {
|
||||
const instance = fieldFormats.getDefaultInstance('number', getConfig);
|
||||
expect(instance.type.id).to.be('number');
|
||||
expect(instance.convert('0.33333')).to.be('0.333');
|
||||
});
|
||||
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
import { asPrettyString } from 'ui/utils/as_pretty_string';
|
||||
import { getHighlightHtml } from 'ui/highlight';
|
||||
import { asPrettyString } from '../../core_plugins/kibana/common/utils/as_pretty_string';
|
||||
import { getHighlightHtml } from '../../core_plugins/kibana/common/highlight/highlight_html';
|
||||
|
||||
const types = {
|
||||
html: function (format, convert) {
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
import { contentTypesSetup } from 'ui/index_patterns/_field_format/content_types';
|
||||
import { contentTypesSetup } from './content_types';
|
||||
|
||||
export function FieldFormat(params) {
|
||||
// give the constructor a more appropriate name
|
53
src/ui/field_formats/field_formats_service.js
Normal file
53
src/ui/field_formats/field_formats_service.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
export class FieldFormatsService {
|
||||
constructor(fieldFormatClasses, getConfig) {
|
||||
this._fieldFormats = _.indexBy(fieldFormatClasses, 'id');
|
||||
this.getConfig = getConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the default type for this field type
|
||||
* using the format:defaultTypeMap config map
|
||||
*
|
||||
* @param {String} fieldType - the field type
|
||||
* @return {String}
|
||||
*/
|
||||
getDefaultConfig(fieldType) {
|
||||
const defaultMap = this.getConfig('format:defaultTypeMap');
|
||||
return defaultMap[fieldType] || defaultMap._default_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default fieldFormat instance for a field type.
|
||||
*
|
||||
* @param {String} fieldType
|
||||
* @return {FieldFormat}
|
||||
*/
|
||||
getDefaultInstance(fieldType) {
|
||||
const conf = this.getDefaultConfig(fieldType);
|
||||
const FieldFormat = this._fieldFormats[conf.id];
|
||||
return new FieldFormat(conf.params, this.getConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fieldFormat instance for a field format configuration.
|
||||
*
|
||||
* @param {Object} conf:id, conf:params
|
||||
* @return {FieldFormat}
|
||||
*/
|
||||
getInstance(conf) {
|
||||
const FieldFormat = this._fieldFormats[conf.id];
|
||||
return new FieldFormat(conf.params, this.getConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a FieldFormat type (class) by it's id.
|
||||
*
|
||||
* @param {String} fieldFormatId - the FieldFormat id
|
||||
* @return {FieldFormat}
|
||||
*/
|
||||
getType(fieldFormatId) {
|
||||
return this._fieldFormats[fieldFormatId];
|
||||
}
|
||||
}
|
28
src/ui/field_formats_mixin.js
Normal file
28
src/ui/field_formats_mixin.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import _ from 'lodash';
|
||||
import { FieldFormatsService } from './field_formats/field_formats_service';
|
||||
|
||||
export function fieldFormatsMixin(kbnServer, server) {
|
||||
const fieldFormatClasses = [];
|
||||
|
||||
// for use in the context of a request, the default context
|
||||
server.decorate('request', 'getFieldFormatService', async function () {
|
||||
return await server.fieldFormatServiceFactory(this.getUiSettingsService());
|
||||
});
|
||||
|
||||
// for use outside of the request context, for special cases
|
||||
server.decorate('server', 'fieldFormatServiceFactory', async function (uiSettings) {
|
||||
const uiConfigs = await uiSettings.getAll();
|
||||
const uiSettingDefaults = await uiSettings.getDefaults();
|
||||
Object.keys(uiSettingDefaults).forEach((key) => {
|
||||
if (_.has(uiConfigs, key) && uiSettingDefaults[key].type === 'json') {
|
||||
uiConfigs[key] = JSON.parse(uiConfigs[key]);
|
||||
}
|
||||
});
|
||||
const getConfig = (key) => uiConfigs[key];
|
||||
return new FieldFormatsService(fieldFormatClasses, getConfig);
|
||||
});
|
||||
|
||||
server.decorate('server', 'registerFieldFormatClass', (FieldFormat) => {
|
||||
fieldFormatClasses.push(FieldFormat);
|
||||
});
|
||||
}
|
|
@ -10,6 +10,7 @@ import UiBundlerEnv from './ui_bundler_env';
|
|||
import { UiI18n } from './ui_i18n';
|
||||
|
||||
import { uiSettingsMixin } from './ui_settings';
|
||||
import { fieldFormatsMixin } from './field_formats_mixin';
|
||||
|
||||
export default async (kbnServer, server, config) => {
|
||||
const uiExports = kbnServer.uiExports = new UiExports({
|
||||
|
@ -18,6 +19,8 @@ export default async (kbnServer, server, config) => {
|
|||
|
||||
await kbnServer.mixin(uiSettingsMixin);
|
||||
|
||||
await kbnServer.mixin(fieldFormatsMixin);
|
||||
|
||||
const uiI18n = kbnServer.uiI18n = new UiI18n(config.get('i18n.defaultLocale'));
|
||||
uiI18n.addUiExportConsumer(uiExports);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { AggTypesBucketsBucketAggTypeProvider } from 'ui/agg_types/buckets/_bucket_agg_type';
|
||||
import { AggTypesBucketsCreateFilterRangeProvider } from 'ui/agg_types/buckets/create_filter/range';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
import { FieldFormat } from '../../../field_formats/field_format';
|
||||
import { RangeKeyProvider } from './range_key';
|
||||
import rangesTemplate from 'ui/agg_types/controls/ranges.html';
|
||||
|
||||
|
|
|
@ -24,7 +24,6 @@ import 'ui/modals';
|
|||
import 'ui/state_management/app_state';
|
||||
import 'ui/state_management/global_state';
|
||||
import 'ui/storage';
|
||||
import 'ui/stringify/register';
|
||||
import 'ui/style_compile';
|
||||
import 'ui/timefilter';
|
||||
import 'ui/timepicker';
|
||||
|
|
|
@ -8,7 +8,7 @@ import { ErrorHandlersProvider } from '../_error_handlers';
|
|||
import { FetchProvider } from '../fetch';
|
||||
import { DecorateQueryProvider } from './_decorate_query';
|
||||
import { FieldWildcardProvider } from '../../field_wildcard';
|
||||
import { getHighlightRequest } from '../../highlight';
|
||||
import { getHighlightRequest } from '../../../../core_plugins/kibana/common/highlight';
|
||||
import { migrateFilter } from './_migrate_filter';
|
||||
|
||||
export function AbstractDataSourceProvider(Private, Promise, PromiseEmitter, config) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
|
||||
import { toJson } from 'ui/utils/aggressive_parse';
|
||||
import { toJson } from '../../../../../core_plugins/kibana/common/utils/aggressive_parse';
|
||||
|
||||
export function SearchStrategyProvider(Private, Promise, timefilter, kbnIndex, sessionId) {
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import _ from 'lodash';
|
||||
import $ from 'jquery';
|
||||
import rison from 'rison-node';
|
||||
import 'ui/highlight';
|
||||
import 'ui/highlight/highlight_tags';
|
||||
import 'ui/doc_viewer';
|
||||
import 'ui/filters/trust_as_html';
|
||||
import 'ui/filters/short_dots';
|
||||
import './table_row.less';
|
||||
import { noWhiteSpace } from 'ui/utils/no_white_space';
|
||||
import { noWhiteSpace } from '../../../../core_plugins/kibana/common/utils/no_white_space';
|
||||
import openRowHtml from 'ui/doc_table/components/table_row/open.html';
|
||||
import detailsHtml from 'ui/doc_table/components/table_row/details.html';
|
||||
import { uiModules } from 'ui/modules';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import './color.less';
|
||||
import colorTemplate from './color.html';
|
||||
import { DEFAULT_COLOR } from 'ui/stringify/types/color_default';
|
||||
import { DEFAULT_COLOR } from '../../../../../core_plugins/kibana/common/field_formats/types/color_default';
|
||||
|
||||
export function colorEditor() {
|
||||
return {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import _ from 'lodash';
|
||||
import { shortenDottedString } from 'ui/utils/shorten_dotted_string';
|
||||
import { shortenDottedString } from '../../../core_plugins/kibana/common/utils/shorten_dotted_string';
|
||||
import { uiModules } from 'ui/modules';
|
||||
// Shorts dot notated strings
|
||||
// eg: foo.bar.baz becomes f.b.baz
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import './_index_pattern';
|
||||
import './_get_computed_fields';
|
||||
import './_field_format';
|
||||
describe('Index Patterns', function () {
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ObjDefine } from 'ui/utils/obj_define';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
import { FieldFormat } from '../../field_formats/field_format';
|
||||
import { RegistryFieldFormatsProvider } from 'ui/registry/field_formats';
|
||||
import { getKbnFieldType } from '../../../utils';
|
||||
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
import ngMock from 'ng_mock';
|
||||
import sinon from 'sinon';
|
||||
import expect from 'expect.js';
|
||||
import { escape } from 'lodash';
|
||||
|
||||
import { contentTypesSetup } from '../content_types';
|
||||
|
||||
describe('index_patterns/_field_format/content_types', () => {
|
||||
|
||||
let render;
|
||||
const callMe = sinon.stub();
|
||||
afterEach(() => callMe.reset());
|
||||
|
||||
function getAllContents(node) {
|
||||
return [...node.childNodes].reduce((acc, child) => {
|
||||
return acc.concat(child, getAllContents(child));
|
||||
}, []);
|
||||
}
|
||||
|
||||
angular.module('testApp', [])
|
||||
.directive('testDirective', () => ({
|
||||
restrict: 'EACM',
|
||||
link: callMe
|
||||
}));
|
||||
|
||||
beforeEach(ngMock.module('testApp'));
|
||||
beforeEach(ngMock.inject(($injector) => {
|
||||
const $rootScope = $injector.get('$rootScope');
|
||||
const $compile = $injector.get('$compile');
|
||||
|
||||
$rootScope.callMe = callMe;
|
||||
|
||||
render = (convert) => {
|
||||
const $el = $('<div>');
|
||||
const { html } = contentTypesSetup({ _convert: { html: convert } });
|
||||
$compile($el.html(html(`
|
||||
<!-- directive: test-directive -->
|
||||
<div></div>
|
||||
<test-directive>{{callMe()}}</test-directive>
|
||||
<span test-directive></span>
|
||||
<marquee class="test-directive"></marquee>
|
||||
`)))($rootScope);
|
||||
return $el;
|
||||
};
|
||||
}));
|
||||
|
||||
it('no element directive', () => {
|
||||
const $el = render(value => `
|
||||
<test-directive>${escape(value)}</test-directive>
|
||||
`);
|
||||
|
||||
expect($el.find('test-directive')).to.have.length(1);
|
||||
sinon.assert.notCalled(callMe);
|
||||
});
|
||||
|
||||
it('no attribute directive', () => {
|
||||
const $el = render(value => `
|
||||
<div test-directive>${escape(value)}</div>
|
||||
`);
|
||||
|
||||
expect($el.find('[test-directive]')).to.have.length(1);
|
||||
sinon.assert.notCalled(callMe);
|
||||
});
|
||||
|
||||
it('no comment directive', () => {
|
||||
const $el = render(value => `
|
||||
<!-- directive: test-directive -->
|
||||
<div>${escape(value)}</div>
|
||||
`);
|
||||
|
||||
const comments = getAllContents($el.get(0))
|
||||
.filter(node => node.nodeType === 8);
|
||||
|
||||
expect(comments).to.have.length(1);
|
||||
expect(comments[0].textContent).to.contain('test-directive');
|
||||
sinon.assert.notCalled(callMe);
|
||||
});
|
||||
|
||||
it('no class directive', () => {
|
||||
const $el = render(value => `
|
||||
<div class="test-directive">${escape(value)}</div>
|
||||
`);
|
||||
|
||||
expect($el.find('.test-directive')).to.have.length(1);
|
||||
sinon.assert.notCalled(callMe);
|
||||
});
|
||||
|
||||
it('no interpolation', () => {
|
||||
const $el = render(value => `
|
||||
<div class="foo {{callMe()}}">${escape(value)}</div>
|
||||
`);
|
||||
|
||||
expect($el.find('.foo')).to.have.length(1);
|
||||
sinon.assert.notCalled(callMe);
|
||||
});
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
import { RegistryFieldFormatsProvider } from 'ui/registry/field_formats';
|
||||
import { stringifyUrl } from 'ui/stringify/types/url';
|
||||
import { stringifyBytes } from 'ui/stringify/types/bytes';
|
||||
import { stringifyDate } from 'ui/stringify/types/date';
|
||||
import { stringifyDuration } from 'ui/stringify/types/duration';
|
||||
import { stringifyIp } from 'ui/stringify/types/ip';
|
||||
import { stringifyNumber } from 'ui/stringify/types/number';
|
||||
import { stringifyPercent } from 'ui/stringify/types/percent';
|
||||
import { stringifyString } from 'ui/stringify/types/string';
|
||||
import { stringifySource } from 'ui/stringify/types/source';
|
||||
import { stringifyColor } from 'ui/stringify/types/color';
|
||||
import { stringifyTruncate } from 'ui/stringify/types/truncate';
|
||||
import { stringifyBoolean } from 'ui/stringify/types/boolean';
|
||||
|
||||
RegistryFieldFormatsProvider.register(stringifyUrl);
|
||||
RegistryFieldFormatsProvider.register(stringifyBytes);
|
||||
RegistryFieldFormatsProvider.register(stringifyDate);
|
||||
RegistryFieldFormatsProvider.register(stringifyDuration);
|
||||
RegistryFieldFormatsProvider.register(stringifyIp);
|
||||
RegistryFieldFormatsProvider.register(stringifyNumber);
|
||||
RegistryFieldFormatsProvider.register(stringifyPercent);
|
||||
RegistryFieldFormatsProvider.register(stringifyString);
|
||||
RegistryFieldFormatsProvider.register(stringifySource);
|
||||
RegistryFieldFormatsProvider.register(stringifyColor);
|
||||
RegistryFieldFormatsProvider.register(stringifyTruncate);
|
||||
RegistryFieldFormatsProvider.register(stringifyBoolean);
|
|
@ -1,7 +0,0 @@
|
|||
<dl class="source truncate-by-height">
|
||||
<% defPairs.forEach(function (def) { %>
|
||||
<dt><%- def[0] %>:</dt>
|
||||
<dd><%= def[1] %></dd>
|
||||
<%= ' ' %>
|
||||
<% }); %>
|
||||
</dl>
|
|
@ -1,35 +0,0 @@
|
|||
import { asPrettyString } from 'ui/utils/as_pretty_string';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
|
||||
export function stringifyBoolean() {
|
||||
|
||||
class Bool extends FieldFormat {
|
||||
|
||||
_convert(value) {
|
||||
if (typeof value === 'string') {
|
||||
value = value.trim().toLowerCase();
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case false:
|
||||
case 0:
|
||||
case 'false':
|
||||
case 'no':
|
||||
return 'false';
|
||||
case true:
|
||||
case 1:
|
||||
case 'true':
|
||||
case 'yes':
|
||||
return 'true';
|
||||
default:
|
||||
return asPrettyString(value);
|
||||
}
|
||||
}
|
||||
|
||||
static id = 'boolean';
|
||||
static title = 'Boolean';
|
||||
static fieldType = ['boolean', 'number', 'string'];
|
||||
}
|
||||
|
||||
return Bool;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { Numeral } from 'ui/stringify/types/_numeral';
|
||||
|
||||
export function stringifyBytes() {
|
||||
return Numeral.factory({
|
||||
id: 'bytes',
|
||||
title: 'Bytes'
|
||||
});
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import { asPrettyString } from 'ui/utils/as_pretty_string';
|
||||
import { DEFAULT_COLOR } from './color_default';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
|
||||
export function stringifyColor() {
|
||||
|
||||
const convertTemplate = _.template('<span style="<%- style %>"><%- val %></span>');
|
||||
|
||||
class ColorFormat extends FieldFormat {
|
||||
getParamDefaults() {
|
||||
return {
|
||||
fieldType: null, // populated by editor, see controller below
|
||||
colors: [_.cloneDeep(DEFAULT_COLOR)]
|
||||
};
|
||||
}
|
||||
|
||||
findColorRuleForVal(val) {
|
||||
switch (this.param('fieldType')) {
|
||||
case 'string':
|
||||
return _.findLast(this.param('colors'), (colorParam) => {
|
||||
return new RegExp(colorParam.regex).test(val);
|
||||
});
|
||||
|
||||
case 'number':
|
||||
return _.findLast(this.param('colors'), ({ range }) => {
|
||||
if (!range) return;
|
||||
const [start, end] = range.split(':');
|
||||
return val >= Number(start) && val <= Number(end);
|
||||
});
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static id = 'color';
|
||||
static title = 'Color';
|
||||
static fieldType = [
|
||||
'number',
|
||||
'string'
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
ColorFormat.prototype._convert = {
|
||||
html(val) {
|
||||
const color = this.findColorRuleForVal(val);
|
||||
if (!color) return asPrettyString(val);
|
||||
|
||||
let style = '';
|
||||
if (color.text) style += `color: ${color.text};`;
|
||||
if (color.background) style += `background-color: ${color.background};`;
|
||||
return convertTemplate({ val, style });
|
||||
}
|
||||
};
|
||||
|
||||
return ColorFormat;
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
|
||||
export function stringifyDate() {
|
||||
|
||||
class DateFormat extends FieldFormat {
|
||||
constructor(params, getConfig) {
|
||||
super(params);
|
||||
|
||||
this.getConfig = getConfig;
|
||||
}
|
||||
|
||||
getParamDefaults() {
|
||||
return {
|
||||
pattern: this.getConfig('dateFormat'),
|
||||
timezone: this.getConfig('dateFormat:tz')
|
||||
};
|
||||
}
|
||||
|
||||
_convert(val) {
|
||||
// don't give away our ref to converter so
|
||||
// we can hot-swap when config changes
|
||||
const pattern = this.param('pattern');
|
||||
const timezone = this.param('timezone');
|
||||
|
||||
const timezoneChanged = this._timeZone !== timezone;
|
||||
const datePatternChanged = this._memoizedPattern !== pattern;
|
||||
if (timezoneChanged || datePatternChanged) {
|
||||
this._timeZone = timezone;
|
||||
this._memoizedPattern = pattern;
|
||||
|
||||
this._memoizedConverter = _.memoize(function converter(val) {
|
||||
if (val === null || val === undefined) {
|
||||
return '-';
|
||||
}
|
||||
|
||||
const date = moment(val);
|
||||
if (date.isValid()) {
|
||||
return date.format(pattern);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this._memoizedConverter(val);
|
||||
}
|
||||
|
||||
static id = 'date';
|
||||
static title = 'Date';
|
||||
static fieldType = 'date';
|
||||
}
|
||||
|
||||
return DateFormat;
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
import moment from 'moment';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
|
||||
export function stringifyDuration() {
|
||||
const ratioToSeconds = {
|
||||
picoseconds: 0.000000000001,
|
||||
nanoseconds: 0.000000001,
|
||||
microseconds: 0.000001
|
||||
};
|
||||
const HUMAN_FRIENDLY = 'humanize';
|
||||
const DEFAULT_OUTPUT_PRECISION = 2;
|
||||
const DEFAULT_INPUT_FORMAT = { text: 'Seconds', kind: 'seconds' };
|
||||
const inputFormats = [
|
||||
{ text: 'Picoseconds', kind: 'picoseconds' },
|
||||
{ text: 'Nanoseconds', kind: 'nanoseconds' },
|
||||
{ text: 'Microseconds', kind: 'microseconds' },
|
||||
{ text: 'Milliseconds', kind: 'milliseconds' },
|
||||
DEFAULT_INPUT_FORMAT,
|
||||
{ text: 'Minutes', kind: 'minutes' },
|
||||
{ text: 'Hours', kind: 'hours' },
|
||||
{ text: 'Days', kind: 'days' },
|
||||
{ text: 'Weeks', kind: 'weeks' },
|
||||
{ text: 'Months', kind: 'months' },
|
||||
{ text: 'Years', kind: 'years' }
|
||||
];
|
||||
const DEFAULT_OUTPUT_FORMAT = { text: 'Human Readable', method: 'humanize' };
|
||||
const outputFormats = [
|
||||
DEFAULT_OUTPUT_FORMAT,
|
||||
{ text: 'Milliseconds', method: 'asMilliseconds' },
|
||||
{ text: 'Seconds', method: 'asSeconds' },
|
||||
{ text: 'Minutes', method: 'asMinutes' },
|
||||
{ text: 'Hours', method: 'asHours' },
|
||||
{ text: 'Days', method: 'asDays' },
|
||||
{ text: 'Weeks', method: 'asWeeks' },
|
||||
{ text: 'Months', method: 'asMonths' },
|
||||
{ text: 'Years', method: 'asYears' }
|
||||
];
|
||||
|
||||
class Duration extends FieldFormat {
|
||||
isHuman() {
|
||||
return this.param('outputFormat') === HUMAN_FRIENDLY;
|
||||
}
|
||||
_convert(val) {
|
||||
const inputFormat = this.param('inputFormat');
|
||||
const outputFormat = this.param('outputFormat');
|
||||
const outputPrecision = this.param('outputPrecision');
|
||||
const human = this.isHuman();
|
||||
const prefix = val < 0 && human ? 'minus ' : '';
|
||||
const duration = parseInputAsDuration(val, inputFormat);
|
||||
const formatted = duration[outputFormat]();
|
||||
const precise = human ? formatted : formatted.toFixed(outputPrecision);
|
||||
return prefix + precise;
|
||||
}
|
||||
|
||||
static id = 'duration';
|
||||
static title = 'Duration';
|
||||
static fieldType = 'number';
|
||||
|
||||
static inputFormats = inputFormats;
|
||||
static outputFormats = outputFormats;
|
||||
}
|
||||
|
||||
Duration.prototype.getParamDefaults = function () {
|
||||
return {
|
||||
inputFormat: DEFAULT_INPUT_FORMAT.kind,
|
||||
outputFormat: DEFAULT_OUTPUT_FORMAT.method,
|
||||
outputPrecision: DEFAULT_OUTPUT_PRECISION
|
||||
};
|
||||
};
|
||||
|
||||
return Duration;
|
||||
|
||||
function parseInputAsDuration(val, inputFormat) {
|
||||
const ratio = ratioToSeconds[inputFormat] || 1;
|
||||
const kind = inputFormat in ratioToSeconds ? 'seconds' : inputFormat;
|
||||
return moment.duration(val * ratio, kind);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
|
||||
export function stringifyIp() {
|
||||
|
||||
class IpFormat extends FieldFormat {
|
||||
_convert(val) {
|
||||
if (val === undefined || val === null) return '-';
|
||||
if (!isFinite(val)) return val;
|
||||
|
||||
// shazzam!
|
||||
return [val >>> 24, val >>> 16 & 0xFF, val >>> 8 & 0xFF, val & 0xFF].join('.');
|
||||
}
|
||||
|
||||
static id = 'ip';
|
||||
static title = 'IP Address';
|
||||
static fieldType = 'ip';
|
||||
}
|
||||
|
||||
return IpFormat;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
import { Numeral } from 'ui/stringify/types/_numeral';
|
||||
|
||||
export function stringifyNumber() {
|
||||
return Numeral.factory({
|
||||
id: 'number',
|
||||
title: 'Number'
|
||||
});
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import { Numeral } from 'ui/stringify/types/_numeral';
|
||||
|
||||
export function stringifyPercent() {
|
||||
return Numeral.factory({
|
||||
id: 'percent',
|
||||
title: 'Percentage',
|
||||
getParamDefaults: (getConfig) => {
|
||||
return {
|
||||
pattern: getConfig('format:percent:defaultPattern'),
|
||||
fractional: true
|
||||
};
|
||||
},
|
||||
prototype: {
|
||||
_convert: _.compose(Numeral.prototype._convert, function (val) {
|
||||
return this.param('fractional') ? val : val / 100;
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import { noWhiteSpace } from 'ui/utils/no_white_space';
|
||||
import { toJson } from 'ui/utils/aggressive_parse';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
import { shortenDottedString } from 'ui/utils/shorten_dotted_string';
|
||||
|
||||
export function stringifySource() {
|
||||
const template = _.template(noWhiteSpace(require('ui/stringify/types/_source.html')));
|
||||
|
||||
class SourceFormat extends FieldFormat {
|
||||
constructor(params, getConfig) {
|
||||
super(params);
|
||||
|
||||
this.getConfig = getConfig;
|
||||
}
|
||||
|
||||
static id = '_source';
|
||||
static title = '_source';
|
||||
static fieldType = '_source';
|
||||
}
|
||||
|
||||
SourceFormat.prototype._convert = {
|
||||
text: (value) => toJson(value),
|
||||
html: function sourceToHtml(source, field, hit) {
|
||||
if (!field) return this.getConverterFor('text')(source, field, hit);
|
||||
|
||||
const highlights = (hit && hit.highlight) || {};
|
||||
const formatted = field.indexPattern.formatHit(hit);
|
||||
const highlightPairs = [];
|
||||
const sourcePairs = [];
|
||||
|
||||
const isShortDots = this.getConfig('shortDots:enable');
|
||||
_.keys(formatted).forEach((key) => {
|
||||
const pairs = highlights[key] ? highlightPairs : sourcePairs;
|
||||
const field = isShortDots ? shortenDottedString(key) : key;
|
||||
const val = formatted[key];
|
||||
pairs.push([field, val]);
|
||||
}, []);
|
||||
|
||||
return template({ defPairs: highlightPairs.concat(sourcePairs) });
|
||||
}
|
||||
};
|
||||
|
||||
return SourceFormat;
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
import { asPrettyString } from 'ui/utils/as_pretty_string';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
import { shortenDottedString } from 'ui/utils/shorten_dotted_string';
|
||||
|
||||
export function stringifyString() {
|
||||
|
||||
class StringFormat extends FieldFormat {
|
||||
getParamDefaults() {
|
||||
return {
|
||||
transform: false
|
||||
};
|
||||
}
|
||||
|
||||
_base64Decode(val) {
|
||||
try {
|
||||
return window.atob(val);
|
||||
} catch (e) {
|
||||
return asPrettyString(val);
|
||||
}
|
||||
}
|
||||
|
||||
_toTitleCase(val) {
|
||||
return val.replace(/\w\S*/g, txt => { return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); });
|
||||
}
|
||||
|
||||
_convert(val) {
|
||||
switch (this.param('transform')) {
|
||||
case 'lower': return String(val).toLowerCase();
|
||||
case 'upper': return String(val).toUpperCase();
|
||||
case 'title': return this._toTitleCase(val);
|
||||
case 'short': return shortenDottedString(val);
|
||||
case 'base64': return this._base64Decode(val);
|
||||
default: return asPrettyString(val);
|
||||
}
|
||||
}
|
||||
|
||||
static id = 'string';
|
||||
static title = 'String';
|
||||
static fieldType = [
|
||||
'number',
|
||||
'boolean',
|
||||
'date',
|
||||
'ip',
|
||||
'attachment',
|
||||
'geo_point',
|
||||
'geo_shape',
|
||||
'string',
|
||||
'murmur3',
|
||||
'unknown',
|
||||
'conflict'
|
||||
];
|
||||
}
|
||||
|
||||
return StringFormat;
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
|
||||
export function stringifyTruncate() {
|
||||
const omission = '...';
|
||||
|
||||
class TruncateFormat extends FieldFormat {
|
||||
_convert(val) {
|
||||
const length = this.param('fieldLength');
|
||||
if (length > 0) {
|
||||
return _.trunc(val, {
|
||||
'length': length + omission.length,
|
||||
'omission': omission
|
||||
});
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static id = 'truncate';
|
||||
static title = 'Truncated String';
|
||||
static fieldType = ['string'];
|
||||
}
|
||||
|
||||
return TruncateFormat;
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import { FieldFormat } from 'ui/index_patterns/_field_format/field_format';
|
||||
import { getHighlightHtml } from 'ui/highlight';
|
||||
|
||||
export function stringifyUrl() {
|
||||
|
||||
const templateMatchRE = /{{([\s\S]+?)}}/g;
|
||||
|
||||
class UrlFormat extends FieldFormat {
|
||||
constructor(params) {
|
||||
super(params);
|
||||
this._compileTemplate = _.memoize(this._compileTemplate);
|
||||
}
|
||||
|
||||
getParamDefaults() {
|
||||
return {
|
||||
type: 'a',
|
||||
urlTemplate: null,
|
||||
labelTemplate: null
|
||||
};
|
||||
}
|
||||
|
||||
_formatLabel(value, url) {
|
||||
const template = this.param('labelTemplate');
|
||||
if (url == null) url = this._formatUrl(value);
|
||||
if (!template) return url;
|
||||
|
||||
return this._compileTemplate(template)({
|
||||
value: value,
|
||||
url: url
|
||||
});
|
||||
}
|
||||
|
||||
_formatUrl(value) {
|
||||
const template = this.param('urlTemplate');
|
||||
if (!template) return value;
|
||||
|
||||
return this._compileTemplate(template)({
|
||||
value: encodeURIComponent(value),
|
||||
rawValue: value
|
||||
});
|
||||
}
|
||||
|
||||
_compileTemplate(template) {
|
||||
const parts = template.split(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
|
||||
let output = '';
|
||||
let i = -1;
|
||||
while (++i < parts.length) {
|
||||
if (i % 2) {
|
||||
if (locals.hasOwnProperty(parts[i])) {
|
||||
const local = locals[parts[i]];
|
||||
output += local == null ? '' : local;
|
||||
}
|
||||
} else {
|
||||
output += parts[i];
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
}
|
||||
|
||||
static id = 'url';
|
||||
static title = 'Url';
|
||||
static fieldType = [
|
||||
'number',
|
||||
'boolean',
|
||||
'date',
|
||||
'ip',
|
||||
'string',
|
||||
'murmur3',
|
||||
'unknown',
|
||||
'conflict'
|
||||
];
|
||||
}
|
||||
|
||||
UrlFormat.prototype._convert = {
|
||||
text: function (value) {
|
||||
return this._formatLabel(value);
|
||||
},
|
||||
|
||||
html: function (rawValue, field, hit) {
|
||||
const url = _.escape(this._formatUrl(rawValue));
|
||||
const label = _.escape(this._formatLabel(rawValue, url));
|
||||
|
||||
switch (this.param('type')) {
|
||||
case 'img':
|
||||
// If the URL hasn't been formatted to become a meaningful label then the best we can do
|
||||
// is tell screen readers where the image comes from.
|
||||
const imageLabel =
|
||||
label === url
|
||||
? `A dynamically-specified image located at ${url}`
|
||||
: label;
|
||||
|
||||
return `<img src="${url}" alt="${imageLabel}">`;
|
||||
default:
|
||||
let linkLabel;
|
||||
|
||||
if (hit && hit.highlight && hit.highlight[field.name]) {
|
||||
linkLabel = getHighlightHtml(label, hit.highlight[field.name]);
|
||||
} else {
|
||||
linkLabel = label;
|
||||
}
|
||||
|
||||
return `<a href="${url}" target="_blank">${linkLabel}</a>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return UrlFormat;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue