Merge pull request #2396 from lightpriest/fuzzy-fix-lookup

Fix fuzzy search lookup issues
This commit is contained in:
Julius Volz 2017-02-28 19:06:30 +01:00 committed by GitHub
commit 7e14533b32
5 changed files with 204 additions and 47 deletions

File diff suppressed because one or more lines are too long

View File

@ -212,13 +212,12 @@ Prometheus.Graph.prototype.populateInsertableMetrics = function() {
items: "all",
matcher: function(item) {
// If we have result for current query, skip
if (!self.fuzzyResult.query || self.fuzzyResult.query !== this.query) {
if (self.fuzzyResult.query !== this.query) {
self.fuzzyResult.query = this.query;
self.fuzzyResult.map = {};
self.fuzzyResult.result = fuzzy.filter(this.query.replace('_', ' '), metrics, {
self.fuzzyResult.result = fuzzy.filter(this.query.replace(/ /g, ''), metrics, {
pre: '<strong>',
post: '</strong>',
extract: function(el) { return el.replace('_', ' ') }
post: '</strong>'
});
self.fuzzyResult.result.forEach(function(r) {
self.fuzzyResult.map[r.original] = r;
@ -237,7 +236,7 @@ Prometheus.Graph.prototype.populateInsertableMetrics = function() {
},
highlighter: function (item) {
return $('<div>' + self.fuzzyResult.map[item].string.replace(' ', '_') + '</div>')
return $('<div>' + self.fuzzyResult.map[item].string + '</div>')
},
});
// This needs to happen after attaching the typeahead plugin, as it

View File

@ -1,26 +0,0 @@
// Copyright (c) 2012 Matt York
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// https://github.com/mattyork/fuzzy/blob/3613086aa40c180ca722aeaf48cef575dc57eb5d/fuzzy-min.js
(function(){var root=this;var fuzzy={};if(typeof exports!=="undefined"){module.exports=fuzzy}else{root.fuzzy=fuzzy}fuzzy.simpleFilter=function(pattern,array){return array.filter(function(str){return fuzzy.test(pattern,str)})};fuzzy.test=function(pattern,str){return fuzzy.match(pattern,str)!==null};fuzzy.match=function(pattern,str,opts){opts=opts||{};var patternIdx=0,result=[],len=str.length,totalScore=0,currScore=0,pre=opts.pre||"",post=opts.post||"",compareString=opts.caseSensitive&&str||str.toLowerCase(),ch;pattern=opts.caseSensitive&&pattern||pattern.toLowerCase();for(var idx=0;idx<len;idx++){ch=str[idx];if(compareString[idx]===pattern[patternIdx]){ch=pre+ch+post;patternIdx+=1;currScore+=1+currScore}else{currScore=0}totalScore+=currScore;result[result.length]=ch}if(patternIdx===pattern.length){totalScore=compareString===pattern?Infinity:totalScore;return{rendered:result.join(""),score:totalScore}}return null};fuzzy.filter=function(pattern,arr,opts){if(!arr||arr.length===0){return[]}if(typeof pattern!=="string"){return arr}opts=opts||{};return arr.reduce(function(prev,element,idx,arr){var str=element;if(opts.extract){str=opts.extract(element)}var rendered=fuzzy.match(pattern,str,opts);if(rendered!=null){prev[prev.length]={string:rendered.rendered,score:rendered.score,index:idx,original:element}}return prev},[]).sort(function(a,b){var compare=b.score-a.score;if(compare)return compare;return a.index-b.index})}})();

182
web/ui/static/vendor/fuzzy/fuzzy.js vendored Normal file
View File

@ -0,0 +1,182 @@
/*
* Fuzzy
* https://github.com/myork/fuzzy
*
* Copyright (c) 2012 Matt York
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* A slightly modified version of https://github.com/mattyork/fuzzy/blob/3613086aa40c180ca722aeaf48cef575dc57eb5d/lib/fuzzy.js
*/
(function() {
var root = this;
var fuzzy = {};
// Use in node or in browser
if (typeof exports !== 'undefined') {
module.exports = fuzzy;
} else {
root.fuzzy = fuzzy;
}
// Return all elements of `array` that have a fuzzy
// match against `pattern`.
fuzzy.simpleFilter = function(pattern, array) {
return array.filter(function(str) {
return fuzzy.test(pattern, str);
});
};
// Does `pattern` fuzzy match `str`?
fuzzy.test = function(pattern, str) {
return fuzzy.match(pattern, str) !== null;
};
// If `pattern` matches `str`, wrap each matching character
// in `opts.pre` and `opts.post`. If no match, return null
fuzzy.match = function(pattern, str, opts, _fromIndex) {
opts = opts || {};
var patternIdx = 0
, result = []
, len = str.length
, fromIndex = _fromIndex || 0
, totalScore = 0
, currScore = 0
// prefix
, pre = opts.pre || ''
// suffix
, post = opts.post || ''
// String to compare against. This might be a lowercase version of the
// raw string
, compareString = opts.caseSensitive && str || str.toLowerCase()
, ch;
pattern = opts.caseSensitive && pattern || pattern.toLowerCase();
// If there's an exact match, add pre/post, max out score and skip the lookup
if (compareString === pattern) {
return {
rendered: pre + compareString.split('').join(post+pre) + post,
score: Infinity
};
}
// For each character in the string, either add it to the result
// or wrap in template if it's the next string in the pattern
for(var idx = 0; idx < len; idx++) {
ch = str[idx];
if(idx >= fromIndex && compareString[idx] === pattern[patternIdx]) {
ch = pre + ch + post;
patternIdx += 1;
// consecutive characters should increase the score more than linearly
currScore += 1 + currScore;
} else {
currScore = 0;
}
totalScore += currScore;
result[result.length] = ch;
}
// return rendered string if we have a match for every char
if(patternIdx === pattern.length) {
var nextPossible = str.indexOf(pattern[0], str.indexOf(pattern[0], fromIndex) + 1)
, candidate;
// If possible, try to find a better match at the rest of the string
if (nextPossible > -1 && str.length - nextPossible >= pattern.length) {
var candidate = fuzzy.match(pattern, str, opts, nextPossible);
}
return candidate && candidate.score > totalScore ? candidate : {
rendered: result.join(''), score: totalScore
};
}
return null;
};
// The normal entry point. Filters `arr` for matches against `pattern`.
// It returns an array with matching values of the type:
//
// [{
// string: '<b>lah' // The rendered string
// , index: 2 // The index of the element in `arr`
// , original: 'blah' // The original element in `arr`
// }]
//
// `opts` is an optional argument bag. Details:
//
// opts = {
// // string to put before a matching character
// pre: '<b>'
//
// // string to put after matching character
// , post: '</b>'
//
// // Optional function. Input is an entry in the given arr`,
// // output should be the string to test `pattern` against.
// // In this example, if `arr = [{crying: 'koala'}]` we would return
// // 'koala'.
// , extract: function(arg) { return arg.crying; }
// }
fuzzy.filter = function(pattern, arr, opts) {
if(!arr || arr.length === 0) {
return [];
}
if (typeof pattern !== 'string' || pattern === '') {
return arr;
}
opts = opts || {};
return arr
.reduce(function(prev, element, idx, arr) {
var str = element;
if(opts.extract) {
str = opts.extract(element);
}
var rendered = fuzzy.match(pattern, str, opts);
if(rendered != null) {
prev[prev.length] = {
string: rendered.rendered
, score: rendered.score
, index: idx
, original: element
};
}
return prev;
}, [])
// Sort by score. Browsers are inconsistent wrt stable/unstable
// sorting, so force stable by using the index in the case of tie.
// See http://ofb.net/~sethml/is-sort-stable.html
.sort(function(a,b) {
var compare = b.score - a.score;
if(compare) return compare;
return a.index - b.index;
});
};
}());

View File

@ -10,7 +10,7 @@
<script src="{{ pathPrefix }}/static/vendor/moment/moment.min.js"></script>
<script src="{{ pathPrefix }}/static/vendor/eonasdan-bootstrap-datetimepicker/bootstrap-datetimepicker.min.js"></script>
<script src="{{ pathPrefix }}/static/vendor/bootstrap3-typeahead/bootstrap3-typeahead.min.js"></script>
<script src="{{ pathPrefix }}/static/vendor/fuzzy.js"></script>
<script src="{{ pathPrefix }}/static/vendor/fuzzy/fuzzy.js"></script>
<script src="{{ pathPrefix }}/static/vendor/mustache/mustache.min.js"></script>
<script src="{{ pathPrefix }}/static/vendor/js/jquery.selection.js"></script>