mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[fieldFormat] implement highlighting
Asking for a formatted field of contentType 'html' will now highlight the formatted html when appropriate. In order to do this the format converter function must be passed the field and hit that corelate to the value. This makes the code read a bit like this: var convert = field.format.getConverterFor('html'); var formatted = convert(value, field, hit); The field and hit arguments are not required if highlighting is not desired (like when formatting results that are not search hits).
This commit is contained in:
parent
e19024e22e
commit
fa41afceb2
11 changed files with 90 additions and 117 deletions
|
@ -22,7 +22,7 @@ define(function (require) {
|
|||
* <tr ng-repeat="row in rows" kbn-table-row="row"></tr>
|
||||
* ```
|
||||
*/
|
||||
module.directive('kbnTableRow', function ($compile, highlightFilter) {
|
||||
module.directive('kbnTableRow', function ($compile) {
|
||||
var openRowHtml = require('text!components/doc_table/components/table_row/open.html');
|
||||
var detailsHtml = require('text!components/doc_table/components/table_row/details.html');
|
||||
var cellTemplate = _.template(require('text!components/doc_table/components/table_row/cell.html'));
|
||||
|
@ -150,18 +150,7 @@ define(function (require) {
|
|||
*/
|
||||
function _displayField(row, fieldName, breakWords) {
|
||||
var indexPattern = $scope.indexPattern;
|
||||
|
||||
if (fieldName === '_source') {
|
||||
if (!row.$$_formattedSource) {
|
||||
var field = indexPattern.fields.byName._source;
|
||||
var converter = field.format.getConverterFor('html');
|
||||
row.$$_formattedSource = converter(row._source, indexPattern, row);
|
||||
}
|
||||
return row.$$_formattedSource;
|
||||
}
|
||||
|
||||
var text = indexPattern.formatField(row, fieldName);
|
||||
text = highlightFilter(text, row.highlight && row.highlight[fieldName]);
|
||||
|
||||
if (breakWords) {
|
||||
text = addWordBreaks(text, MIN_LINE_LENGTH);
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
<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>
|
|
@ -49,7 +49,7 @@
|
|||
tooltip-placement="top"
|
||||
tooltip="Objects in arrays are not well supported."
|
||||
class="fa fa-warning text-color-warning ng-scope doc-viewer-object-array"></i>
|
||||
<div class="doc-viewer-value" ng-bind-html="(typeof(formatted[field]) === 'undefined' ? hit[field] : formatted[field]) | highlight : hit.highlight[field] | trustAsHtml"></div>
|
||||
<div class="doc-viewer-value" ng-bind-html="typeof(formatted[field]) === 'undefined' ? hit[field] : formatted[field] | trustAsHtml"></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
|
@ -36,12 +36,11 @@ define(function (require) {
|
|||
|
||||
/**
|
||||
* 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
|
||||
* @param {string} [contentType=text]
|
||||
* @return {function} - a bound converter function
|
||||
*/
|
||||
FieldFormat.prototype.getConverterFor = function (contentType) {
|
||||
return this._convert[contentType] || this._convert.text;
|
||||
return this._convert[contentType || 'text'];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
define(function (require) {
|
||||
return function contentTypesProvider() {
|
||||
return function contentTypesProvider(highlightFilter) {
|
||||
var _ = require('lodash');
|
||||
var angular = require('angular');
|
||||
|
||||
var types = {
|
||||
html: function (format, convert) {
|
||||
return function recurse(value) {
|
||||
return function recurse(value, field, hit) {
|
||||
var type = typeof value;
|
||||
|
||||
if (type === 'object' && typeof value.map === 'function') {
|
||||
|
@ -19,14 +19,14 @@ define(function (require) {
|
|||
return value.$$_formattedField = subVals.join(',' + (useMultiLine ? '\n' : ' '));
|
||||
}
|
||||
|
||||
return convert.call(format, value);
|
||||
return convert.call(format, value, field, hit);
|
||||
};
|
||||
},
|
||||
|
||||
text: function (format, convert) {
|
||||
return function recurse(value) {
|
||||
if (value && typeof value.map === 'function') {
|
||||
return angular.toJson(value.map(recurse));
|
||||
return angular.toJson(value.map(recurse), true);
|
||||
}
|
||||
|
||||
return _.escape(convert.call(format, value));
|
||||
|
@ -34,21 +34,26 @@ define(function (require) {
|
|||
}
|
||||
};
|
||||
|
||||
function fallbackText(value) {
|
||||
return _.escape(_.asPrettyString(value));
|
||||
}
|
||||
|
||||
function fallbackHtml(value, field, hit) {
|
||||
var formatted = this.convert(value, 'text');
|
||||
|
||||
if (!hit || !hit.highlight || !hit.highlight[field.name]) {
|
||||
return formatted;
|
||||
} else {
|
||||
return highlightFilter(formatted, hit.highlight[field.name]);
|
||||
}
|
||||
}
|
||||
|
||||
function setup(format) {
|
||||
var src = format._convert || {};
|
||||
var converters = format._convert = {};
|
||||
|
||||
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);
|
||||
}
|
||||
converters.text = types.text(format, src.text || fallbackText);
|
||||
converters.html = types.html(format, src.html || fallbackHtml);
|
||||
|
||||
return format._convert;
|
||||
}
|
||||
|
|
|
@ -5,36 +5,41 @@ define(function (require) {
|
|||
|
||||
return function (indexPattern, defaultFormat) {
|
||||
|
||||
function transformField(memo, val, name) {
|
||||
var field = indexPattern.fields.byName[name];
|
||||
return memo[name] = field ? field.format.convert(val, 'html') : defaultFormat.convert(val, 'html');
|
||||
function convert(hit, val, fieldName) {
|
||||
var field = indexPattern.fields.byName[fieldName];
|
||||
if (!field) return defaultFormat.convert(val, 'html');
|
||||
return field.format.getConverterFor('html')(val, field, hit);
|
||||
}
|
||||
|
||||
function formatHit(hit) {
|
||||
if (hit.$$_formatted) return hit.$$_formatted;
|
||||
var cache = hit.$$_partialFormatted = hit.$$_formatted = {};
|
||||
|
||||
// use and update the partial cache, but don't rewrite it. _source is stored in partials
|
||||
// but not $$_formatted
|
||||
var partials = hit.$$_partialFormatted || (hit.$$_partialFormatted = {});
|
||||
var cache = hit.$$_formatted = {};
|
||||
|
||||
_.forOwn(indexPattern.flattenHit(hit), function (val, fieldName) {
|
||||
transformField(cache, val, fieldName);
|
||||
// sync the formatted and partial cache
|
||||
var formatted = partials[fieldName] == null ? convert(hit, val, fieldName) : partials[fieldName];
|
||||
cache[fieldName] = partials[fieldName] = formatted;
|
||||
});
|
||||
|
||||
return hit.$$_formatted;
|
||||
return cache;
|
||||
}
|
||||
|
||||
formatHit.formatField = function (hit, fieldName) {
|
||||
// formatHit was previously called
|
||||
if (hit.$$_formatted) return hit.$$_formatted[fieldName];
|
||||
|
||||
var partial = hit.$$_partialFormatted;
|
||||
if (partial && _.has(partial, fieldName)) {
|
||||
return partial[fieldName];
|
||||
var partials = hit.$$_partialFormatted;
|
||||
if (partials && partials[fieldName] != null) {
|
||||
return partials[fieldName];
|
||||
}
|
||||
|
||||
if (!partial) {
|
||||
partial = hit.$$_partialFormatted = {};
|
||||
if (!partials) {
|
||||
partials = hit.$$_partialFormatted = {};
|
||||
}
|
||||
|
||||
return transformField(partial, indexPattern.flattenHit(hit)[fieldName], fieldName);
|
||||
var val = fieldName === '_source' ? hit._source : indexPattern.flattenHit(hit)[fieldName];
|
||||
return partials[fieldName] = convert(hit, val, fieldName);
|
||||
};
|
||||
|
||||
return formatHit;
|
||||
|
|
|
@ -1,59 +1,41 @@
|
|||
define(function (require) {
|
||||
return function _StringProvider(Private, shortDotsFilter, highlightFilter) {
|
||||
return function _SourceProvider(Private, shortDotsFilter) {
|
||||
var _ = require('lodash');
|
||||
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
|
||||
var noWhiteSpace = require('utils/no_white_space');
|
||||
var template = _.template(noWhiteSpace(require('text!components/stringify/types/_source.html')));
|
||||
|
||||
var angular = require('angular');
|
||||
|
||||
_(Source).inherits(FieldFormat);
|
||||
function Source(params) {
|
||||
var self = this;
|
||||
|
||||
// _source converters are weird and override the _convert object
|
||||
// that is setup by the FieldFormat constructor
|
||||
Source.prototype._convert = {};
|
||||
|
||||
Source.Super.call(self, params);
|
||||
|
||||
function sourceToText(source) {
|
||||
return _.escape(JSON.stringify(source));
|
||||
}
|
||||
|
||||
function sourceToHtml(source, indexPattern, hit) {
|
||||
if (!indexPattern) return sourceToText(source);
|
||||
var highlights = (hit && hit.highlight) || {};
|
||||
|
||||
var formatted = indexPattern.formatHit(hit);
|
||||
var highlightPairs = [];
|
||||
var sourcePairs = [];
|
||||
|
||||
_.keys(formatted).forEach(function (key) {
|
||||
var pairs = sourcePairs;
|
||||
var field = shortDotsFilter(key);
|
||||
var val = formatted[key];
|
||||
|
||||
if (highlights[key]) {
|
||||
pairs = highlightPairs;
|
||||
val = highlightFilter(val, highlights[key]);
|
||||
}
|
||||
|
||||
pairs.push([field, val]);
|
||||
}, []);
|
||||
|
||||
return template({ defPairs: highlightPairs.concat(sourcePairs) });
|
||||
}
|
||||
|
||||
self._convert = {
|
||||
text: sourceToText,
|
||||
html: sourceToHtml
|
||||
};
|
||||
Source.Super.call(this, params);
|
||||
}
|
||||
|
||||
Source.id = '_source';
|
||||
Source.title = '_source';
|
||||
Source.fieldType = '_source';
|
||||
|
||||
Source.prototype._convert = {
|
||||
text: angular.toJson,
|
||||
html: function sourceToHtml(source, field, hit) {
|
||||
if (!field) return this.getConverter('text')(source, field, hit);
|
||||
|
||||
var highlights = (hit && hit.highlight) || {};
|
||||
var formatted = field.indexPattern.formatHit(hit);
|
||||
var highlightPairs = [];
|
||||
var sourcePairs = [];
|
||||
|
||||
_.keys(formatted).forEach(function (key) {
|
||||
var pairs = highlights[key] ? highlightPairs : sourcePairs;
|
||||
var field = shortDotsFilter(key);
|
||||
var val = formatted[key];
|
||||
pairs.push([field, val]);
|
||||
}, []);
|
||||
|
||||
return template({ defPairs: highlightPairs.concat(sourcePairs) });
|
||||
}
|
||||
};
|
||||
|
||||
return Source;
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
define(function (require) {
|
||||
return function UrlFormatProvider(Private) {
|
||||
return function UrlFormatProvider(Private, highlightFilter) {
|
||||
var _ = require('lodash');
|
||||
|
||||
var FieldFormat = Private(require('components/index_patterns/_field_format/FieldFormat'));
|
||||
|
@ -44,20 +44,25 @@ define(function (require) {
|
|||
];
|
||||
|
||||
Url.prototype._convert = {
|
||||
html: function (rawValue) {
|
||||
text: function (value) {
|
||||
var template = this.param('template');
|
||||
return !template ? value : this._compileTemplate(template)(value);
|
||||
},
|
||||
|
||||
html: function (rawValue, field, hit) {
|
||||
var url = this.convert(rawValue, 'text');
|
||||
var value = _.escape(rawValue);
|
||||
|
||||
switch (this.param('type')) {
|
||||
case 'img': return '<img src="' + url + '" alt="' + value + '">';
|
||||
case 'img': return '<img src="' + url + '" alt="' + value + '" title="' + value + '">';
|
||||
default:
|
||||
return '<a href="' + url + '" target="_blank">' + url + '</a>';
|
||||
}
|
||||
},
|
||||
var urlDisplay = url;
|
||||
if (hit && hit.highlight && hit.highlight[field.name]) {
|
||||
urlDisplay = highlightFilter(url, hit.highlight[field.name]);
|
||||
}
|
||||
|
||||
text: function (value) {
|
||||
var template = this.param('template');
|
||||
return !template ? value : this._compileTemplate(template)(value);
|
||||
return '<a href="' + url + '" target="_blank">' + urlDisplay + '</a>';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
define(function (require) {
|
||||
define(function () {
|
||||
function AggConfigResult(aggConfig, parent, value, key) {
|
||||
this.$parent = parent;
|
||||
this.key = key;
|
||||
|
@ -32,8 +32,8 @@ define(function (require) {
|
|||
return this.aggConfig.createFilter(this.key);
|
||||
};
|
||||
|
||||
AggConfigResult.prototype.toString = function () {
|
||||
return this.aggConfig.fieldFormatter()(this.value);
|
||||
AggConfigResult.prototype.toString = function (contentType) {
|
||||
return this.aggConfig.fieldFormatter(contentType)(this.value);
|
||||
};
|
||||
|
||||
AggConfigResult.prototype.valueOf = function () {
|
||||
|
|
|
@ -23,7 +23,7 @@ define(function (require) {
|
|||
$cell.scope = $scope.$new();
|
||||
$cell.addClass('cell-hover');
|
||||
$cell.attr('ng-click', 'clickHandler()');
|
||||
$cell.scope.clickHandler = function (negate) {
|
||||
$cell.scope.clickHandler = function () {
|
||||
clickHandler({ point: { aggConfigResult: aggConfigResult } });
|
||||
};
|
||||
return $compile($cell)($cell.scope);
|
||||
|
@ -33,7 +33,7 @@ define(function (require) {
|
|||
if (contents.type === 'bucket' && contents.aggConfig.field() && contents.aggConfig.field().filterable) {
|
||||
$cell = createAggConfigResultCell(contents);
|
||||
}
|
||||
contents = contents.toString();
|
||||
contents = contents.toString('html');
|
||||
}
|
||||
|
||||
if (_.isObject(contents)) {
|
||||
|
|
|
@ -15,7 +15,8 @@ define(function (require) {
|
|||
'number',
|
||||
'percent',
|
||||
'string',
|
||||
'url'
|
||||
'url',
|
||||
'_source'
|
||||
];
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue