Skip to content

Commit

Permalink
[5.3] [fieldFormat] make formatted output non-bindable (#11911) (#11917)
Browse files Browse the repository at this point in the history
* [fieldFormat] make formatted output non-bindable (#11911)

* [fieldFormat] make formatted output non-bindable

* [aggTable] totals are consumed as text, produce text

* [tests] fix existing tests

* [fieldFormat/contentType] add tests for non-bindable wrapper

(cherry picked from commit 1d2341f)
(cherry picked from commit f85570d)

* fix import statement

* [metricVis] Fix html support (#11008)

* [metricVis] Fix html support

* [metric_vis] add test to verify 42d11b0

* [ui/vis] fix import

(cherry picked from commit 34c33f3)
(cherry picked from commit eea7622)

* fix import statement

(cherry picked from commit 07d9a3b)
  • Loading branch information
spalger committed May 19, 2017
1 parent ba76cbe commit 366c537
Show file tree
Hide file tree
Showing 11 changed files with 225 additions and 52 deletions.
60 changes: 60 additions & 0 deletions src/core_plugins/metric_vis/public/__tests__/metric_vis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import $ from 'jquery';
import ngMock from 'ng_mock';
import expect from 'expect.js';

import VisProvider from 'ui/vis';
import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern';
import MetricVisProvider from '../metric_vis';

describe('metric_vis', () => {
let setup = null;

beforeEach(ngMock.module('kibana'));
beforeEach(ngMock.inject((Private, $rootScope) => {
setup = () => {
const Vis = Private(VisProvider);
const metricVisType = Private(MetricVisProvider);
const indexPattern = Private(LogstashIndexPatternStubProvider);

indexPattern.stubSetFieldFormat('ip', 'url', {
urlTemplate: 'http://ip.info?address={{value}}',
labelTemplate: 'ip[{{value}}]'
});

const vis = new Vis(indexPattern, {
type: 'metric',
aggs: [{ id: '1', type: 'top_hits', schema: 'metric', params: { field: 'ip' } }],
});

const $el = $('<div>');
const renderbot = metricVisType.createRenderbot(vis, $el);
const render = (esResponse) => {
renderbot.render(esResponse);
$rootScope.$digest();
};

return { $el, render };
};
}));

it('renders html value from field formatter', () => {
const { $el, render } = setup();

const ip = '235.195.237.208';
render({
hits: { total: 0, hits: [] },
aggregations: {
'1': {
hits: { total: 1, hits: [{ _source: { ip } }] }
}
}
});

const $link = $el
.find('a[href]')
.filter(function () { return this.href.includes('ip.info'); });

expect($link).to.have.length(1);
expect($link.text()).to.be(`ip[${ip}]`);
});
});
2 changes: 1 addition & 1 deletion src/core_plugins/metric_vis/public/metric_vis.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div ng-controller="KbnMetricVisController" class="metric-vis">
<div class="metric-container" ng-repeat="metric in metrics">
<div class="metric-value" ng-style="{'font-size': vis.params.fontSize+'pt'}">{{metric.value}}</div>
<div class="metric-value" ng-bind-html="metric.value" ng-style="{'font-size': vis.params.fontSize+'pt'}"></div>
<div>{{metric.label}}</div>
</div>
</div>
24 changes: 11 additions & 13 deletions src/test_utils/stub_index_pattern.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import _ from 'lodash';
import sinon from 'sinon';
import Promise from 'bluebird';
import IndexedArray from 'ui/indexed_array';
import IndexPattern from 'ui/index_patterns/_index_pattern';
import formatHit from 'ui/index_patterns/_format_hit';
import getComputedFields from 'ui/index_patterns/_get_computed_fields';
import RegistryFieldFormatsProvider from 'ui/registry/field_formats';
import IndexPatternsFlattenHitProvider from 'ui/index_patterns/_flatten_hit';
import IndexPatternsFieldProvider from 'ui/index_patterns/_field';
import IndexPatternsFieldListProvider from 'ui/index_patterns/_field_list';

export default function (Private) {
const fieldFormats = Private(RegistryFieldFormatsProvider);
const flattenHit = Private(IndexPatternsFlattenHitProvider);

const Field = Private(IndexPatternsFieldProvider);
const FieldList = Private(IndexPatternsFieldListProvider);

function StubIndexPattern(pattern, timeField, fields) {
this.id = pattern;
Expand All @@ -39,17 +37,17 @@ export default function (Private) {
this.formatHit = formatHit(this, fieldFormats.getDefaultInstance('string'));
this.formatField = this.formatHit.formatField;

this._indexFields = function () {
this.fields = new IndexedArray({
index: ['name'],
group: ['type'],
initialSet: fields.map(function (field) {
return new Field(this, field);
}, this)
});
this._reindexFields = function () {
this.fields = new FieldList(this, this.fields || fields);
};

this.stubSetFieldFormat = function (fieldName, id, params) {
const FieldFormat = fieldFormats.byId[id];
this.fieldFormatMap[fieldName] = new FieldFormat(params);
this._reindexFields();
};

this._indexFields();
this._reindexFields();
}

return StubIndexPattern;
Expand Down
9 changes: 2 additions & 7 deletions src/ui/public/agg_types/__tests__/buckets/_range.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import resp from 'fixtures/agg_resp/range';
import AggTypesIndexProvider from 'ui/agg_types/index';
import VisProvider from 'ui/vis';
import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import RegistryFieldFormatsProvider from 'ui/registry/field_formats';

describe('Range Agg', function () {

const buckets = values(resp.aggregations[1].buckets);
Expand All @@ -20,14 +20,9 @@ describe('Range Agg', function () {
range = Private(AggTypesIndexProvider).byName.range;
Vis = Private(VisProvider);
indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);

const BytesFormat = Private(RegistryFieldFormatsProvider).byId.bytes;

indexPattern.fieldFormatMap.bytes = new BytesFormat({
indexPattern.stubSetFieldFormat('bytes', 'bytes', {
pattern: '0,0.[000] b'
});

indexPattern._indexFields();
}));

describe('formating', function () {
Expand Down
8 changes: 2 additions & 6 deletions src/ui/public/field_editor/__tests__/field_editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import $ from 'jquery';
import ngMock from 'ng_mock';
import expect from 'expect.js';
import IndexPatternsFieldProvider from 'ui/index_patterns/_field';
import RegistryFieldFormatsProvider from 'ui/registry/field_formats';

import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern';
import _ from 'lodash';

describe('FieldEditor directive', function () {

let Field;
let StringFormat;
let $rootScope;

let compile;
Expand All @@ -27,12 +26,9 @@ describe('FieldEditor directive', function () {

$rootScope = $injector.get('$rootScope');
Field = Private(IndexPatternsFieldProvider);
StringFormat = Private(RegistryFieldFormatsProvider).getType('string');

$rootScope.indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider);
// set the field format for this field
$rootScope.indexPattern.fieldFormatMap.time = new StringFormat({ foo: 1, bar: 2 });
$rootScope.indexPattern._indexFields();
$rootScope.indexPattern.stubSetFieldFormat('time', 'string', { foo: 1, bar: 2 });
$rootScope.field = $rootScope.indexPattern.fields.byName.time;

compile = function () {
Expand Down
12 changes: 7 additions & 5 deletions src/ui/public/index_patterns/__tests__/_field_format.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _ from 'lodash';
import expect from 'expect.js';
import ngMock from 'ng_mock';
import IndexPatternsFieldFormatFieldFormatProvider from 'ui/index_patterns/_field_format/field_format';

describe('FieldFormat class', function () {

let FieldFormat;
Expand Down Expand Up @@ -86,7 +87,7 @@ describe('FieldFormat class', function () {
const html = f.getConverterFor('html');
expect(text).to.not.be(html);
expect(text('formatted')).to.be('formatted');
expect(html('formatted')).to.be('formatted');
expect(html('formatted')).to.be('<span ng-non-bindable>formatted</span>');
});

it('can be an object, with seperate text and html converter', function () {
Expand All @@ -100,7 +101,7 @@ describe('FieldFormat class', function () {
const html = f.getConverterFor('html');
expect(text).to.not.be(html);
expect(text('formatted text')).to.be('formatted text');
expect(html('formatted html')).to.be('formatted html');
expect(html('formatted html')).to.be('<span ng-non-bindable>formatted html</span>');
});

it('does not escape the output of the text converter', function () {
Expand All @@ -112,7 +113,8 @@ describe('FieldFormat class', function () {
it('does escape the output of the text converter if used in an html context', function () {
TestFormat.prototype._convert = _.constant('<script>alert("xxs");</script>');
const f = new TestFormat();
expect(f.convert('', 'html')).to.not.contain('<');
expect(_.trimRight(_.trimLeft(f.convert('', 'html'), '<span ng-non-bindable>'), '</span>'))
.to.not.contain('<');
});

it('does not escape the output of an html specific converter', function () {
Expand All @@ -123,7 +125,7 @@ describe('FieldFormat class', function () {

const f = new TestFormat();
expect(f.convert('', 'text')).to.be('<img>');
expect(f.convert('', 'html')).to.be('<img>');
expect(f.convert('', 'html')).to.be('<span ng-non-bindable><img></span>');
});
});

Expand All @@ -145,7 +147,7 @@ describe('FieldFormat class', function () {
};

const f = new TestFormat();
expect(f.convert('val', 'html')).to.be('html');
expect(f.convert('val', 'html')).to.be('<span ng-non-bindable>html</span>');
});

it('formats a value as " - " when no value is specified', function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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 IndexPatternsFieldFormatContentTypesProvider 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 contentTypes = new IndexPatternsFieldFormatContentTypesProvider();
const $rootScope = $injector.get('$rootScope');
const $compile = $injector.get('$compile');

$rootScope.callMe = callMe;

render = (convert) => {
const $el = $('<div>');
const { html } = contentTypes.setup({ _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);
});
});
6 changes: 5 additions & 1 deletion src/ui/public/index_patterns/_field_format/content_types.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default function contentTypesProvider() {

const types = {
html: function (format, convert) {
return function recurse(value, field, hit) {
function recurse(value, field, hit) {
if (value == null) {
return _.asPrettyString(value);
}
Expand All @@ -22,6 +22,10 @@ export default function contentTypesProvider() {
});

return subVals.join(',' + (useMultiLine ? '\n' : ' '));
}

return function (...args) {
return `<span ng-non-bindable>${recurse(...args)}</span>`;
};
},

Expand Down
Loading

0 comments on commit 366c537

Please sign in to comment.