Commit 43fa21b8 by Samuel Padgett

Bug 1449038 - Add a simple weighted search to KeywordService

https://trello.com/c/2fFSJMX1
https://bugzilla.redhat.com/show_bug.cgi?id=1449038
parent 0964cfa9
...@@ -2932,7 +2932,9 @@ angular.module('openshiftCommonServices') ...@@ -2932,7 +2932,9 @@ angular.module('openshiftCommonServices')
;'use strict'; ;'use strict';
angular.module("openshiftCommonServices") angular.module("openshiftCommonServices")
.service("KeywordService", function(){ .service("KeywordService", function($filter) {
var displayName = $filter('displayName');
var generateKeywords = function(filterText) { var generateKeywords = function(filterText) {
if (!filterText) { if (!filterText) {
...@@ -2977,8 +2979,57 @@ angular.module("openshiftCommonServices") ...@@ -2977,8 +2979,57 @@ angular.module("openshiftCommonServices")
return filteredObjects; return filteredObjects;
}; };
// Perform a simple weighted search.
// weightedSearch is like filterForKeywords, except each field has a weight
// and the result is a sorted array of matches. filterFields is an array of
// objects with keys `path` and `weight`.
var weightedSearch = function(objects, filterFields, keywords) {
if (_.isEmpty(keywords)) {
return [];
}
var results = [];
_.each(objects, function(object) {
// Keep a score for matches, weighted by field.
var score = 0;
_.each(keywords, function(regex) {
var matchesKeyword = false;
_.each(filterFields, function(field) {
var value = _.get(object, field.path);
if (!value) {
return;
}
if (regex.test(value)) {
// For each matching keyword, add the field weight to the score.
score += field.weight;
matchesKeyword = true;
}
});
if (!matchesKeyword) {
// We've missed a keyword. Set score to 0 and short circuit the loop.
score = 0;
return false;
}
});
if (score > 0) {
results.push({
object: object,
score: score
});
}
});
// Sort first by score, then by display name for items that have the same score.
var orderedResult = _.orderBy(results, ['score', displayName], ['desc', 'asc']);
return _.map(orderedResult, 'object');
};
return { return {
filterForKeywords: filterForKeywords, filterForKeywords: filterForKeywords,
weightedSearch: weightedSearch,
generateKeywords: generateKeywords generateKeywords: generateKeywords
}; };
}); });
......
...@@ -5000,7 +5000,9 @@ angular.module('openshiftCommonServices') ...@@ -5000,7 +5000,9 @@ angular.module('openshiftCommonServices')
;'use strict'; ;'use strict';
angular.module("openshiftCommonServices") angular.module("openshiftCommonServices")
.service("KeywordService", function(){ .service("KeywordService", ["$filter", function($filter) {
var displayName = $filter('displayName');
var generateKeywords = function(filterText) { var generateKeywords = function(filterText) {
if (!filterText) { if (!filterText) {
...@@ -5045,11 +5047,60 @@ angular.module("openshiftCommonServices") ...@@ -5045,11 +5047,60 @@ angular.module("openshiftCommonServices")
return filteredObjects; return filteredObjects;
}; };
// Perform a simple weighted search.
// weightedSearch is like filterForKeywords, except each field has a weight
// and the result is a sorted array of matches. filterFields is an array of
// objects with keys `path` and `weight`.
var weightedSearch = function(objects, filterFields, keywords) {
if (_.isEmpty(keywords)) {
return [];
}
var results = [];
_.each(objects, function(object) {
// Keep a score for matches, weighted by field.
var score = 0;
_.each(keywords, function(regex) {
var matchesKeyword = false;
_.each(filterFields, function(field) {
var value = _.get(object, field.path);
if (!value) {
return;
}
if (regex.test(value)) {
// For each matching keyword, add the field weight to the score.
score += field.weight;
matchesKeyword = true;
}
});
if (!matchesKeyword) {
// We've missed a keyword. Set score to 0 and short circuit the loop.
score = 0;
return false;
}
});
if (score > 0) {
results.push({
object: object,
score: score
});
}
});
// Sort first by score, then by display name for items that have the same score.
var orderedResult = _.orderBy(results, ['score', displayName], ['desc', 'asc']);
return _.map(orderedResult, 'object');
};
return { return {
filterForKeywords: filterForKeywords, filterForKeywords: filterForKeywords,
weightedSearch: weightedSearch,
generateKeywords: generateKeywords generateKeywords: generateKeywords
}; };
}); }]);
;'use strict'; ;'use strict';
angular.module('openshiftCommonServices') angular.module('openshiftCommonServices')
......
'use strict'; 'use strict';
angular.module("openshiftCommonServices") angular.module("openshiftCommonServices")
.service("KeywordService", function(){ .service("KeywordService", function($filter) {
var displayName = $filter('displayName');
var generateKeywords = function(filterText) { var generateKeywords = function(filterText) {
if (!filterText) { if (!filterText) {
...@@ -46,8 +48,57 @@ angular.module("openshiftCommonServices") ...@@ -46,8 +48,57 @@ angular.module("openshiftCommonServices")
return filteredObjects; return filteredObjects;
}; };
// Perform a simple weighted search.
// weightedSearch is like filterForKeywords, except each field has a weight
// and the result is a sorted array of matches. filterFields is an array of
// objects with keys `path` and `weight`.
var weightedSearch = function(objects, filterFields, keywords) {
if (_.isEmpty(keywords)) {
return [];
}
var results = [];
_.each(objects, function(object) {
// Keep a score for matches, weighted by field.
var score = 0;
_.each(keywords, function(regex) {
var matchesKeyword = false;
_.each(filterFields, function(field) {
var value = _.get(object, field.path);
if (!value) {
return;
}
if (regex.test(value)) {
// For each matching keyword, add the field weight to the score.
score += field.weight;
matchesKeyword = true;
}
});
if (!matchesKeyword) {
// We've missed a keyword. Set score to 0 and short circuit the loop.
score = 0;
return false;
}
});
if (score > 0) {
results.push({
object: object,
score: score
});
}
});
// Sort first by score, then by display name for items that have the same score.
var orderedResult = _.orderBy(results, ['score', displayName], ['desc', 'asc']);
return _.map(orderedResult, 'object');
};
return { return {
filterForKeywords: filterForKeywords, filterForKeywords: filterForKeywords,
weightedSearch: weightedSearch,
generateKeywords: generateKeywords generateKeywords: generateKeywords
}; };
}); });
...@@ -107,4 +107,94 @@ describe('KeywordService', function() { ...@@ -107,4 +107,94 @@ describe('KeywordService', function() {
expect(result).toEqual(mockData); expect(result).toEqual(mockData);
}); });
}); });
describe('#weightedSearch', function() {
var mockData = {
'sample-app': {
metadata: {
name: 'sample-app',
annotations: {
'openshift.io/display-name': 'Sample App',
'openshift.io/description': 'Sample app that uses Maria'
}
}
},
'maria-db': {
metadata: {
name: 'maria-db',
annotations: {
'openshift.io/display-name': 'Maria DB'
}
}
},
'mongo-db': {
metadata: {
name: 'mongo-db',
annotations: {
'openshift.io/display-name': 'Mongo DB',
tags: 'db'
}
}
}
};
it('should honor field weights', function() {
var keywords = KeywordService.generateKeywords('maria');
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations["openshift.io/description"]',
weight: 2
}], keywords);
expect(result).toEqual([ mockData['maria-db'], mockData['sample-app'] ]);
});
it('should weight multiple matches higher', function() {
var keywords = KeywordService.generateKeywords('db');
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations.tags',
weight: 2
}], keywords);
expect(result).toEqual([ mockData['mongo-db'], mockData['maria-db'] ]);
});
it('should match all keywords', function() {
var keywords = KeywordService.generateKeywords('maria db');
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations["openshift.io/description"]',
weight: 2
}], keywords);
expect(result).toEqual([ mockData['maria-db'] ]);
});
it('should return an empty array when no keywords', function() {
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations["openshift.io/description"]',
weight: 2
}], []);
expect(result.length).toEqual(0);
});
it('should return an empty array when no matches', function() {
var keywords = KeywordService.generateKeywords('MISSING');
var result = KeywordService.weightedSearch(mockData, [{
path: 'metadata.annotations["openshift.io/display-name"]',
weight: 10
}, {
path: 'metadata.annotations["openshift.io/description"]',
weight: 2
}], keywords);
expect(result.length).toEqual(0);
});
});
}); });
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment